diff --git a/apis/lagoon/v1beta1/lagoonbuild_types.go b/apis/lagoon/v1beta1/lagoonbuild_types.go index 5964211..c901ef2 100644 --- a/apis/lagoon/v1beta1/lagoonbuild_types.go +++ b/apis/lagoon/v1beta1/lagoonbuild_types.go @@ -140,6 +140,12 @@ type Project struct { EnvironmentIdling *int `json:"environmentIdling,omitempty"` ProjectIdling *int `json:"projectIdling,omitempty"` StorageCalculator *int `json:"storageCalculator,omitempty"` + Organization *Organization `json:"organization,omitempty"` +} + +type Organization struct { + ID *uint `json:"id,omitempty"` + Name string `json:"name,omitempty"` } // Variables contains the project and environment variables from lagoon. diff --git a/apis/lagoon/v1beta1/lagoontask_types.go b/apis/lagoon/v1beta1/lagoontask_types.go index 1708f0b..3231e53 100644 --- a/apis/lagoon/v1beta1/lagoontask_types.go +++ b/apis/lagoon/v1beta1/lagoontask_types.go @@ -124,6 +124,7 @@ type LagoonTaskProject struct { Name string `json:"name"` NamespacePattern string `json:"namespacePattern,omitempty"` Variables LagoonVariables `json:"variables,omitempty"` + Organization *Organization `json:"organization,omitempty"` } // LagoonTaskEnvironment defines the lagoon environment information. diff --git a/apis/lagoon/v1beta1/zz_generated.deepcopy.go b/apis/lagoon/v1beta1/zz_generated.deepcopy.go index c19a407..842af61 100644 --- a/apis/lagoon/v1beta1/zz_generated.deepcopy.go +++ b/apis/lagoon/v1beta1/zz_generated.deepcopy.go @@ -466,6 +466,11 @@ func (in *LagoonTaskList) DeepCopyObject() runtime.Object { func (in *LagoonTaskProject) DeepCopyInto(out *LagoonTaskProject) { *out = *in in.Variables.DeepCopyInto(&out.Variables) + if in.Organization != nil { + in, out := &in.Organization, &out.Organization + *out = new(Organization) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LagoonTaskProject. @@ -571,6 +576,26 @@ func (in *Monitoring) DeepCopy() *Monitoring { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Organization) DeepCopyInto(out *Organization) { + *out = *in + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(uint) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Organization. +func (in *Organization) DeepCopy() *Organization { + if in == nil { + return nil + } + out := new(Organization) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Project) DeepCopyInto(out *Project) { *out = *in @@ -606,6 +631,11 @@ func (in *Project) DeepCopyInto(out *Project) { *out = new(int) **out = **in } + if in.Organization != nil { + in, out := &in.Organization, &out.Organization + *out = new(Organization) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Project. diff --git a/config/crd/bases/crd.lagoon.sh_lagoonbuilds.yaml b/config/crd/bases/crd.lagoon.sh_lagoonbuilds.yaml index e3b5946..bbc2dc2 100644 --- a/config/crd/bases/crd.lagoon.sh_lagoonbuilds.yaml +++ b/config/crd/bases/crd.lagoon.sh_lagoonbuilds.yaml @@ -94,6 +94,13 @@ spec: type: string namespacePattern: type: string + organization: + properties: + id: + type: integer + name: + type: string + type: object productionEnvironment: type: string projectIdling: diff --git a/config/crd/bases/crd.lagoon.sh_lagoontasks.yaml b/config/crd/bases/crd.lagoon.sh_lagoontasks.yaml index 180051c..f1394f9 100644 --- a/config/crd/bases/crd.lagoon.sh_lagoontasks.yaml +++ b/config/crd/bases/crd.lagoon.sh_lagoontasks.yaml @@ -109,6 +109,13 @@ spec: type: string namespacePattern: type: string + organization: + properties: + id: + type: integer + name: + type: string + type: object variables: description: Variables contains the project and environment variables from lagoon. diff --git a/controller-test.sh b/controller-test.sh index 415ce1b..014247c 100755 --- a/controller-test.sh +++ b/controller-test.sh @@ -17,6 +17,7 @@ CHECK_TIMEOUT=20 NS=nginx-example-main LBUILD=7m5zypx LBUILD2=8m5zypx +LBUILD3=9m5zypx HARBOR_VERSION=${HARBOR_VERSION:-1.6.4} @@ -201,6 +202,37 @@ kubectl -n $CONTROLLER_NAMESPACE patch lagoonbuilds.crd.lagoon.sh lagoon-build-$ sleep 10 check_lagoon_build lagoon-build-${LBUILD} +echo "==> Trigger a lagoon build using kubectl apply and check organization labels exist" +kubectl -n $CONTROLLER_NAMESPACE apply -f test-resources/example-project2.yaml +# patch the resource with the controller namespace +kubectl -n $CONTROLLER_NAMESPACE patch lagoonbuilds.crd.lagoon.sh lagoon-build-${LBUILD2} --type=merge --patch '{"metadata":{"labels":{"lagoon.sh/controller":"'$CONTROLLER_NAMESPACE'"}}}' +# patch the resource with a random label to bump the controller event filter +kubectl -n $CONTROLLER_NAMESPACE patch lagoonbuilds.crd.lagoon.sh lagoon-build-${LBUILD2} --type=merge --patch '{"metadata":{"labels":{"bump":"bump"}}}' +sleep 10 +check_lagoon_build lagoon-build-${LBUILD2} +echo "==> Check organization.lagoon.sh/name label exists on namespace" +if ! $(kubectl get namespace -l 'organization.lagoon.sh/name=test-org' --no-headers 2> /dev/null | grep -q ${NS}); then + echo "==> Build failed to set organization name label on namespace" + clean_task_test_resources + check_controller_log ${1} + tear_down + echo "============== FAILED ===============" + exit 1 +else + echo "===> label exists" +fi +echo "==> Check organization.lagoon.sh/id label exists on namespace" +if ! $(kubectl get namespace -l 'organization.lagoon.sh/id=123' --no-headers 2> /dev/null | grep -q ${NS}); then + echo "==> Build failed to set organization id label on namespace" + clean_task_test_resources + check_controller_log ${1} + tear_down + echo "============== FAILED ===============" + exit 1 +else + echo "===> label exists" +fi + echo "==> Trigger a Task using kubectl apply to test dynamic secret mounting" kubectl -n $NS apply -f test-resources/dynamic-secret-in-task-project1-secret.yaml @@ -235,7 +267,7 @@ echo ' "routing_key":"ci-local-controller-kubernetes:builddeploy", "payload":"{ \"metadata\": { - \"name\": \"lagoon-build-8m5zypx\" + \"name\": \"lagoon-build-9m5zypx\" }, \"spec\": { \"build\": { @@ -246,7 +278,7 @@ echo ' \"project\": { \"name\": \"nginx-example\", \"environment\": \"main\", - \"uiLink\": \"https:\/\/dashboard.amazeeio.cloud\/projects\/project\/project-environment\/deployments\/lagoon-build-8m5zypx\", + \"uiLink\": \"https:\/\/dashboard.amazeeio.cloud\/projects\/project\/project-environment\/deployments\/lagoon-build-9m5zypx\", \"routerPattern\": \"main-nginx-example\", \"environmentType\": \"production\", \"productionEnvironment\": \"main\", @@ -274,11 +306,29 @@ echo ' curl -s -u guest:guest -H "Accept: application/json" -H "Content-Type:application/json" -X POST -d @payload.json http://172.17.0.1:15672/api/exchanges/%2f/lagoon-tasks/publish echo "" sleep 10 -check_lagoon_build lagoon-build-${LBUILD2} +check_lagoon_build lagoon-build-${LBUILD3} echo "==> Check pod cleanup worked" CHECK_COUNTER=1 -until ! $(kubectl -n nginx-example-main get pods lagoon-build-7m5zypx &> /dev/null) +# wait for first build pod to clean up +until ! $(kubectl -n nginx-example-main get pods lagoon-build-${LBUILD} &> /dev/null) +do +if [ $CHECK_COUNTER -lt 14 ]; then + let CHECK_COUNTER=CHECK_COUNTER+1 + echo "Build pod not deleted yet" + sleep 5 +else + echo "Timeout of 70seconds for build pod clean up check" + check_controller_log + tear_down + echo "================ END ================" + echo "============== FAILED ===============" + exit 1 +fi +done +CHECK_COUNTER=1 +# wait for second build pod to clean up +until ! $(kubectl -n nginx-example-main get pods lagoon-build-${LBUILD2} &> /dev/null) do if [ $CHECK_COUNTER -lt 14 ]; then let CHECK_COUNTER=CHECK_COUNTER+1 diff --git a/controllers/v1beta1/build_helpers.go b/controllers/v1beta1/build_helpers.go index 50703cc..81831ab 100644 --- a/controllers/v1beta1/build_helpers.go +++ b/controllers/v1beta1/build_helpers.go @@ -106,6 +106,10 @@ func (r *LagoonBuildReconciler) getOrCreateNamespace(ctx context.Context, namesp "lagoon.sh/environmentType": lagoonBuild.Spec.Project.EnvironmentType, "lagoon.sh/controller": r.ControllerNamespace, } + if lagoonBuild.Spec.Project.Organization != nil { + nsLabels["organization.lagoon.sh/id"] = fmt.Sprintf("%d", *lagoonBuild.Spec.Project.Organization.ID) + nsLabels["organization.lagoon.sh/name"] = lagoonBuild.Spec.Project.Organization.Name + } if lagoonBuild.Spec.Project.ID != nil { nsLabels["lagoon.sh/projectId"] = fmt.Sprintf("%d", *lagoonBuild.Spec.Project.ID) } @@ -712,6 +716,19 @@ func (r *LagoonBuildReconciler) processBuild(ctx context.Context, opLog logr.Log Value: r.LFFDefaultRWX2RWO, }) } + + // set the organization variables into the build pod so they're available to builds for consumption + if lagoonBuild.Spec.Project.Organization != nil { + podEnvs = append(podEnvs, corev1.EnvVar{ + Name: "LAGOON_ORGANIZATION_ID", + Value: fmt.Sprintf("%d", *lagoonBuild.Spec.Project.Organization.ID), + }) + podEnvs = append(podEnvs, corev1.EnvVar{ + Name: "LAGOON_ORGANIZATION_NAME", + Value: lagoonBuild.Spec.Project.Organization.Name, + }) + } + // add any LAGOON_FEATURE_FLAG_ variables in the controller into the build pods for fName, fValue := range r.LagoonFeatureFlags { podEnvs = append(podEnvs, corev1.EnvVar{ @@ -821,6 +838,12 @@ func (r *LagoonBuildReconciler) processBuild(ctx context.Context, opLog logr.Log }, } + // set the organization labels on build pods + if lagoonBuild.Spec.Project.Organization != nil { + newPod.ObjectMeta.Labels["organization.lagoon.sh/id"] = fmt.Sprintf("%d", *lagoonBuild.Spec.Project.Organization.ID) + newPod.ObjectMeta.Labels["organization.lagoon.sh/name"] = lagoonBuild.Spec.Project.Organization.Name + } + // set the pod security context, if defined to a non-default value if r.BuildPodRunAsUser != 0 || r.BuildPodRunAsGroup != 0 || r.BuildPodFSGroup != 0 { diff --git a/controllers/v1beta1/task_controller.go b/controllers/v1beta1/task_controller.go index dae8387..4119b47 100644 --- a/controllers/v1beta1/task_controller.go +++ b/controllers/v1beta1/task_controller.go @@ -271,6 +271,11 @@ func (r *LagoonTaskReconciler) getTaskPodDeployment(ctx context.Context, lagoonT }, Spec: dep.Spec.Template.Spec, } + // set the organization labels on task pods + if lagoonTask.Spec.Project.Organization != nil { + taskPod.ObjectMeta.Labels["organization.lagoon.sh/id"] = fmt.Sprintf("%d", *lagoonTask.Spec.Project.Organization.ID) + taskPod.ObjectMeta.Labels["organization.lagoon.sh/name"] = lagoonTask.Spec.Project.Organization.Name + } return taskPod, nil } } @@ -564,6 +569,10 @@ func (r *LagoonTaskReconciler) createAdvancedTask(ctx context.Context, lagoonTas }, }, } + if lagoonTask.Spec.Project.Organization != nil { + newPod.ObjectMeta.Labels["organization.lagoon.sh/id"] = fmt.Sprintf("%d", *lagoonTask.Spec.Project.Organization.ID) + newPod.ObjectMeta.Labels["organization.lagoon.sh/name"] = lagoonTask.Spec.Project.Organization.Name + } if lagoonTask.Spec.AdvancedTask.DeployerToken { // start this with the serviceaccount so that it gets the token mounted into it newPod.Spec.ServiceAccountName = "lagoon-deployer" diff --git a/test-resources/example-project2.yaml b/test-resources/example-project2.yaml index 36eadd7..67451af 100644 --- a/test-resources/example-project2.yaml +++ b/test-resources/example-project2.yaml @@ -1,7 +1,7 @@ kind: LagoonBuild apiVersion: crd.lagoon.sh/v1beta1 metadata: - name: lagoon-build-9m5zypx + name: lagoon-build-8m5zypx spec: build: ci: 'true' #to make sure that readwritemany is changed to readwriteonce @@ -10,12 +10,15 @@ spec: project: name: nginx-example environment: main + organization: + id: 123 + name: test-org uiLink: https://dashboard.amazeeio.cloud/projects/project/project-environment/deployments/lagoon-build-7m5zypx routerPattern: 'main-nginx-example' environmentType: production productionEnvironment: main standbyEnvironment: master - gitUrl: https://https://github.com/shreddedbacon/lagoon-nginx-example.git + gitUrl: https://github.com/shreddedbacon/lagoon-nginx-example.git deployTarget: kind projectSecret: 4d6e7dd0f013a75d62a0680139fa82d350c2a1285f43f867535bad1143f228b1 key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWFFJQkFBS0JnUUNjc1g2RG5KNXpNb0RqQ2R6a1JFOEg2TEh2TDQzaUhsekJLTWo4T1VNV05ZZG5YekdqCkR5Mkp1anQ3ZDNlMTVLeC8zOFo5UzJLdHNnVFVtWi9lUlRQSTdabE1idHRJK250UmtyblZLblBWNzhEeEFKNW8KTGZtQndmdWE2MnlVYnl0cnpYQ2pwVVJrQUlBMEZiR2VqS2Rvd3cxcnZGMzJoZFUzQ3ZIcG5rKzE2d0lEQVFBQgpBb0dCQUkrV0dyL1NDbVMzdCtIVkRPVGtMNk9vdVR6Y1QrRVFQNkVGbGIrRFhaV0JjZFhwSnB3c2NXZFBEK2poCkhnTEJUTTFWS3hkdnVEcEE4aW83cUlMTzJWYm1MeGpNWGk4TUdwY212dXJFNVJydTZTMXJzRDl2R0c5TGxoR3UKK0pUSmViMVdaZFduWFZ2am5LbExrWEV1eUthbXR2Z253Um5xNld5V05OazJ6SktoQWtFQThFenpxYnowcFVuTApLc241K2k0NUdoRGVpRTQvajRtamo1b1FHVzJxbUZWT2pHaHR1UGpaM2lwTis0RGlTRkFyMkl0b2VlK085d1pyCkRINHBkdU5YOFFKQkFLYnVOQ3dXK29sYXA4R2pUSk1TQjV1MW8wMVRHWFdFOGhVZG1leFBBdjl0cTBBT0gzUUQKUTIrM0RsaVY0ektoTlMra2xaSkVjNndzS0YyQmJIby81NXNDUVFETlBJd24vdERja3loSkJYVFJyc1RxZEZuOApCUWpZYVhBZTZEQ3o1eXg3S3ZFSmp1K1h1a01xTXV1ajBUSnpITFkySHVzK3FkSnJQVG9VMDNSS3JHV2hBa0JFCnB3aXI3Vk5pYy9jMFN2MnVLcWNZWWM1a2ViMnB1R0I3VUs1Q0lvaWdGakZzNmFJRDYyZXJwVVJ3S0V6RlFNbUgKNjQ5Y0ZXemhMVlA0aU1iZFREVHJBa0FFMTZXU1A3WXBWOHV1eFVGMGV0L3lFR3dURVpVU2R1OEppSTBHN0tqagpqcVR6RjQ3YkJZc0pIYTRYcWpVb2E3TXgwcS9FSUtRWkJ2NGFvQm42bGFOQwotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==