From 9e44bbe672c4c34c8e1a2e1546cc3dc9f0353296 Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Tue, 14 Nov 2023 11:44:22 +1100 Subject: [PATCH 1/8] chore: update makefile to allow remote-controller image overrides --- Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Makefile b/Makefile index b84ba29181..72e5b5e589 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,11 @@ UPSTREAM_TAG ?= latest # edge is the most current merged change BUILD_DEPLOY_IMAGE_TAG ?= edge +# OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG and OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY +# set this to a particular build image if required, defaults to nothing to consume what the chart provides +OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG= +OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY= + # To build k3d with Calico instead of Flannel, set this to true. Note that the Calico install in lagoon-charts is always # disabled for use with k3d, as the cluster needs it on creation. USE_CALICO_CNI ?= false @@ -496,6 +501,8 @@ k3d/test: k3d/cluster helm/repos $(addprefix local-dev/,$(K3D_TOOLS)) build HELM=$$(realpath ../local-dev/helm) KUBECTL=$$(realpath ../local-dev/kubectl) \ JQ=$$(realpath ../local-dev/jq) \ OVERRIDE_BUILD_DEPLOY_DIND_IMAGE=uselagoon/build-deploy-image:${BUILD_DEPLOY_IMAGE_TAG} \ + $$([ $(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG) ] && echo 'OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG=$(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG)') \ + $$([ $(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY) ] && echo 'OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY=$(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY)') \ OVERRIDE_ACTIVE_STANDBY_TASK_IMAGE=$$IMAGE_REGISTRY/task-activestandby:$(SAFE_BRANCH_NAME) \ IMAGE_REGISTRY=$$IMAGE_REGISTRY \ SKIP_INSTALL_REGISTRY=true \ @@ -528,6 +535,8 @@ k3d/setup: k3d/cluster helm/repos $(addprefix local-dev/,$(K3D_TOOLS)) build HELM=$$(realpath ../local-dev/helm) KUBECTL=$$(realpath ../local-dev/kubectl) \ JQ=$$(realpath ../local-dev/jq) \ OVERRIDE_BUILD_DEPLOY_DIND_IMAGE=uselagoon/build-deploy-image:${BUILD_DEPLOY_IMAGE_TAG} \ + $$([ $(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG) ] && echo 'OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG=$(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG)') \ + $$([ $(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY) ] && echo 'OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY=$(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY)') \ OVERRIDE_ACTIVE_STANDBY_TASK_IMAGE=$$IMAGE_REGISTRY/task-activestandby:$(SAFE_BRANCH_NAME) \ IMAGE_REGISTRY=$$IMAGE_REGISTRY @@ -581,6 +590,8 @@ k3d/dev: build HELM=$$(realpath ../local-dev/helm) KUBECTL=$$(realpath ../local-dev/kubectl) \ JQ=$$(realpath ../local-dev/jq) \ OVERRIDE_BUILD_DEPLOY_DIND_IMAGE=uselagoon/build-deploy-image:${BUILD_DEPLOY_IMAGE_TAG} \ + $$([ $(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG) ] && echo 'OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG=$(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGETAG)') \ + $$([ $(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY) ] && echo 'OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY=$(OVERRIDE_BUILD_DEPLOY_CONTROLLER_IMAGE_REPOSITORY)') \ OVERRIDE_ACTIVE_STANDBY_TASK_IMAGE=$$IMAGE_REGISTRY/task-activestandby:$(SAFE_BRANCH_NAME) \ IMAGE_REGISTRY=$$IMAGE_REGISTRY From 711dd009716983782506fc3b98429a385c285397 Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Tue, 14 Nov 2023 11:46:15 +1100 Subject: [PATCH 2/8] refactor: update api to support newer activestandby task payload --- node-packages/commons/src/tasks.ts | 9 +++------ services/actions-handler/handler/controller_tasks.go | 2 +- services/api/src/resources/deployment/resolvers.ts | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/node-packages/commons/src/tasks.ts b/node-packages/commons/src/tasks.ts index 3be8efba20..94697c1004 100644 --- a/node-packages/commons/src/tasks.ts +++ b/node-packages/commons/src/tasks.ts @@ -1323,18 +1323,15 @@ export const createMiscTask = async function(taskData: any) { const restoreBytes = new Buffer(JSON.stringify(restoreConf).replace(/\\n/g, "\n")).toString('base64') miscTaskData.misc.miscResource = restoreBytes break; - case 'deploytarget:route:migrate': + case 'deploytarget:task:activestandby': // handle setting up the task configuration for running the active/standby switch // this uses the `advanced task` system in the controllers - // first generate the migration CRD - const migrateConf = migrateHosts( - makeSafe(taskData.data.productionEnvironment.openshiftProjectName), - makeSafe(taskData.data.environment.openshiftProjectName)) // generate out custom json payload to send to the advanced task var jsonPayload: any = { productionEnvironment: taskData.data.productionEnvironment.name, standbyEnvironment: taskData.data.environment.name, - crd: migrateConf + sourceNamespace: makeSafe(taskData.data.environment.openshiftProjectName), + destinationNamespace: makeSafe(taskData.data.productionEnvironment.openshiftProjectName) } // encode it const jsonPayloadBytes = new Buffer(JSON.stringify(jsonPayload).replace(/\\n/g, "\n")).toString('base64') diff --git a/services/actions-handler/handler/controller_tasks.go b/services/actions-handler/handler/controller_tasks.go index 0e6dabbe3b..2fe6498bf3 100644 --- a/services/actions-handler/handler/controller_tasks.go +++ b/services/actions-handler/handler/controller_tasks.go @@ -34,7 +34,7 @@ func (m *Messenger) handleTask(ctx context.Context, messageQueue *mq.MessageQueu l := lclient.New(m.LagoonAPI.Endpoint, "actions-handler", &token, false) switch message.Meta.Key { - case "kubernetes:route:migrate", "deploytarget:route:migrate": + case "kubernetes:route:migrate", "deploytarget:route:migrate", "deploytarget:task:activestandby": // if the result is from an active/standby task, handle updating the project here switch message.Meta.JobStatus { case "complete", "succeeded": diff --git a/services/api/src/resources/deployment/resolvers.ts b/services/api/src/resources/deployment/resolvers.ts index dec0c2104e..8b119365c4 100644 --- a/services/api/src/resources/deployment/resolvers.ts +++ b/services/api/src/resources/deployment/resolvers.ts @@ -1247,7 +1247,7 @@ export const switchActiveStandby: ResolverFn = async ( data.task.id = sourceTaskData.addTask.id.toString(); // queue the task to trigger the migration - await createMiscTask({ key: 'route:migrate', data }); + await createMiscTask({ key: 'task:activestandby', data }); // return the task id and remote id var retData = { From 5d041c8547b7b0662beffcf43fd7eb801f1b2d9e Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Tue, 14 Nov 2023 11:46:55 +1100 Subject: [PATCH 3/8] refactor: incorporate dioscuri into the task itself --- .../activestandby/dioscuri/certificates.go | 43 +++ taskimages/activestandby/dioscuri/dioscuri.go | 363 ++++++++++++++++++ taskimages/activestandby/dioscuri/ingress.go | 227 +++++++++++ taskimages/activestandby/dioscuri/secrets.go | 103 +++++ taskimages/activestandby/go.mod | 37 +- taskimages/activestandby/go.sum | 107 +++--- taskimages/activestandby/main.go | 180 +-------- 7 files changed, 820 insertions(+), 240 deletions(-) create mode 100644 taskimages/activestandby/dioscuri/certificates.go create mode 100644 taskimages/activestandby/dioscuri/dioscuri.go create mode 100644 taskimages/activestandby/dioscuri/ingress.go create mode 100644 taskimages/activestandby/dioscuri/secrets.go diff --git a/taskimages/activestandby/dioscuri/certificates.go b/taskimages/activestandby/dioscuri/certificates.go new file mode 100644 index 0000000000..c135941941 --- /dev/null +++ b/taskimages/activestandby/dioscuri/certificates.go @@ -0,0 +1,43 @@ +package dioscuri + +import ( + "context" + "fmt" + + certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + networkv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/types" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// copy any certificate into a slice of certificates +func copyCertificates(ctx context.Context, c client.Client, ingress *networkv1.Ingress) []*certv1.Certificate { + var certificates []*certv1.Certificate + for _, tls := range ingress.Spec.TLS { + certificate := &certv1.Certificate{} + err := c.Get(ctx, types.NamespacedName{Namespace: ingress.ObjectMeta.Namespace, Name: tls.SecretName}, certificate) + if err != nil { + break + } + certificates = append(certificates, certificate) + fmt.Println(fmt.Sprintf(">> Copying certificate %s in namespace %s", certificate.ObjectMeta.Name, certificate.ObjectMeta.Namespace)) + } + return certificates +} + +// create any certificates in the destination namespace +func createCertificates(ctx context.Context, c client.Client, destinationNamespace string, certificates []*certv1.Certificate) error { + for _, certificate := range certificates { + certificate.ObjectMeta.Namespace = destinationNamespace + certificate.ResourceVersion = "" + certificate.SelfLink = "" + certificate.UID = "" + err := c.Create(ctx, certificate) + if err != nil { + break + } + // secrets = append(secrets, certificate) + fmt.Println(fmt.Sprintf(">> Creating certificate %s in namespace %s", certificate.ObjectMeta.Name, certificate.ObjectMeta.Namespace)) + } + return nil +} diff --git a/taskimages/activestandby/dioscuri/dioscuri.go b/taskimages/activestandby/dioscuri/dioscuri.go new file mode 100644 index 0000000000..52c36b8caa --- /dev/null +++ b/taskimages/activestandby/dioscuri/dioscuri.go @@ -0,0 +1,363 @@ +package dioscuri + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + networkv1 "k8s.io/api/networking/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MigratedIngress . +type MigratedIngress struct { + NewIngress *networkv1.Ingress + OldIngressNamespace string +} + +// this is the return data structure for the active standby task. +// this will be sent back to lagoon as a payload to be processed. +type ReturnData struct { + Status string `json:"status"` + ProductionEnvironment string `json:"productionEnvironment"` + StandbyProdutionEnvironment string `json:"standbyProductionEnvironment"` + ProductionRoutes string `json:"productionRoutes"` + StandbyRoutes string `json:"standbyRoutes"` + SourceNamespace string `json:"sourceNamespace"` + DestinationNamespace string `json:"DestinationNamespace"` +} + +func RunMigration(c client.Client, rData ReturnData, podName, podNamespace string) error { + ctx := context.Background() + var activeMigratedIngress []string + var standbyMigratedIngress []string + fmt.Println(fmt.Sprintf("> Running migration checks for ingress in %s moving to %s", rData.SourceNamespace, rData.DestinationNamespace)) + + // check destination namespace exists + namespace := corev1.Namespace{} + if err := c.Get(ctx, + types.NamespacedName{ + Name: rData.DestinationNamespace, + }, + &namespace, + ); err != nil { + return fmt.Errorf(`======================================== +Task failed to check destination namespace, error was: %v +======================================== +This means the task has not performed any migrations yet and failed during pre-flight checks. +The task may be performed again, but successive failures of this type should be reported to your Lagoon administrator. +Provide a copy of this entire log to the team.`, err) + } + + // START CHECKING SERVICES SECTION + // migrateLabels := map[string]string{"activestandby.lagoon.sh/migrate": "true"} + // get the ingress from the source namespace, these will get moved to the destination namespace + ingressSourceToDestination := &networkv1.IngressList{} + ingressSourceToDestination, err := getIngressWithLabel(ctx, + c, + rData.SourceNamespace, + ) + if err != nil { + return fmt.Errorf(`======================================== +Task failed to check ingress in source namespace, error was: %v +======================================== +This means the task has not performed any migrations yet and failed during pre-flight checks. +The task may be performed again, but successive failures of this type should be reported to your Lagoon administrator. +Provide a copy of this entire log to the team.`, err) + } + // get the ingress from the destination namespace, these will get moved to the source namespace + ingressDestinationToSource, err := getIngressWithLabel(ctx, + c, + rData.DestinationNamespace, + ) + if err != nil { + return fmt.Errorf(`======================================== +Task failed to check ingress in destination namespace, error was: %v +======================================== +This means the task has not performed any migrations yet and failed during pre-flight checks. +The task may be performed again, but successive failures of this type should be reported to your Lagoon administrator. +Provide a copy of this entire log to the team.`, err) + } + // check that the services for the ingress we are moving exist in each namespace + migrateDestinationToSource := &networkv1.IngressList{} + if err := checkKubernetesServices(ctx, + c, + ingressDestinationToSource, + migrateDestinationToSource, + rData.SourceNamespace, + ); err != nil { + return fmt.Errorf(`======================================== +Task failed to check services in source namespace, error was: %v +======================================== +This means the task has not performed any migrations yet and failed during pre-flight checks. +The task may be performed again, but successive failures of this type should be reported to your Lagoon administrator. +Provide a copy of this entire log to the team.`, err) + } + migrateSourceToDestination := &networkv1.IngressList{} + if err := checkKubernetesServices(ctx, + c, + ingressSourceToDestination, + migrateSourceToDestination, + rData.DestinationNamespace, + ); err != nil { + return fmt.Errorf(`======================================== +Task failed to check services in destination namespace, error was: %v +======================================== +This means the task has not performed any migrations yet and failed during pre-flight checks. +The task may be performed again, but successive failures of this type should be reported to your Lagoon administrator. +Provide a copy of this entire log to the team.`, err) + } + if err := checkSecrets(ctx, + c, + ingressDestinationToSource, + rData.SourceNamespace, + ); err != nil { + return fmt.Errorf(`======================================== +Task failed to check ingress secrets in source namespace, error was: %v +======================================== +This means the task has not performed any migrations yet, and failed during pre-flight checks +The task may be performed again, but successive failures of this type should be reported to your Lagoon administrator. +Provide a copy of this entire log to the team.`, err) + } + // check that the secrets for the ingress we are moving don't already exist in each namespace + if err := checkSecrets(ctx, + c, + ingressSourceToDestination, + rData.DestinationNamespace, + ); err != nil { + return fmt.Errorf(`======================================== +Task failed to check ingress secrets in destination namespace, error was: %v +======================================== +This means the task has not performed any migrations yet, and failed during pre-flight checks +The task may be performed again, but successive failures of this type should be reported to your Lagoon administrator. +Provide a copy of this entire log to the team.`, err) + } + // END CHECKING SERVICES SECTION + + // START MIGRATING ROUTES SECTION + fmt.Println(fmt.Sprintf("> Running ingress migrations in %s moving to %s", rData.SourceNamespace, rData.DestinationNamespace)) + // actually start the migrations here + var migratedIngress []MigratedIngress + for _, ingress := range migrateSourceToDestination.Items { + // before we move anything we need to modify some annotations/labels + // with the provided values + if err := patchIngress(ctx, + c, + &ingress, + map[string]interface{}{"activestandby.lagoon.sh/migrating": "true"}, + ); err != nil { + return fmt.Errorf(`======================================== +Task failed to patch ingress %s, error was: %v +======================================== +The active standby switch failed to patch an ingress before the migration took place. +Depending on where this fails, the environment may have already migrated one or more ingress. + +DO NOT PERFORM THIS TASK AGAIN + +Please contact your Lagoon administrator to make sure your project gets updated correctly. +Provide a copy of this entire log to the team.`, ingress.ObjectMeta.Name, err) + } + + // migrate these ingress + newIngress, err := individualIngressMigration(ctx, + c, + &ingress, + rData.SourceNamespace, + rData.DestinationNamespace, + ) + if err != nil { + return fmt.Errorf(`======================================== +Task failed to migrate ingress %s, error was: %v +======================================== +The active standby switch failed to migrate and ingress in the source namespace to the destination namespace + +DO NOT PERFORM THIS TASK AGAIN + +Please contact your Lagoon administrator to make sure your project gets updated correctly. +Provide a copy of this entire log to the team.`, ingress.ObjectMeta.Name, err) + } + migratedIngress = append(migratedIngress, + MigratedIngress{ + NewIngress: newIngress, + OldIngressNamespace: rData.SourceNamespace, + }, + ) + ingressScheme := "http://" + if ingress.Spec.TLS != nil { + ingressScheme = "https://" + } + for _, rule := range ingress.Spec.Rules { + standbyMigratedIngress = append(standbyMigratedIngress, fmt.Sprintf("%s%s", ingressScheme, rule.Host)) + } + } + for _, ingress := range migrateDestinationToSource.Items { + // before we move anything we may need to modify some annotations + // patch all the annotations we are given in the `pre-migrate-resource-annotations` + // with the provided values + if err := patchIngress(ctx, + c, + &ingress, + map[string]interface{}{"activestandby.lagoon.sh/migrating": "true"}, + ); err != nil { + return fmt.Errorf(`======================================== +Task failed to patch ingress %s, error was: %v +======================================== +The active standby switch failed to patch an ingress before the migration took place. +Depending on where this fails, the environment may have already migrated one or more ingress. + +DO NOT PERFORM THIS TASK AGAIN + +Please contact your Lagoon administrator to make sure your project gets updated correctly. +Provide a copy of this entire log to the team.`, ingress.ObjectMeta.Name, err) + } + // migrate these ingress + newIngress, err := individualIngressMigration(ctx, + c, + &ingress, + rData.DestinationNamespace, + rData.SourceNamespace, + ) + if err != nil { + return fmt.Errorf(`======================================== +Task failed to migrate ingress %s, error was: %v +======================================== +The active standby switch failed to migrate and ingress in the destination namespace to the source namespace + +DO NOT PERFORM THIS TASK AGAIN + +Please contact your Lagoon administrator to make sure your project gets updated correctly. +Provide a copy of this entire log to the team.`, ingress.ObjectMeta.Name, err) + } + // add the migrated ingress so we go through and fix them up later + migratedIngress = append(migratedIngress, + MigratedIngress{NewIngress: newIngress, + OldIngressNamespace: rData.DestinationNamespace, + }, + ) + ingressScheme := "http://" + if ingress.Spec.TLS != nil { + ingressScheme = "https://" + } + for _, rule := range ingress.Spec.Rules { + activeMigratedIngress = append(activeMigratedIngress, fmt.Sprintf("%s%s", ingressScheme, rule.Host)) + } + } + // wait a sec before updating the ingress + checkInterval := time.Duration(1) + time.Sleep(checkInterval * time.Second) + + // once we move all the ingress, we have to go through and do a final update on them to make sure any `HostAlreadyClaimed` warning/errors go away + for _, migratedIngress := range migratedIngress { + // // we may need to move some resources after we move the ingress, we can define their annotations here + err := updateIngress(ctx, + c, + migratedIngress.NewIngress, + migratedIngress.OldIngressNamespace, + map[string]interface{}{ + "activestandby.lagoon.sh/migrating": "false", + "activestandby.lagoon.sh/migrated-from": migratedIngress.OldIngressNamespace, + }, + ) + if err != nil { + return fmt.Errorf(`======================================== +Task failed to update ingress, error was: %v +======================================== +The active standby switch failed to update one or more ingress after the migration took place, +this means that the task likely completed successfully, but the updates may not appear in Lagoon. + +DO NOT PERFORM THIS TASK AGAIN + +Please contact your Lagoon administrator to make sure your project gets updated correctly. +Provide a copy of this entire log to the team.`, err) + } + } + + rData.Status = "Completed" + rData.ProductionRoutes = strings.Join(activeMigratedIngress, ",") + rData.StandbyRoutes = strings.Join(standbyMigratedIngress, ",") + + // print the result of the task, it will go back to lagoon-logs to be displayed + // to the user + jsonData, _ := json.Marshal(rData) + fmt.Println("========================================") + fmt.Println("Status:", rData.Status) + fmt.Println("Active Environment:", rData.StandbyProdutionEnvironment) + fmt.Println("Active Routes:", rData.ProductionRoutes) + fmt.Println("Standby Environment:", rData.ProductionEnvironment) + fmt.Println("Standby Routes:", rData.StandbyRoutes) + fmt.Println("========================================") + pod := corev1.Pod{} + if err := c.Get(context.Background(), types.NamespacedName{ + Namespace: podNamespace, + Name: podName, + }, &pod); err != nil { + return fmt.Errorf(`======================================== +Task failed to get the pod to update, error was: %v +======================================== +The active standby switch completed, but Lagoon has not been updated to reflect the changes. + +DO NOT PERFORM THIS TASK AGAIN UNTIL LAGOON REFLECTS THE CHANGES REQUIRED + +Please contact your Lagoon administrator to make sure your project gets updated correctly. +Provide a copy of this entire log to the team.`, err) + } + // the job data to send back to lagoon must be base64 encoded + mergePatch, _ := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "lagoon.sh/taskData": base64.StdEncoding.EncodeToString(jsonData), + }, + }, + }) + // update the pod with the annotation + if err := c.Patch(context.Background(), &pod, client.RawPatch(types.MergePatchType, mergePatch)); err != nil { + return fmt.Errorf(`======================================== +Task failed to update pod with return information, error was: %v +======================================== +The active standby switch completed, but Lagoon has not been updated to reflect the changes. + +DO NOT PERFORM THIS TASK AGAIN UNTIL LAGOON REFLECTS THE CHANGES REQUIRED + +Please contact your Lagoon administrator to make sure your project gets updated correctly. +Provide a copy of this entire log to the team.`, err) + } + return nil +} + +func checkKubernetesServices(ctx context.Context, + c client.Client, + ingressList *networkv1.IngressList, + ingressToMigrate *networkv1.IngressList, + destinationNamespace string, +) error { + // check service for ingress exists in destination namespace + for _, ingress := range ingressList.Items { + for _, host := range ingress.Spec.Rules { + for _, path := range host.HTTP.Paths { + service := &corev1.Service{} + err := c.Get(ctx, + types.NamespacedName{ + Namespace: destinationNamespace, + Name: path.Backend.Service.Name, + }, + service, + ) + if err != nil { + if apierrors.IsNotFound(err) { + return fmt.Errorf("Service %s for ingress %s doesn't exist in namespace %s, skipping ingress", + path.Backend.Service.Name, host.Host, destinationNamespace) + } + return fmt.Errorf("Error getting service, error was: %v", err) + } + ingressToMigrate.Items = append(ingressToMigrate.Items, ingress) + } + } + } + return nil +} diff --git a/taskimages/activestandby/dioscuri/ingress.go b/taskimages/activestandby/dioscuri/ingress.go new file mode 100644 index 0000000000..8a36e70516 --- /dev/null +++ b/taskimages/activestandby/dioscuri/ingress.go @@ -0,0 +1,227 @@ +package dioscuri + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/matryer/try" + networkv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/types" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +func getIngressWithLabel(ctx context.Context, c client.Client, + namespace string, +) (*networkv1.IngressList, error) { + // collect any ingress with legacy dioscuri labels to update them + dioscuriIngress := networkv1.IngressList{} + listOption := (&client.ListOptions{}).ApplyOptions([]client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels(map[string]string{"dioscuri.amazee.io/migrate": "true"}), + }) + if err := c.List(context.TODO(), &dioscuriIngress, listOption); err != nil { + return nil, fmt.Errorf("Unable to get any ingress: %v", err) + } + for _, i := range dioscuriIngress.Items { + // remove the old label if it exists + if err := patchIngress(ctx, + c, + &i, + map[string]interface{}{"dioscuri.amazee.io/migrate": nil, "activestandby.lagoon.sh/migrate": "true"}, + ); err != nil { + return nil, fmt.Errorf("Unable to patch ingress with updated label: %v", err) + } + } + // collect ingress ingress with new lagoon.sh labels + ingress := networkv1.IngressList{} + listOption = (&client.ListOptions{}).ApplyOptions([]client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels(map[string]string{"activestandby.lagoon.sh/migrate": "true"}), + }) + if err := c.List(context.TODO(), &ingress, listOption); err != nil { + return nil, fmt.Errorf("Unable to get any ingress: %v", err) + } + return &ingress, nil +} + +func individualIngressMigration(ctx context.Context, + c client.Client, + ingress *networkv1.Ingress, + sourceNamespace string, + destinationNamespace string, +) (*networkv1.Ingress, error) { + oldIngress := &networkv1.Ingress{} + newIngress := &networkv1.Ingress{} + err := c.Get(context.TODO(), types.NamespacedName{Namespace: sourceNamespace, Name: ingress.ObjectMeta.Name}, oldIngress) + if err != nil { + return newIngress, fmt.Errorf("Ingress %s in namespace %s doesn't exist: %v", ingress.ObjectMeta.Name, sourceNamespace, err) + } + ingressSecrets := copySecrets(ctx, c, oldIngress) + if err := createSecrets(ctx, c, destinationNamespace, ingressSecrets); err != nil { + return newIngress, fmt.Errorf("Unable to create secrets in destination namespace, error was: %v", err) + } + ingressCerts := copyCertificates(ctx, c, oldIngress) + if err := createCertificates(ctx, c, destinationNamespace, ingressCerts); err != nil { + return newIngress, fmt.Errorf("Unable to create secrets in destination namespace, error was: %v", err) + } + // if we ever need to do anything for any ingress with `tls-acme: true` enabled on them, for now, info only + // if oldIngress.Annotations["kubernetes.io/tls-acme"] == "true" { + // fmt.Println(fmt.Sprintf("Lets Encrypt is enabled for %s", oldIngress.Spec.Host)) + // } + // actually migrate here + // we need to create a new ingress now, but we need to swap the namespace to the destination. + // deepcopyinto from old to the new ingress + oldIngress.DeepCopyInto(newIngress) + // set the newingress namespace as the destination namespace + newIngress.ObjectMeta.Namespace = destinationNamespace + newIngress.ObjectMeta.ResourceVersion = "" + // fmt.Println(fmt.Sprintf("Attempting to migrate ingress %s - %s", newIngress.ObjectMeta.Name, newIngress.Spec.Host)) + if err := migrateIngress(ctx, c, newIngress, oldIngress); err != nil { + return newIngress, fmt.Errorf("Error migrating ingress %s in namespace %s: %v", ingress.ObjectMeta.Name, sourceNamespace, err) + } + fmt.Println(fmt.Sprintf("> Done migrating ingress %s", ingress.ObjectMeta.Name)) + return newIngress, nil +} + +// add ingress, and then remove the old one only if we successfully create the new one +func migrateIngress(ctx context.Context, + c client.Client, + newIngress *networkv1.Ingress, + oldIngress *networkv1.Ingress, +) error { + // delete old ingress from the old namespace + fmt.Println(fmt.Sprintf("> Removing old ingress %s in namespace %s", oldIngress.ObjectMeta.Name, oldIngress.ObjectMeta.Namespace)) + if err := removeIngress(ctx, c, oldIngress); err != nil { + return err + } + // add ingress + if err := addIngressIfNotExist(ctx, c, newIngress); err != nil { + return fmt.Errorf("Unable to create ingress %s in %s: %v", newIngress.ObjectMeta.Name, newIngress.ObjectMeta.Namespace, err) + } + return nil +} + +// add any ingress if they don't already exist in the new namespace +func addIngressIfNotExist(ctx context.Context, c client.Client, ingress *networkv1.Ingress) error { + // add ingress + // fmt.Println(fmt.Sprintf(">> Getting existing ingress %s in namespace %s", ingress.ObjectMeta.Name, ingress.ObjectMeta.Namespace)) + err := c.Get(ctx, types.NamespacedName{Namespace: ingress.ObjectMeta.Namespace, Name: ingress.ObjectMeta.Name}, ingress) + if err != nil { + // there is no ingress in the destination namespace, then we create it + fmt.Println(fmt.Sprintf(">> Creating ingress %s in namespace %s", ingress.ObjectMeta.Name, ingress.ObjectMeta.Namespace)) + if err := c.Create(ctx, ingress); err != nil { + return fmt.Errorf("Unable to create ingress %s in %s: %v", ingress.ObjectMeta.Name, ingress.ObjectMeta.Namespace, err) + } + } + return nil +} + +func updateIngress(ctx context.Context, c client.Client, newIngress *networkv1.Ingress, oldIngressNamespace string, labels map[string]interface{}) error { + // check a few times to make sure the old ingress no longer exists + for i := 0; i < 10; i++ { + oldIngressExists := checkOldIngressExists(c, newIngress, oldIngressNamespace) + if !oldIngressExists { + // the new ingress with label to ensure ingresscontroller picks it up after we do the deletion + mergePatch, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": labels, + }, + }) + if err != nil { + return fmt.Errorf("Unable to create mergepatch for %s, error was: %v", newIngress.ObjectMeta.Name, err) + } + certificates, secrets := deleteOldSecrets(ctx, c, oldIngressNamespace, newIngress) + if len(certificates) > 0 { + // there was an issue with some of the secrets remaining in the source namespace + fmt.Println(">> The following certificates remained in the namespace:") + for c, b := range certificates { + if !b { + fmt.Println(fmt.Sprintf(">>> %s", c)) + } + } + } + if len(secrets) > 0 { + fmt.Println(">> The following secrets remained in the namespace:") + for c, b := range secrets { + if !b { + fmt.Println(fmt.Sprintf(">>> %s", c)) + } + } + } + fmt.Println(fmt.Sprintf(">> Patching ingress %s in namespace %s", newIngress.ObjectMeta.Name, newIngress.ObjectMeta.Namespace)) + if err := c.Patch(ctx, newIngress, client.RawPatch(types.MergePatchType, mergePatch)); err != nil { + return fmt.Errorf("Unable to patch ingress %s, error was: %v", newIngress.ObjectMeta.Name, err) + } + return nil + } + // wait 5 secs before re-trying + checkInterval := time.Duration(5) + time.Sleep(checkInterval * time.Second) + } + return fmt.Errorf("There was an error checking if the old ingress still exists before trying to patch the new ingress, there may be an issue with the ingress") +} + +func checkOldIngressExists(c client.Client, ingress *networkv1.Ingress, sourceNamespace string) bool { + // fmt.Println(fmt.Sprintf(">> Checking ingress %s is not in source namespace %s", ingress.ObjectMeta.Name, sourceNamespace)) + getIngress := &networkv1.Ingress{} + err := c.Get(context.TODO(), types.NamespacedName{Namespace: sourceNamespace, Name: ingress.ObjectMeta.Name}, getIngress) + if err != nil { + // there is no ingress in the source namespace + fmt.Println(fmt.Sprintf(">> Ingress %s is not in source namespace %s", ingress.ObjectMeta.Name, sourceNamespace)) + return false + } + // fmt.Println(fmt.Sprintf(">> Ingress %s is in source namespace %s", ingress.ObjectMeta.Name, sourceNamespace)) + return true +} + +// remove a given ingress +func removeIngress(ctx context.Context, c client.Client, ingress *networkv1.Ingress) error { + // remove ingress + if err := c.Delete(ctx, ingress); err != nil { + return fmt.Errorf("Unable to delete ingress %s in %s: %v", ingress.ObjectMeta.Name, ingress.ObjectMeta.Namespace, err) + } + // check that the ingress is actually deleted before continuing + // fmt.Println(fmt.Sprintf(">> Check ingress %s in %s deleted", ingress.ObjectMeta.Name, ingress.ObjectMeta.Namespace)) + try.MaxRetries = 60 + err := try.Do(func(attempt int) (bool, error) { + var ingressErr error + err := c.Get(ctx, types.NamespacedName{ + Namespace: ingress.ObjectMeta.Namespace, + Name: ingress.ObjectMeta.Name, + }, ingress) + if err != nil { + // the ingress doesn't exist anymore, so exit the retry + ingressErr = nil + fmt.Println(fmt.Sprintf(">> Ingress %s in %s deleted", ingress.ObjectMeta.Name, ingress.ObjectMeta.Namespace)) + } else { + // if the ingress still exists wait 5 seconds before trying again + msg := fmt.Sprintf(">> Ingress %s in %s still exists", ingress.ObjectMeta.Name, ingress.ObjectMeta.Namespace) + ingressErr = fmt.Errorf("%s: %v", msg, err) + fmt.Println(msg) + } + time.Sleep(1 * time.Second) + return attempt < 60, ingressErr + }) + if err != nil { + // if the ingress still exists, return the error + return err + } + return nil +} + +func patchIngress(ctx context.Context, c client.Client, ingress *networkv1.Ingress, labels map[string]interface{}) error { + mergePatch, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": labels, + }, + }) + if err != nil { + return fmt.Errorf("Unable to create mergepatch for %s, error was: %v", ingress.ObjectMeta.Name, err) + } + if err := c.Patch(ctx, ingress, client.RawPatch(types.MergePatchType, mergePatch)); err != nil { + return fmt.Errorf("Unable to patch ingress %s, error was: %v", ingress.ObjectMeta.Name, err) + } + return nil +} diff --git a/taskimages/activestandby/dioscuri/secrets.go b/taskimages/activestandby/dioscuri/secrets.go new file mode 100644 index 0000000000..fb54ef88eb --- /dev/null +++ b/taskimages/activestandby/dioscuri/secrets.go @@ -0,0 +1,103 @@ +package dioscuri + +import ( + "context" + "fmt" + + certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + corev1 "k8s.io/api/core/v1" + networkv1 "k8s.io/api/networking/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +func checkSecrets(ctx context.Context, + c client.Client, + ingressList *networkv1.IngressList, + destinationNamespace string, +) error { + // check service for ingress exists in destination namespace + for _, ingress := range ingressList.Items { + for _, hosts := range ingress.Spec.TLS { + secret := &corev1.Secret{} + err := c.Get(ctx, + types.NamespacedName{ + Namespace: destinationNamespace, + Name: hosts.SecretName, + }, + secret, + ) + if err != nil { + if apierrors.IsNotFound(err) { + // fmt.Println(fmt.Sprintf(">> Secret %s for ingress %s doesn't exist in namespace %s", hosts.SecretName, hosts, destinationNamespace)) + return nil + } + return fmt.Errorf("Error getting secret, error was: %v", err) + } + // return fmt.Errorf("Secret %s for ingress %s exists in namespace %s, skipping ingress", hosts.SecretName, hosts, destinationNamespace) + } + } + return nil +} + +// copy any secret into a slice of secrets +func copySecrets(ctx context.Context, c client.Client, ingress *networkv1.Ingress) []*corev1.Secret { + var secrets []*corev1.Secret + for _, tls := range ingress.Spec.TLS { + secret := &corev1.Secret{} + err := c.Get(ctx, types.NamespacedName{Namespace: ingress.ObjectMeta.Namespace, Name: tls.SecretName}, secret) + if err != nil { + break + } + secrets = append(secrets, secret) + fmt.Println(fmt.Sprintf(">> Copying secret %s in namespace %s", secret.ObjectMeta.Name, secret.ObjectMeta.Namespace)) + } + return secrets +} + +// create secret in destination namespace +func createSecrets(ctx context.Context, c client.Client, destinationNamespace string, secrets []*corev1.Secret) error { + for _, secret := range secrets { + secret.ObjectMeta.Namespace = destinationNamespace + secret.ResourceVersion = "" + secret.SelfLink = "" + secret.UID = "" + err := c.Create(ctx, secret) + if err != nil { + break + } + secrets = append(secrets, secret) + fmt.Println(fmt.Sprintf(">> Creating secret %s in namespace %s", secret.ObjectMeta.Name, secret.ObjectMeta.Namespace)) + } + return nil +} + +func deleteOldSecrets(ctx context.Context, c client.Client, namespace string, ingress *networkv1.Ingress) (map[string]bool, map[string]bool) { + certificates := make(map[string]bool) + secrets := make(map[string]bool) + for _, tls := range ingress.Spec.TLS { + certificate := &certv1.Certificate{} + err := c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: tls.SecretName}, certificate) + if err == nil { + certificates[tls.SecretName] = false + if err = c.Delete(ctx, certificate); err != nil { + fmt.Println(fmt.Sprintf(">> Unable to delete certificate %s in namespace %s; error was: %v", certificate.ObjectMeta.Name, certificate.ObjectMeta.Namespace, err)) + continue + } + certificates[tls.SecretName] = true + } // else the secret didn't exist, so nothing to try and delete + secret := &corev1.Secret{} + err = c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: tls.SecretName}, secret) + if err == nil { + secrets[tls.SecretName] = false + if err = c.Delete(ctx, secret); err != nil { + fmt.Println(fmt.Sprintf(">> Unable to patch secret %s in namespace %s; error was: %v", secret.ObjectMeta.Name, secret.ObjectMeta.Namespace, err)) + continue + } + secrets[tls.SecretName] = true + } // else the secret didn't exist, so nothing to try and delete + // fmt.Println(fmt.Sprintf(">> Added delete annotation to secret %s in namespace %s", secret.ObjectMeta.Name, secret.ObjectMeta.Namespace)) + } + return certificates, secrets +} diff --git a/taskimages/activestandby/go.mod b/taskimages/activestandby/go.mod index 68500fae68..b4ac0232c8 100644 --- a/taskimages/activestandby/go.mod +++ b/taskimages/activestandby/go.mod @@ -1,53 +1,54 @@ -module github.com/uselagoon/lagoon/images/tasks/activestandby +module github.com/uselagoon/lagoon/taskimages/activestandby go 1.21 require ( - gopkg.in/matryer/try.v1 v1.0.0-20150601225556-312d2599e12e - k8s.io/api v0.28.2 - k8s.io/apimachinery v0.28.2 - k8s.io/client-go v0.28.2 - sigs.k8s.io/controller-runtime v0.16.2 + github.com/cert-manager/cert-manager v1.13.2 + github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 + k8s.io/api v0.28.3 + k8s.io/apimachinery v0.28.3 + k8s.io/client-go v0.28.3 + sigs.k8s.io/controller-runtime v0.16.3 ) require ( github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/go-logr/logr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.8 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.28.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d // indirect + k8s.io/kube-openapi v0.0.0-20230905202853-d090da108d2f // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/gateway-api v0.8.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/taskimages/activestandby/go.sum b/taskimages/activestandby/go.sum index 93a37285a3..a20934110e 100644 --- a/taskimages/activestandby/go.sum +++ b/taskimages/activestandby/go.sum @@ -1,17 +1,20 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cert-manager/cert-manager v1.13.2 h1:LG8+OLvxtc49CSyfjW7zHSyvlt7JVaHgRGyhfdvPpkk= +github.com/cert-manager/cert-manager v1.13.2/go.mod h1:AdfSU8muS+bj3C46YaD1VrlpXh672z5MeW/k1k5Sl1w= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= -github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -19,24 +22,22 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= -github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -47,8 +48,9 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -75,14 +77,16 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= @@ -91,8 +95,8 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -106,7 +110,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= @@ -114,43 +117,32 @@ go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -159,17 +151,16 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= @@ -179,30 +170,30 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/matryer/try.v1 v1.0.0-20150601225556-312d2599e12e h1:bJHzu9Qwc9wQRWJ/WVkJGAfs+riucl/tKAFNxf9pzqk= -gopkg.in/matryer/try.v1 v1.0.0-20150601225556-312d2599e12e/go.mod h1:tve0rTLdGlwnXF7iBO9rbAEyeXvuuPx0n4DvXS/Nw7o= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= -k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= -k8s.io/apiextensions-apiserver v0.28.0 h1:CszgmBL8CizEnj4sj7/PtLGey6Na3YgWyGCPONv7E9E= -k8s.io/apiextensions-apiserver v0.28.0/go.mod h1:uRdYiwIuu0SyqJKriKmqEN2jThIJPhVmOWETm8ud1VE= -k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= -k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= -k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= -k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= +k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= +k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= +k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= +k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= +k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= +k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= +k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d h1:/CFeJBjBrZvHX09rObS2+2iEEDevMWYc1v3aIYAjIYI= -k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kube-openapi v0.0.0-20230905202853-d090da108d2f h1:eeEUOoGYWhOz7EyXqhlR2zHKNw2mNJ9vzJmub6YN6kk= +k8s.io/kube-openapi v0.0.0-20230905202853-d090da108d2f/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.16.2 h1:mwXAVuEk3EQf478PQwQ48zGOXvW27UJc8NHktQVuIPU= -sigs.k8s.io/controller-runtime v0.16.2/go.mod h1:vpMu3LpI5sYWtujJOa2uPK61nB5rbwlN7BAB8aSLvGU= +sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= +sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/gateway-api v0.8.0 h1:isQQ3Jx2qFP7vaA3ls0846F0Amp9Eq14P08xbSwVbQg= +sigs.k8s.io/gateway-api v0.8.0/go.mod h1:okOnjPNBFbIS/Rw9kAhuIUaIkLhTKEu+ARIuXk2dgaM= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= diff --git a/taskimages/activestandby/main.go b/taskimages/activestandby/main.go index d85b8b858b..d1185d6746 100644 --- a/taskimages/activestandby/main.go +++ b/taskimages/activestandby/main.go @@ -1,32 +1,16 @@ package main import ( - "context" "encoding/base64" "encoding/json" "fmt" - "io/ioutil" "os" - "time" - "gopkg.in/matryer/try.v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" + "github.com/uselagoon/lagoon/taskimages/activestandby/dioscuri" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" ) -// this is the return data structure for the active standby task. -// this will be sent back to lagoon as a payload to be processed. -type returnData struct { - Status string `json:"status"` - ProductionEnvironment string `json:"productionEnvironment"` - StandbyProdutionEnvironment string `json:"standbyProductionEnvironment"` - ProductionRoutes string `json:"productionRoutes"` - StandbyRoutes string `json:"standbyRoutes"` -} - func main() { // get the values from the provided environment variables. JSONPayload := os.Getenv("JSON_PAYLOAD") @@ -46,13 +30,13 @@ func main() { os.Exit(1) } - // read the deployer token. - token, err := ioutil.ReadFile("/var/run/secrets/lagoon/deployer/token") + // read the serviceaccount deployer token first. + token, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") if err != nil { - // read the deployer token. - token, err = ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") + // read the legacy deployer token if for some reason the serviceaccount is not found. + token, err = os.ReadFile("/var/run/secrets/lagoon/deployer/token") if err != nil { - fmt.Println(fmt.Sprintf("Task failed to read the token, error was: %v", err)) + fmt.Println(fmt.Sprintf("Task failed to find a kubernetes token to use, error was: %v", err)) os.Exit(1) } } @@ -67,7 +51,7 @@ func main() { // create the client using the rest config. c, err := client.New(config, client.Options{}) if err != nil { - fmt.Println(fmt.Sprintf("Task failed creating the client, error was: %v", err)) + fmt.Println(fmt.Sprintf("Task failed creating the kubernetes client, error was: %v", err)) os.Exit(1) } @@ -79,153 +63,21 @@ func main() { } var payloadData map[string]interface{} if err := json.Unmarshal(payloadBytes, &payloadData); err != nil { - fmt.Println(fmt.Sprintf("Task failed to unsmarshal the given payload data, error was: %v", err)) + fmt.Println(fmt.Sprintf("Task failed to unsmarshal the supplied payload data, error was: %v", err)) os.Exit(1) } - // get the provided CRD from the payload data and unmarshal it to unstructured so we can create it in kubernetes. - crdBytes, err := json.Marshal(payloadData["crd"]) - if err != nil { - fmt.Println(fmt.Sprintf("Task failed to marshal the given payload data, error was: %v", err)) - os.Exit(1) + // seed the return data with the initial data + rData := dioscuri.ReturnData{ + SourceNamespace: payloadData["sourceNamespace"].(string), + DestinationNamespace: payloadData["destinationNamespace"].(string), + ProductionEnvironment: payloadData["productionEnvironment"].(string), + StandbyProdutionEnvironment: payloadData["standbyEnvironment"].(string), } - crd := unstructured.Unstructured{} - if err := crd.UnmarshalJSON([]byte(crdBytes)); err != nil { - fmt.Println(fmt.Sprintf("Task failed to unsmarshal the given payload data, error was: %v", err)) - os.Exit(1) - } - // set the namespace for the crd. - crd.SetNamespace(podNamespace) - // create the crd in kubernetes. - if err := c.Create(context.Background(), &crd); err != nil { - fmt.Println(fmt.Sprintf("Task failed to create the object, error was: %v", err)) - os.Exit(1) - } - - // check the status of the crd until we have the status conditions. - // otherwise give up after 30 minutes. 180 retries, 10 seconds apart. - try.MaxRetries = 180 - err = try.Do(func(attempt int) (bool, error) { - var err error - if err := c.Get(context.Background(), types.NamespacedName{ - Namespace: podNamespace, - Name: crd.GetName(), - }, &crd); err != nil { - fmt.Println(fmt.Sprintf("Task failed to get the object from kubernetes, error was: %v", err)) - os.Exit(1) - } - // check if the status exists, the job may not have started. - if _, ok := crd.Object["status"]; ok { - conditions := crd.Object["status"].(map[string]interface{}) - // check if the conditions exists, the job may not have started. - if _, ok := conditions["conditions"]; ok { - // loop over the conditions until we get a completed or failed status. - for _, condition := range conditions["conditions"].([]interface{}) { - mapval := condition.(map[string]interface{}) - // if the status is failed, we need to make sure the pod exits accordingly - if mapval["type"].(string) == "failed" { - crdSpec := crd.Object["spec"].(map[string]interface{}) - if crdSpec != nil { - crdHosts := crdSpec["hosts"].(map[string]interface{}) - if crdHosts != nil { - rData := returnData{ - Status: "Failed", - ProductionEnvironment: payloadData["productionEnvironment"].(string), - StandbyProdutionEnvironment: payloadData["standbyEnvironment"].(string), - ProductionRoutes: crdHosts["activeHosts"].(string), - StandbyRoutes: crdHosts["standbyHosts"].(string), - } - - // print the result of the task, it will go back to lagoon-logs to be displayed - // to the user - jsonData, _ := json.Marshal(rData) - fmt.Println(string(jsonData)) - // exit as the task failed - os.Exit(1) - } - fmt.Println("Task failed, error was: no hosts found in resource") - os.Exit(1) - } - fmt.Println("Task failed, error was: no spec found in resource") - os.Exit(1) - } - // if the status is completed, then do some additional steps as there could still be a failure - if mapval["type"].(string) == "completed" { - crdSpec := crd.Object["spec"].(map[string]interface{}) - if crdSpec != nil { - crdHosts := crdSpec["hosts"].(map[string]interface{}) - if crdHosts != nil { - rData := returnData{ - Status: "Completed", - ProductionEnvironment: payloadData["productionEnvironment"].(string), - StandbyProdutionEnvironment: payloadData["standbyEnvironment"].(string), - ProductionRoutes: crdHosts["activeHosts"].(string), - StandbyRoutes: crdHosts["standbyHosts"].(string), - } - - // print the result of the task, it will go back to lagoon-logs to be displayed - // to the user - jsonData, _ := json.Marshal(rData) - fmt.Println(string(jsonData)) - - // update this pods annotations so that the lagoonmonitor controller - // knows that it needs to send information back to lagoon - pod := corev1.Pod{} - if err := c.Get(context.Background(), types.NamespacedName{ - Namespace: podNamespace, - Name: podName, - }, &pod); err != nil { - fmt.Println(fmt.Sprintf(`======================================== -Task failed to get the pod to update, error was: %v -======================================== -The active standby switch completed, but Lagoon has not been updated to reflect the changes. -Please contact your Lagoon administrator to make sure your project gets updated correctly. -Provide a copy of this entire log to the team.`, err)) - os.Exit(1) - } - // the job data to send back to lagoon must be base64 encoded - mergePatch, _ := json.Marshal(map[string]interface{}{ - "metadata": map[string]interface{}{ - "annotations": map[string]interface{}{ - "lagoon.sh/taskData": base64.StdEncoding.EncodeToString(jsonData), - }, - }, - }) - // update the pod with the annotation - if err := c.Patch(context.Background(), &pod, client.RawPatch(types.MergePatchType, mergePatch)); err != nil { - fmt.Println(fmt.Sprintf(`======================================== -Task failed to update pod with return information, error was: %v -======================================== -The active standby switch completed, but Lagoon has not been updated to reflect the changes. -Please contact your Lagoon administrator to make sure your project gets updated correctly. -Provide a copy of this entire log to the team.`, err)) - // if the update fails, exit 1 - // if this update fails, it will not update the annotation on the pod - // and so the monitor controller won't know to send the response data to lagoon - // in this case, the migration completed, but the task failed - // inform the user - os.Exit(1) - } - os.Exit(0) - } - fmt.Println("Task failed, error was: no hosts found in resource") - os.Exit(1) - } - fmt.Println("Task failed, error was: no spec found in resource") - os.Exit(1) - } - fmt.Println(fmt.Sprintf("Task current status is %s, retrying check", mapval["type"].(string))) - } - } - } - // sleep for 10 seconds up to a maximum of 180 times (30 minutes) before finally giving up - time.Sleep(10 * time.Second) - err = fmt.Errorf("status condition not met yet") - return attempt < 180, err - }) + err = dioscuri.RunMigration(c, rData, podName, podNamespace) if err != nil { - fmt.Println(fmt.Sprintf("Task failed, timed out after 30 minutes waiting for the job to start: %v", err)) + fmt.Println(err) os.Exit(1) } } From 6f1c515ebda600c7f7b35bf4372f8ac19e2ae47a Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Tue, 14 Nov 2023 12:17:55 +1100 Subject: [PATCH 4/8] chore: remove unused migratehost function --- node-packages/commons/src/tasks.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/node-packages/commons/src/tasks.ts b/node-packages/commons/src/tasks.ts index 94697c1004..377cf76279 100644 --- a/node-packages/commons/src/tasks.ts +++ b/node-packages/commons/src/tasks.ts @@ -1081,28 +1081,6 @@ const restoreConfig = (name, backupId, backupS3Config, restoreS3Config) => { return config; }; -// creates the route/ingress migration config -const migrateHosts = (destinationNamespace, sourceNamespace) => { - const randId = Math.random().toString(36).substring(7); - const migrateName = `host-migration-${randId}`; - let config = { - apiVersion: 'dioscuri.amazee.io/v1', - kind: 'HostMigration', - metadata: { - name: migrateName, - annotations: { - 'dioscuri.amazee.io/migrate':'true' - } - }, - spec: { - destinationNamespace: destinationNamespace, - activeEnvironment: sourceNamespace, - }, - }; - - return config; -}; - export const getTaskProjectEnvironmentVariables =async (projectName: string, environmentId: number) => { // inject variables into tasks the same way it is in builds // this makes variables available to tasks the same way for consumption From f1fc25ad96755ed1d8d6b43529277ea0bb6693af Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Tue, 14 Nov 2023 12:23:29 +1100 Subject: [PATCH 5/8] chore: update readme for task image --- taskimages/activestandby/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/taskimages/activestandby/README.md b/taskimages/activestandby/README.md index a1c224088f..2e98d199f1 100644 --- a/taskimages/activestandby/README.md +++ b/taskimages/activestandby/README.md @@ -1,3 +1,11 @@ # tasks-activestandby -This image is used by the activestandby task when using the Lagoon Kubernetes Controllers \ No newline at end of file +This image is used by the activestandby task. The remote-controller has knowledge of this task and will create a role binding between the two namespaces to allow them to temporarily talk and create/edit/delete resources between them as part of the task process. + +The resulting payload contains information about the actions that were performed, which are sent back to Lagoon via the message queue to be reflected in the API. + +The basic idea is that when activestandby is triggered, it collects the ingress in both namespaces that match the labels `dioscuri.amazee.io/migrate=true` and `activestandby.lagoon.sh/migrate=true` and will then perform the action of storing information about them, removing them from the source namespace, and then creating them in the destination namespace. + +Part of this process also involves copying secrets and certificates if they are present, so that they are also available in the destination namespace. + +When the process of migrating is taking place, all ingress have a new label added which is `activestandby.lagoon.sh/migrating=true`, which at the end of the migration process is set to `false`. This label will only be true while migrations are taking place. This allows external systems to be aware of the migration if they need to take any action, or prevent some actions from taking place. \ No newline at end of file From 8b1767881d0fcf3a9e1d188b27699d7820333d3b Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Mon, 20 Nov 2023 16:33:52 +1100 Subject: [PATCH 6/8] test: add test coverage for activestandby task --- taskimages/activestandby/Dockerfile | 1 + taskimages/activestandby/dioscuri/dioscuri.go | 14 +- .../activestandby/dioscuri/dioscuri_test.go | 250 ++++++++++++++++++ .../activestandby/dioscuri/ingress_test.go | 191 +++++++++++++ .../activestandby/dioscuri/secrets_test.go | 72 +++++ .../check-secrets/test1-seed-ingresslist.json | 51 ++++ .../check-secrets/test1-seed-secret.json | 9 + .../test1-result-ingresslist.json | 49 ++++ .../test1-seed-ingresslist.json | 51 ++++ .../check-services/test1-seed-service.json | 32 +++ .../test2-result-ingresslist.json | 49 ++++ .../test2-seed-ingresslist.json | 51 ++++ .../check-services/test2-seed-service.json | 32 +++ .../test3-result-ingresslist.json | 4 + .../test3-seed-ingresslist.json | 51 ++++ .../check-services/test3-seed-service.json | 32 +++ .../test1-result-ingresslist.json | 51 ++++ .../test1-seed-ingress.json | 42 +++ .../test2-result-ingresslist.json | 51 ++++ .../test2-seed-ingress.json | 42 +++ .../test1-result-ingress.json | 45 ++++ .../test1-seed-ingress.json | 43 +++ .../patch-ingress/test1-seed-ingress.json | 42 +++ .../run-migration/test1-seed-ingress1.json | 42 +++ .../run-migration/test1-seed-ingress2.json | 42 +++ .../run-migration/test1-seed-ingress3.json | 42 +++ .../run-migration/test1-seed-service1.json | 32 +++ .../run-migration/test1-seed-service2.json | 32 +++ taskimages/activestandby/go.mod | 1 + taskimages/activestandby/go.sum | 2 + taskimages/activestandby/main.go | 2 +- 31 files changed, 1446 insertions(+), 4 deletions(-) create mode 100644 taskimages/activestandby/dioscuri/dioscuri_test.go create mode 100644 taskimages/activestandby/dioscuri/ingress_test.go create mode 100644 taskimages/activestandby/dioscuri/secrets_test.go create mode 100644 taskimages/activestandby/dioscuri/test-data/check-secrets/test1-seed-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-secrets/test1-seed-secret.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test1-result-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test1-seed-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test1-seed-service.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test2-result-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test2-seed-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test2-seed-service.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test3-result-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test3-seed-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/check-services/test3-seed-service.json create mode 100644 taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test1-result-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test1-seed-ingress.json create mode 100644 taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test2-result-ingresslist.json create mode 100644 taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test2-seed-ingress.json create mode 100644 taskimages/activestandby/dioscuri/test-data/individual-migration/test1-result-ingress.json create mode 100644 taskimages/activestandby/dioscuri/test-data/individual-migration/test1-seed-ingress.json create mode 100644 taskimages/activestandby/dioscuri/test-data/patch-ingress/test1-seed-ingress.json create mode 100644 taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress1.json create mode 100644 taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress2.json create mode 100644 taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress3.json create mode 100644 taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-service1.json create mode 100644 taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-service2.json diff --git a/taskimages/activestandby/Dockerfile b/taskimages/activestandby/Dockerfile index 407cf75ca8..f3ba784e53 100644 --- a/taskimages/activestandby/Dockerfile +++ b/taskimages/activestandby/Dockerfile @@ -5,6 +5,7 @@ COPY . /go/src/github.com/uselagoon/lagoon/taskimages/activestandby/ WORKDIR /go/src/github.com/uselagoon/lagoon/taskimages/activestandby/ # Build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} go test -v ./... RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} go build -a -o taskrunner . # Use distroless as minimal base image to package the binary diff --git a/taskimages/activestandby/dioscuri/dioscuri.go b/taskimages/activestandby/dioscuri/dioscuri.go index 52c36b8caa..a97b7cd9db 100644 --- a/taskimages/activestandby/dioscuri/dioscuri.go +++ b/taskimages/activestandby/dioscuri/dioscuri.go @@ -33,7 +33,7 @@ type ReturnData struct { DestinationNamespace string `json:"DestinationNamespace"` } -func RunMigration(c client.Client, rData ReturnData, podName, podNamespace string) error { +func RunMigration(c client.Client, rData *ReturnData, podName, podNamespace string) error { ctx := context.Background() var activeMigratedIngress []string var standbyMigratedIngress []string @@ -284,12 +284,20 @@ Provide a copy of this entire log to the team.`, err) // print the result of the task, it will go back to lagoon-logs to be displayed // to the user + oldProduction := rData.ProductionEnvironment + oldStandby := rData.StandbyProdutionEnvironment + rData.ProductionEnvironment = oldStandby + rData.StandbyProdutionEnvironment = oldProduction + // oldSource := rData.SourceNamespace + // oldDestination := rData.DestinationNamespace + rData.ProductionEnvironment = oldStandby + rData.StandbyProdutionEnvironment = oldProduction jsonData, _ := json.Marshal(rData) fmt.Println("========================================") fmt.Println("Status:", rData.Status) - fmt.Println("Active Environment:", rData.StandbyProdutionEnvironment) + fmt.Println("Active Environment:", rData.ProductionEnvironment) fmt.Println("Active Routes:", rData.ProductionRoutes) - fmt.Println("Standby Environment:", rData.ProductionEnvironment) + fmt.Println("Standby Environment:", rData.StandbyProdutionEnvironment) fmt.Println("Standby Routes:", rData.StandbyRoutes) fmt.Println("========================================") pod := corev1.Pod{} diff --git a/taskimages/activestandby/dioscuri/dioscuri_test.go b/taskimages/activestandby/dioscuri/dioscuri_test.go new file mode 100644 index 0000000000..97df3c25e5 --- /dev/null +++ b/taskimages/activestandby/dioscuri/dioscuri_test.go @@ -0,0 +1,250 @@ +package dioscuri + +import ( + "context" + "encoding/json" + "fmt" + "os" + "reflect" + "testing" + + corev1 "k8s.io/api/core/v1" + networkv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + client "sigs.k8s.io/controller-runtime/pkg/client" + ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var seedNamespaces = []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "active-main", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "standby-main", + }, + }, +} + +func GetFakeClient() (client.Client, error) { + k8sScheme := runtime.NewScheme() + if err := networkv1.AddToScheme(k8sScheme); err != nil { + return nil, err + } + if err := corev1.AddToScheme(k8sScheme); err != nil { + return nil, err + } + clientBuilder := ctrlfake.NewClientBuilder() + clientBuilder = clientBuilder.WithScheme(k8sScheme) + + fakeClient := clientBuilder.Build() + for _, ns := range seedNamespaces { + err := fakeClient.Create(context.Background(), &ns) + if err != nil { + return nil, err + } + } + + return fakeClient, nil +} + +func getNamespaces(c client.Client) error { + v := &corev1.NamespaceList{} + listOption := (&client.ListOptions{}).ApplyOptions([]client.ListOption{}) + err := c.List(context.Background(), v, listOption) + if err != nil { + return err + } + fmt.Println(v) + return nil +} + +func Test_checkKubernetesServices(t *testing.T) { + type args struct { + ctx context.Context + seedIngressList string + seedServices []string + destinationNamespace string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "test1 - active to standby environment", + args: args{ + destinationNamespace: "standby-main", + seedServices: []string{ + "test-data/check-services/test1-seed-service.json", + }, + seedIngressList: "test-data/check-services/test1-seed-ingresslist.json", + }, + want: "test-data/check-services/test1-result-ingresslist.json", + }, + { + name: "test1 - standby to active environment", + args: args{ + destinationNamespace: "active-main", + seedServices: []string{ + "test-data/check-services/test2-seed-service.json", + }, + seedIngressList: "test-data/check-services/test2-seed-ingresslist.json", + }, + want: "test-data/check-services/test2-result-ingresslist.json", + }, + { + name: "test3 - active to standby environment with no matching service in destination", + args: args{ + destinationNamespace: "standby-main", + seedServices: []string{ + "test-data/check-services/test3-seed-service.json", + }, + seedIngressList: "test-data/check-services/test3-seed-ingresslist.json", + }, + want: "test-data/check-services/test3-result-ingresslist.json", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeClient, _ := GetFakeClient() + for _, service := range tt.args.seedServices { + iBytes, err := os.ReadFile(service) + if err != nil { + t.Errorf("couldn't read file %v: %v", service, err) + } + cService := &corev1.Service{} + json.Unmarshal(iBytes, cService) + err = fakeClient.Create(context.Background(), cService) + if err != nil { + t.Errorf("checkKubernetesServices() error = %v", err) + } + } + r1, err := os.ReadFile(tt.args.seedIngressList) + if err != nil { + t.Errorf("couldn't read file %v: %v", tt.args.seedIngressList, err) + } + seedIngressList := &networkv1.IngressList{} + if err = json.Unmarshal(r1, seedIngressList); err != nil { + t.Errorf("couldn't unmarshal ingress list result %v: %v", tt.args.seedIngressList, err) + } + ingressToMigrate := &networkv1.IngressList{} + if err := checkKubernetesServices(tt.args.ctx, fakeClient, seedIngressList, ingressToMigrate, tt.args.destinationNamespace); (err != nil) != tt.wantErr { + t.Errorf("checkKubernetesServices() error = %v, wantErr %v", err, tt.wantErr) + } + r1, err = os.ReadFile(tt.want) + if err != nil { + t.Errorf("couldn't read file %v: %v", tt.want, err) + } + wantIngressList := &networkv1.IngressList{} + if err = json.Unmarshal(r1, wantIngressList); err != nil { + t.Errorf("couldn't unmarshal ingress list result %v: %v", tt.want, err) + } + if !reflect.DeepEqual(ingressToMigrate, wantIngressList) { + gotB, _ := json.Marshal(ingressToMigrate) + wantB, _ := json.Marshal(wantIngressList) + t.Errorf("checkKubernetesServices() got: \n%v \nwant: \n%v", string(gotB), string(wantB)) + } + }) + } +} + +func TestRunMigration(t *testing.T) { + type args struct { + c client.Client + rData *ReturnData + podName string + podNamespace string + seedIngress []string + seedServices []string + } + tests := []struct { + name string + args args + wantErr bool + want ReturnData + }{ + { + name: "test1", + args: args{ + rData: &ReturnData{ + SourceNamespace: "active-main", + DestinationNamespace: "standby-main", + ProductionEnvironment: "standby", + StandbyProdutionEnvironment: "active", + }, + podName: "task-pod", + podNamespace: "standby-main", + seedIngress: []string{ + "test-data/run-migration/test1-seed-ingress1.json", + "test-data/run-migration/test1-seed-ingress2.json", + "test-data/run-migration/test1-seed-ingress3.json", + }, + seedServices: []string{ + "test-data/run-migration/test1-seed-service1.json", + "test-data/run-migration/test1-seed-service2.json", + }, + }, + want: ReturnData{ + Status: "Completed", + SourceNamespace: "active-main", + DestinationNamespace: "standby-main", + ProductionEnvironment: "active", + ProductionRoutes: "https://example.com,https://www.example.com", + StandbyProdutionEnvironment: "standby", + StandbyRoutes: "https://standby.example.com", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeClient, _ := GetFakeClient() + err := fakeClient.Create(context.Background(), &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: tt.args.podName, + Namespace: tt.args.podNamespace, + }, + }) + if err != nil { + t.Errorf("RunMigration() error = %v, wantErr %v", err, tt.wantErr) + } + for _, ingress := range tt.args.seedIngress { + iBytes, err := os.ReadFile(ingress) + if err != nil { + t.Errorf("couldn't read file %v: %v", ingress, err) + } + cIngress := &networkv1.Ingress{} + json.Unmarshal(iBytes, cIngress) + err = fakeClient.Create(context.Background(), cIngress) + if err != nil { + t.Errorf("getIngressWithLabel() error = %v", err) + } + } + for _, service := range tt.args.seedServices { + iBytes, err := os.ReadFile(service) + if err != nil { + t.Errorf("couldn't read file %v: %v", service, err) + } + cService := &corev1.Service{} + json.Unmarshal(iBytes, cService) + err = fakeClient.Create(context.Background(), cService) + if err != nil { + t.Errorf("checkKubernetesServices() error = %v", err) + } + } + if err := RunMigration(fakeClient, tt.args.rData, tt.args.podName, tt.args.podNamespace); (err != nil) != tt.wantErr { + t.Errorf("RunMigration() error = %v, wantErr %v", err, tt.wantErr) + } + if *tt.args.rData != tt.want { + gotB, _ := json.Marshal(tt.args.rData) + wantB, _ := json.Marshal(tt.want) + t.Errorf("getIngressWithLabel() got: \n%v \nwant: \n%v", string(gotB), string(wantB)) + } + }) + } +} diff --git a/taskimages/activestandby/dioscuri/ingress_test.go b/taskimages/activestandby/dioscuri/ingress_test.go new file mode 100644 index 0000000000..527aaff876 --- /dev/null +++ b/taskimages/activestandby/dioscuri/ingress_test.go @@ -0,0 +1,191 @@ +package dioscuri + +import ( + "context" + "encoding/json" + "os" + "reflect" + "testing" + + networkv1 "k8s.io/api/networking/v1" +) + +func Test_getIngressWithLabel(t *testing.T) { + type args struct { + ctx context.Context + namespace string + seedIngress []string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "test1 - active environment", + args: args{ + namespace: "active-main", + seedIngress: []string{ + "test-data/get-ingress-labels/test1-seed-ingress.json", + }, + }, + want: "test-data/get-ingress-labels/test1-result-ingresslist.json", + }, + { + name: "test2 - standby environment", + args: args{ + namespace: "standby-main", + seedIngress: []string{ + "test-data/get-ingress-labels/test2-seed-ingress.json", + }, + }, + want: "test-data/get-ingress-labels/test2-result-ingresslist.json", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeClient, _ := GetFakeClient() + for _, ingress := range tt.args.seedIngress { + iBytes, err := os.ReadFile(ingress) + if err != nil { + t.Errorf("couldn't read file %v: %v", ingress, err) + } + cIngress := &networkv1.Ingress{} + json.Unmarshal(iBytes, cIngress) + err = fakeClient.Create(context.Background(), cIngress) + if err != nil { + t.Errorf("getIngressWithLabel() error = %v", err) + } + } + got, err := getIngressWithLabel(tt.args.ctx, fakeClient, tt.args.namespace) + if (err != nil) != tt.wantErr { + t.Errorf("getIngressWithLabel() error = %v, wantErr %v", err, tt.wantErr) + return + } + r1, err := os.ReadFile(tt.want) + if err != nil { + t.Errorf("couldn't read file %v: %v", tt.want, err) + } + wantIngressList := &networkv1.IngressList{} + if err = json.Unmarshal(r1, wantIngressList); err != nil { + t.Errorf("couldn't unmarshal ingress list result %v: %v", tt.want, err) + } + if !reflect.DeepEqual(got, wantIngressList) { + gotB, _ := json.Marshal(got) + wantB, _ := json.Marshal(wantIngressList) + t.Errorf("getIngressWithLabel() got: \n%v \nwant: \n%v", string(gotB), string(wantB)) + } + }) + } +} + +func Test_patchIngress(t *testing.T) { + type args struct { + ctx context.Context + seedIngress string + labels map[string]interface{} + } + tests := []struct { + name string + args args + wantErr bool + wantLabels map[string]string + }{ + { + name: "test1 - active environment", + args: args{ + seedIngress: "test-data/patch-ingress/test1-seed-ingress.json", + labels: map[string]interface{}{ + "activestandby.lagoon.sh/migrating": "true", + }, + }, + wantLabels: map[string]string{ + "activestandby.lagoon.sh/migrate": "true", + "activestandby.lagoon.sh/migrating": "true", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeClient, _ := GetFakeClient() + iBytes, err := os.ReadFile(tt.args.seedIngress) + if err != nil { + t.Errorf("couldn't read file %v: %v", tt.args.seedIngress, err) + } + cIngress := &networkv1.Ingress{} + json.Unmarshal(iBytes, cIngress) + err = fakeClient.Create(context.Background(), cIngress) + if err != nil { + t.Errorf("getIngressWithLabel() error = %v", err) + } + if err := patchIngress(tt.args.ctx, fakeClient, cIngress, tt.args.labels); (err != nil) != tt.wantErr { + t.Errorf("patchIngress() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(cIngress.ObjectMeta.Labels, tt.wantLabels) { + gotB, _ := json.Marshal(cIngress.ObjectMeta.Labels) + wantB, _ := json.Marshal(tt.wantLabels) + t.Errorf("checkKubernetesServices() got: \n%v \nwant: \n%v", string(gotB), string(wantB)) + } + }) + } +} + +func Test_individualIngressMigration(t *testing.T) { + type args struct { + ctx context.Context + ingress *networkv1.Ingress + seedIngress string + sourceNamespace string + destinationNamespace string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "test1 - active to standby", + args: args{ + sourceNamespace: "active-main", + destinationNamespace: "standby-main", + seedIngress: "test-data/individual-migration/test1-seed-ingress.json", + }, + want: "test-data/individual-migration/test1-result-ingress.json", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeClient, _ := GetFakeClient() + iBytes, err := os.ReadFile(tt.args.seedIngress) + if err != nil { + t.Errorf("couldn't read file %v: %v", tt.args.seedIngress, err) + } + cIngress := &networkv1.Ingress{} + json.Unmarshal(iBytes, cIngress) + err = fakeClient.Create(context.Background(), cIngress) + if err != nil { + t.Errorf("getIngressWithLabel() error = %v", err) + } + got, err := individualIngressMigration(tt.args.ctx, fakeClient, cIngress, tt.args.sourceNamespace, tt.args.destinationNamespace) + if (err != nil) != tt.wantErr { + t.Errorf("individualIngressMigration() error = %v, wantErr %v", err, tt.wantErr) + return + } + r1, err := os.ReadFile(tt.want) + if err != nil { + t.Errorf("couldn't read file %v: %v", tt.want, err) + } + wantIngress := &networkv1.Ingress{} + if err = json.Unmarshal(r1, wantIngress); err != nil { + t.Errorf("couldn't unmarshal ingress list result %v: %v", tt.want, err) + } + if !reflect.DeepEqual(got, wantIngress) { + gotB, _ := json.Marshal(got) + wantB, _ := json.Marshal(wantIngress) + t.Errorf("individualIngressMigration() got: \n%v \nwant: \n%v", string(gotB), string(wantB)) + } + }) + } +} diff --git a/taskimages/activestandby/dioscuri/secrets_test.go b/taskimages/activestandby/dioscuri/secrets_test.go new file mode 100644 index 0000000000..4a3a7d8efc --- /dev/null +++ b/taskimages/activestandby/dioscuri/secrets_test.go @@ -0,0 +1,72 @@ +package dioscuri + +import ( + "context" + "encoding/json" + "os" + "testing" + + corev1 "k8s.io/api/core/v1" + networkv1 "k8s.io/api/networking/v1" +) + +func Test_checkSecrets(t *testing.T) { + type args struct { + ctx context.Context + seedIngressList string + seedSecrets []string + destinationNamespace string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "test1 - active environment", + args: args{ + destinationNamespace: "active-main", + seedSecrets: []string{ + "test-data/check-secrets/test1-seed-secret.json", + }, + seedIngressList: "test-data/check-secrets/test1-seed-ingresslist.json", + }, + }, + { + name: "test1 - active environment secret doesn't exist", + args: args{ + destinationNamespace: "active-main", + seedSecrets: []string{}, + seedIngressList: "test-data/check-secrets/test1-seed-ingresslist.json", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeClient, _ := GetFakeClient() + for _, service := range tt.args.seedSecrets { + iBytes, err := os.ReadFile(service) + if err != nil { + t.Errorf("couldn't read file %v: %v", service, err) + } + cSecret := &corev1.Secret{} + json.Unmarshal(iBytes, cSecret) + err = fakeClient.Create(context.Background(), cSecret) + if err != nil { + t.Errorf("checkSecrets() error = %v", err) + } + } + r1, err := os.ReadFile(tt.args.seedIngressList) + if err != nil { + t.Errorf("couldn't read file %v: %v", tt.args.seedIngressList, err) + } + seedIngressList := &networkv1.IngressList{} + if err = json.Unmarshal(r1, seedIngressList); err != nil { + t.Errorf("couldn't unmarshal ingress list result %v: %v", tt.args.seedIngressList, err) + } + if err := checkSecrets(tt.args.ctx, fakeClient, seedIngressList, tt.args.destinationNamespace); (err != nil) != tt.wantErr { + t.Errorf("checkSecrets() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/taskimages/activestandby/dioscuri/test-data/check-secrets/test1-seed-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/check-secrets/test1-seed-ingresslist.json new file mode 100644 index 0000000000..22eb527a2a --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-secrets/test1-seed-ingresslist.json @@ -0,0 +1,51 @@ +{ + "kind": "IngressList", + "apiVersion": "networking.k8s.io/v1", + "metadata": {}, + "items": [ + { + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "active-main", + "resourceVersion": "2", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } + } + ] +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/check-secrets/test1-seed-secret.json b/taskimages/activestandby/dioscuri/test-data/check-secrets/test1-seed-secret.json new file mode 100644 index 0000000000..5f6d79c878 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-secrets/test1-seed-secret.json @@ -0,0 +1,9 @@ +{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "example.com-tls", + "namespace": "active-main" + }, + "type": "kubernetes.io/tls" +} diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test1-result-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/check-services/test1-result-ingresslist.json new file mode 100644 index 0000000000..c1c7f0acad --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test1-result-ingresslist.json @@ -0,0 +1,49 @@ +{ + "metadata": {}, + "items": [ + { + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "active-main", + "resourceVersion": "2", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } + } + ] +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test1-seed-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/check-services/test1-seed-ingresslist.json new file mode 100644 index 0000000000..22eb527a2a --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test1-seed-ingresslist.json @@ -0,0 +1,51 @@ +{ + "kind": "IngressList", + "apiVersion": "networking.k8s.io/v1", + "metadata": {}, + "items": [ + { + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "active-main", + "resourceVersion": "2", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } + } + ] +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test1-seed-service.json b/taskimages/activestandby/dioscuri/test-data/check-services/test1-seed-service.json new file mode 100644 index 0000000000..0b3ed3727a --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test1-seed-service.json @@ -0,0 +1,32 @@ +{ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "http", + "namespace": "standby-main" + }, + "spec": { + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP", + "targetPort": "http" + } + ], + "selector": { + "app.kubernetes.io/instance": "nginx", + "app.kubernetes.io/name": "nginx-php-persistent" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } +} diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test2-result-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/check-services/test2-result-ingresslist.json new file mode 100644 index 0000000000..3f78a491cc --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test2-result-ingresslist.json @@ -0,0 +1,49 @@ +{ + "metadata": {}, + "items": [ + { + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "standby-main", + "resourceVersion": "2", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } + } + ] +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test2-seed-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/check-services/test2-seed-ingresslist.json new file mode 100644 index 0000000000..d6f2ad4a53 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test2-seed-ingresslist.json @@ -0,0 +1,51 @@ +{ + "kind": "IngressList", + "apiVersion": "networking.k8s.io/v1", + "metadata": {}, + "items": [ + { + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "standby-main", + "resourceVersion": "2", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } + } + ] +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test2-seed-service.json b/taskimages/activestandby/dioscuri/test-data/check-services/test2-seed-service.json new file mode 100644 index 0000000000..8524505c51 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test2-seed-service.json @@ -0,0 +1,32 @@ +{ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "http", + "namespace": "active-main" + }, + "spec": { + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP", + "targetPort": "http" + } + ], + "selector": { + "app.kubernetes.io/instance": "nginx", + "app.kubernetes.io/name": "nginx-php-persistent" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } +} diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test3-result-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/check-services/test3-result-ingresslist.json new file mode 100644 index 0000000000..5d104f69b7 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test3-result-ingresslist.json @@ -0,0 +1,4 @@ +{ + "metadata": {}, + "items": null +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test3-seed-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/check-services/test3-seed-ingresslist.json new file mode 100644 index 0000000000..22eb527a2a --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test3-seed-ingresslist.json @@ -0,0 +1,51 @@ +{ + "kind": "IngressList", + "apiVersion": "networking.k8s.io/v1", + "metadata": {}, + "items": [ + { + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "active-main", + "resourceVersion": "2", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } + } + ] +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/check-services/test3-seed-service.json b/taskimages/activestandby/dioscuri/test-data/check-services/test3-seed-service.json new file mode 100644 index 0000000000..8524505c51 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/check-services/test3-seed-service.json @@ -0,0 +1,32 @@ +{ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "http", + "namespace": "active-main" + }, + "spec": { + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP", + "targetPort": "http" + } + ], + "selector": { + "app.kubernetes.io/instance": "nginx", + "app.kubernetes.io/name": "nginx-php-persistent" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } +} diff --git a/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test1-result-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test1-result-ingresslist.json new file mode 100644 index 0000000000..22eb527a2a --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test1-result-ingresslist.json @@ -0,0 +1,51 @@ +{ + "kind": "IngressList", + "apiVersion": "networking.k8s.io/v1", + "metadata": {}, + "items": [ + { + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "active-main", + "resourceVersion": "2", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } + } + ] +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test1-seed-ingress.json b/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test1-seed-ingress.json new file mode 100644 index 0000000000..6f52de5513 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test1-seed-ingress.json @@ -0,0 +1,42 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "active-main", + "labels": { + "dioscuri.amazee.io/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test2-result-ingresslist.json b/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test2-result-ingresslist.json new file mode 100644 index 0000000000..1477c6761c --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test2-result-ingresslist.json @@ -0,0 +1,51 @@ +{ + "kind": "IngressList", + "apiVersion": "networking.k8s.io/v1", + "metadata": {}, + "items": [ + { + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "standby.example.com", + "namespace": "standby-main", + "resourceVersion": "2", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "standby.example.com-tls" + } + ], + "rules": [ + { + "host": "standby.example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } + } + ] +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test2-seed-ingress.json b/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test2-seed-ingress.json new file mode 100644 index 0000000000..990f4091f5 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/get-ingress-labels/test2-seed-ingress.json @@ -0,0 +1,42 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "standby.example.com", + "namespace": "standby-main", + "labels": { + "dioscuri.amazee.io/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "standby.example.com-tls" + } + ], + "rules": [ + { + "host": "standby.example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/individual-migration/test1-result-ingress.json b/taskimages/activestandby/dioscuri/test-data/individual-migration/test1-result-ingress.json new file mode 100644 index 0000000000..654d950714 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/individual-migration/test1-result-ingress.json @@ -0,0 +1,45 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "standby-main", + "resourceVersion": "1", + "creationTimestamp": null, + "labels": { + "activestandby.lagoon.sh/migrate": "true", + "activestandby.lagoon.sh/migrating": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/individual-migration/test1-seed-ingress.json b/taskimages/activestandby/dioscuri/test-data/individual-migration/test1-seed-ingress.json new file mode 100644 index 0000000000..e327cde402 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/individual-migration/test1-seed-ingress.json @@ -0,0 +1,43 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "active-main", + "labels": { + "activestandby.lagoon.sh/migrate": "true", + "activestandby.lagoon.sh/migrating": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/patch-ingress/test1-seed-ingress.json b/taskimages/activestandby/dioscuri/test-data/patch-ingress/test1-seed-ingress.json new file mode 100644 index 0000000000..0d9b9550b1 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/patch-ingress/test1-seed-ingress.json @@ -0,0 +1,42 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "active-main", + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress1.json b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress1.json new file mode 100644 index 0000000000..76b20ad22b --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress1.json @@ -0,0 +1,42 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example.com", + "namespace": "standby-main", + "labels": { + "activestandby.lagoon.sh/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "example.com-tls" + } + ], + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress2.json b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress2.json new file mode 100644 index 0000000000..b5933861ef --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress2.json @@ -0,0 +1,42 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "www.example.com", + "namespace": "standby-main", + "labels": { + "dioscuri.amazee.io/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "www.example.com-tls" + } + ], + "rules": [ + { + "host": "www.example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress3.json b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress3.json new file mode 100644 index 0000000000..f755bd64df --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-ingress3.json @@ -0,0 +1,42 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "standby.example.com", + "namespace": "active-main", + "labels": { + "dioscuri.amazee.io/migrate": "true" + } + }, + "spec": { + "tls": [ + { + "secretName": "standby.example.com-tls" + } + ], + "rules": [ + { + "host": "standby.example.com", + "http": { + "paths": [ + { + "path": "/", + "pathType": "Prefix", + "backend": { + "service": { + "name": "http", + "port": { + "name": "http" + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-service1.json b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-service1.json new file mode 100644 index 0000000000..8524505c51 --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-service1.json @@ -0,0 +1,32 @@ +{ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "http", + "namespace": "active-main" + }, + "spec": { + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP", + "targetPort": "http" + } + ], + "selector": { + "app.kubernetes.io/instance": "nginx", + "app.kubernetes.io/name": "nginx-php-persistent" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } +} diff --git a/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-service2.json b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-service2.json new file mode 100644 index 0000000000..0b3ed3727a --- /dev/null +++ b/taskimages/activestandby/dioscuri/test-data/run-migration/test1-seed-service2.json @@ -0,0 +1,32 @@ +{ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "http", + "namespace": "standby-main" + }, + "spec": { + "internalTrafficPolicy": "Cluster", + "ipFamilies": [ + "IPv4" + ], + "ipFamilyPolicy": "SingleStack", + "ports": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP", + "targetPort": "http" + } + ], + "selector": { + "app.kubernetes.io/instance": "nginx", + "app.kubernetes.io/name": "nginx-php-persistent" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } +} diff --git a/taskimages/activestandby/go.mod b/taskimages/activestandby/go.mod index b4ac0232c8..46cc0232f3 100644 --- a/taskimages/activestandby/go.mod +++ b/taskimages/activestandby/go.mod @@ -15,6 +15,7 @@ require ( github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect diff --git a/taskimages/activestandby/go.sum b/taskimages/activestandby/go.sum index a20934110e..0149129e1f 100644 --- a/taskimages/activestandby/go.sum +++ b/taskimages/activestandby/go.sum @@ -13,6 +13,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= diff --git a/taskimages/activestandby/main.go b/taskimages/activestandby/main.go index d1185d6746..b4ecc856d9 100644 --- a/taskimages/activestandby/main.go +++ b/taskimages/activestandby/main.go @@ -75,7 +75,7 @@ func main() { StandbyProdutionEnvironment: payloadData["standbyEnvironment"].(string), } - err = dioscuri.RunMigration(c, rData, podName, podNamespace) + err = dioscuri.RunMigration(c, &rData, podName, podNamespace) if err != nil { fmt.Println(err) os.Exit(1) From cb533d9820641b5521a08f9560e8b351fc605754 Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Mon, 20 Nov 2023 16:34:27 +1100 Subject: [PATCH 7/8] chore: update actions-handler to support new task data, with fallback for old task data --- services/actions-handler/handler/controller_tasks.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/services/actions-handler/handler/controller_tasks.go b/services/actions-handler/handler/controller_tasks.go index 2fe6498bf3..f5af4ff77b 100644 --- a/services/actions-handler/handler/controller_tasks.go +++ b/services/actions-handler/handler/controller_tasks.go @@ -58,11 +58,17 @@ func (m *Messenger) handleTask(ctx context.Context, messageQueue *mq.MessageQueu json.Unmarshal(decodeData, advTask) // then prepare the patch operation updateProject := schema.UpdateProjectPatchInput{ - ProductionEnvironment: &advTask.StandbyProductionEnvironment, // these are inverted because of how the task works - StandbyProductionEnvironment: &advTask.ProductionEnvironment, // these are inverted because of how the task works + ProductionEnvironment: &advTask.ProductionEnvironment, + StandbyProductionEnvironment: &advTask.StandbyProductionEnvironment, ProductionRoutes: &advTask.ProductionRoutes, StandbyRoutes: &advTask.StandbyRoutes, } + switch message.Meta.Key { + case "kubernetes:route:migrate", "deploytarget:route:migrate": + // the old task had these inverted, so this keeps that inversion in place for now + updateProject.ProductionEnvironment = &advTask.StandbyProductionEnvironment + updateProject.StandbyProductionEnvironment = &advTask.ProductionEnvironment + } // update the project in the api updatedProject, err := lagoon.UpdateProject(ctx, int(project.ID), updateProject, l) if err != nil { From 13cc78ee9a489a7301503ad453e03c5b706813a0 Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Mon, 4 Dec 2023 10:55:17 +1100 Subject: [PATCH 8/8] chore: add dioscuri deprecation notice --- DEPRECATIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index 3ecce09fdb..4579e68034 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -12,7 +12,7 @@ All deprecations are listed below, with the most recent announcements at the top ### Lagoon v2.17.0 release link: https://github.com/uselagoon/lagoon/releases/tag/v2.17.0 -* (insert any planned deprecations here) +* This release introduces a new active/standby task image that does not require the use of the [dioscuri controller](https://github.com/amazeeio/dioscuri). Dioscuri is deprecated and will eventually be removed from the `lagoon-remote` helm chart. If you use active/standby functionality in your clusters, you should upgrade to lagoon v2.17.0 and update your remote clusters to the version of the `lagoon-remote` helm chart the v2.17.0 release says to use (see release notes for v2.17.0) ### Lagoon v2.16.0 release link: https://github.com/uselagoon/lagoon/releases/tag/v2.16.0