diff --git a/.gitignore b/.gitignore index ad3596a..54cc2a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules docs/.docusaurus -docs/build \ No newline at end of file +docs/build +functional_tests/tests.log \ No newline at end of file diff --git a/changelog.md b/changelog.md index 9e126df..57f774e 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.3 - 2022-05-03 + +### Changed +- Ensure deployments are in the same namespace as a release +- Enable wildcard matching for deployment name + +```yaml +--- +apiVersion: consul-release-controller.nicholasjackson.io/v1 +kind: Release +metadata: + name: payments + namespace: default +spec: + releaser: + pluginName: "consul" + config: + consulService: "payments" +# namespace: "mynamespace" +# partition: "mypartition" + runtime: + pluginName: "kubernetes" + config: + deployment: "payments-(.*)" + strategy: + pluginName: "canary" + config: + initialDelay: "30s" + initialTraffic: 10 + interval: "30s" + trafficStep: 20 + maxTraffic: 100 + errorThreshold: 5 + monitor: + pluginName: "prometheus" + config: + address: "http://prometheus-kube-prometheus-prometheus.monitoring.svc:9090" + queries: + - name: "request-success" + preset: "envoy-request-success" + min: 99 + - name: "request-duration" + preset: "envoy-request-duration" + min: 20 + max: 200 +``` + +## [0.1.2 - 2022-05-01 + ### Changed - Helm chart Webhook config failure policy now defaults to `Ignore` - Configuration for the server moved to global `config` package diff --git a/clients/consul.go b/clients/consul.go index 8d668ec..705006e 100644 --- a/clients/consul.go +++ b/clients/consul.go @@ -448,6 +448,14 @@ func (c *ConsulImpl) CreateServiceIntention(name string) error { if ce != nil { // we have an existing entry, mutate rather than overwrite defaults = ce.(*api.ServiceIntentionsConfigEntry) + + // first check to see if the source already exists, if so exit + for _, s := range defaults.Sources { + if s.Name == ControllerServiceName { + // intention already exists, exit + return nil + } + } } // update the list of intentions adding the controller intention diff --git a/docs/docs/example_app.md b/docs/docs/example_app.md index 0c79e25..8b1a419 100644 --- a/docs/docs/example_app.md +++ b/docs/docs/example_app.md @@ -1607,7 +1607,8 @@ running, at present the only supported runtime is `kubernetes`, however, other r ##### config | parameter | required | type | values | description | | ---------- | -------- | ------ | ------ | --------------------------------------------------------------- | -| deployment | yes | string | | name of the deployment that will be managed by the controller | +| deployment | yes | string | | name of the deployment that will be managed by the controller, can also contain regular expressions, for example + a deployment value of test-(.*) would match test-v1 and test-v2 | #### strategy diff --git a/functional_tests/tests.log b/functional_tests/tests.log deleted file mode 100755 index 476e503..0000000 --- a/functional_tests/tests.log +++ /dev/null @@ -1,50 +0,0 @@ -2022-04-30T09:36:01.496+0100 [ERROR] Running configuration from: ./shipyard/kubernetes -2022-04-30T09:36:04.320+0100 [ERROR] 2022-04-30T09:36:04.320+0100 [INFO] Creating resources from configuration: path=/home/nicj/go/src/github.com/nicholasjackson/consul-release-controller/shipyard/kubernetes -2022-04-30T09:36:06.972+0100 [ERROR] 2022-04-30T09:36:06.971+0100 [INFO] Creating ImageCache: ref=docker-cache -2022-04-30T09:36:06.975+0100 [ERROR] 2022-04-30T09:36:06.975+0100 [DEBUG] Connecting cache to network: name=network.dc1 -2022-04-30T09:36:06.975+0100 [DEBUG] ImageCache already exists, not recreating -2022-04-30T09:36:06.979+0100 [ERROR] 2022-04-30T09:36:06.979+0100 [DEBUG] Health check urls for browser windows: count=0 -2022-04-30T09:36:06.979+0100 [DEBUG] Browser windows open - -######################################################## - -Title Development setup -Author Nic Jackson -2022-04-30T09:36:06.979+0100 [ERROR] -• Consul: https://localhost:8501 -• Grafana: https://localhost:8080 -• Application: http://localhost:18080 -2022-04-30T09:36:06.979+0100 [ERROR] -This blueprint defines 13 output variables. -2022-04-30T09:36:06.979+0100 [ERROR] -You can set output variables as environment variables for your current terminal session using the following command: -2022-04-30T09:36:06.979+0100 [ERROR] eval $(shipyard env) - -To list output variables use the command: -2022-04-30T09:36:06.979+0100 [ERROR] -shipyard output -2022-04-30T09:36:07.064+0100 [INFO] Starting controller -2022-04-30T09:36:07.064+0100 [ERROR] Error creating TCP listener: error="listen tcp :9443: bind: address already in use" -2022-04-30T09:36:07.064+0100 [ERROR] Unable to start server: error="unable to create TCP listener: listen tcp :9443: bind: address already in use" -2022-04-30T09:36:07.517+0100 [DEBUG] kubernetes-controller.setup: setting up webhook server -2022-04-30T09:36:07.517+0100 [DEBUG] kubernetes-controller.setup: registering webhooks to the webhook server -2022-04-30T09:36:07.517+0100 [DEBUG] kubernetes-controller.controller-runtime.webhook: Registering webhook: path=/validate-v1-deployment -2022-04-30T09:36:07.517+0100 [DEBUG] kubernetes-controller.setup: Starting Kubernetes controller -2022-04-30T09:36:07.517+0100 [DEBUG] kubernetes-controller.controller-runtime.webhook.webhooks: Starting webhook server -2022-04-30T09:36:07.517+0100 [DEBUG] kubernetes-controller.controller-runtime.certwatcher: Updated current TLS certificate -2022-04-30T09:36:07.517+0100 [DEBUG] kubernetes-controller.controller-runtime.certwatcher: Starting certificate watcher -2022-04-30T09:36:07.618+0100 [DEBUG] kubernetes-controller: Stopping and waiting for non leader election runnables -2022-04-30T09:36:07.618+0100 [DEBUG] kubernetes-controller: Stopping and waiting for leader election runnables -2022-04-30T09:36:07.619+0100 [DEBUG] kubernetes-controller.controller.release: Starting EventSource: reconciler group=consul-release-controller.nicholasjackson.io reconciler kind=Release source="kind source: *v1.Release" -2022-04-30T09:36:07.619+0100 [DEBUG] kubernetes-controller.controller.release: Starting Controller: reconciler group=consul-release-controller.nicholasjackson.io reconciler kind=Release -2022-04-30T09:36:07.619+0100 [DEBUG] kubernetes-controller.controller.release: Starting workers: reconciler group=consul-release-controller.nicholasjackson.io reconciler kind=Release worker count=1 -2022-04-30T09:36:07.619+0100 [DEBUG] kubernetes-controller.controller.release: Shutdown signal received, waiting for all workers to finish: reconciler group=consul-release-controller.nicholasjackson.io reconciler kind=Release -2022-04-30T09:36:07.619+0100 [DEBUG] kubernetes-controller.controller.release: All workers finished: reconciler group=consul-release-controller.nicholasjackson.io reconciler kind=Release -2022-04-30T09:36:07.619+0100 [DEBUG] kubernetes-controller: Stopping and waiting for caches -2022-04-30T09:36:07.619+0100 [DEBUG] kubernetes-controller: Stopping and waiting for webhooks -2022-04-30T09:36:07.619+0100 [DEBUG] kubernetes-controller: Wait completed, proceeding to shutdown the manager -2022-04-30T09:36:07.619+0100 [ERROR] kubernetes-controller.setup: problem running manager: error="listen tcp :19443: bind: address already in use" -2022-04-30T09:36:12.064+0100 [INFO] Shutting down server gracefully -2022-04-30T09:36:12.064+0100 [INFO] Shutting down metrics -2022-04-30T09:36:12.064+0100 [INFO] Shutting down kubernetes controller -2022-04-30T09:36:12.064+0100 [INFO] kubernetes-controller: Stopping Kubernetes controller diff --git a/kubernetes/controller/validatingwebhook.go b/kubernetes/controller/validatingwebhook.go index a16a794..a919fa9 100644 --- a/kubernetes/controller/validatingwebhook.go +++ b/kubernetes/controller/validatingwebhook.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "net/http" + "regexp" + "strings" "github.com/hashicorp/go-hclog" "github.com/nicholasjackson/consul-release-controller/plugins/interfaces" @@ -41,7 +43,10 @@ func (a *deploymentAdmission) Handle(ctx context.Context, req admission.Request) a.log.Debug("Handle deployment admission", "deployment", deployment.Name, "namespaces", deployment.Namespace) // was the deployment modified by the release controller, if so, ignore - if deployment.Labels != nil && deployment.Labels["consul-release-controller-version"] != "" && deployment.Labels["consul-release-controller-version"] == deployment.ResourceVersion { + if deployment.Labels != nil && + deployment.Labels[interfaces.RuntimeDeploymentVersionLabel] != "" && + deployment.Labels[interfaces.RuntimeDeploymentVersionLabel] == deployment.ResourceVersion { + a.log.Debug("Ignore deployment, resource was modified by the controller", "name", deployment.Name, "namespace", deployment.Namespace, "labels", deployment.Labels) return admission.Allowed("resource modified by controller") @@ -58,7 +63,23 @@ func (a *deploymentAdmission) Handle(ctx context.Context, req admission.Request) conf := &kubernetes.PluginConfig{} json.Unmarshal(rel.Runtime.Config, conf) - if conf.Deployment == deployment.Name { + // PluginConfig.Deployment can reference deployments using regular expressions + // check if this matches + + //first check to see if the regex terminates in $ (word boundary), if not add it + if !strings.HasSuffix(conf.Deployment, "$") { + conf.Deployment = conf.Deployment + "$" + } + + re, err := regexp.Compile(conf.Deployment) + if err != nil { + a.log.Error("Invalid regular expression for deployment in release config", "release", rel.Name, "error", err) + continue + } + + a.log.Debug("Checking release", "name", deployment.Name, "namespace", deployment.Namespace, "regex", conf.Deployment) + + if re.MatchString(deployment.Name) && conf.Namespace == deployment.Namespace { // found a release for this deployment, check the state sm, err := a.provider.GetStateMachine(rel) if err != nil { @@ -66,7 +87,7 @@ func (a *deploymentAdmission) Handle(ctx context.Context, req admission.Request) return admission.Errored(500, err) } - a.log.Debug("Found existing release", "name", deployment.Name, "namespace", deployment.Namespace, "state", sm.CurrentState()) + a.log.Debug("Found existing release for", "name", deployment.Name, "namespace", deployment.Namespace, "state", sm.CurrentState()) if sm.CurrentState() == interfaces.StateIdle || sm.CurrentState() == interfaces.StateFail { // kick off a new deployment diff --git a/kubernetes/controller/validatingwebhook_test.go b/kubernetes/controller/validatingwebhook_test.go index 640ef7b..45eb27c 100644 --- a/kubernetes/controller/validatingwebhook_test.go +++ b/kubernetes/controller/validatingwebhook_test.go @@ -16,11 +16,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -func setupAdmission(t *testing.T) (*deploymentAdmission, *mocks.Mocks) { +func setupAdmission(t *testing.T, deploymentName, namespace string) (*deploymentAdmission, *mocks.Mocks) { pm, mm := mocks.BuildMocks(t) pc := &kubernetes.PluginConfig{} - pc.Deployment = "test-deployment" + pc.Deployment = deploymentName + pc.Namespace = namespace pcd, _ := json.Marshal(pc) @@ -57,6 +58,7 @@ func createAdmissionRequest(withVersionLabels bool) admission.Request { ar.AdmissionRequest.Name = "test-deployment" dep := &appsv1.Deployment{} + dep.Namespace = "default" dep.Name = "test-deployment" dep.Labels = map[string]string{"app": "test"} @@ -74,7 +76,16 @@ func createAdmissionRequest(withVersionLabels bool) admission.Request { func TestIgnoresDeploymentModifiedByControllerWhenActive(t *testing.T) { ar := createAdmissionRequest(true) - d, mm := setupAdmission(t) + d, mm := setupAdmission(t, "test-deployment", "default") + + resp := d.Handle(context.TODO(), ar) + require.True(t, resp.Allowed) + mm.StateMachineMock.AssertNotCalled(t, "Deploy") +} + +func TestDoesNothingForNewDeploymentWithNamespaceMismatch(t *testing.T) { + ar := createAdmissionRequest(false) + d, mm := setupAdmission(t, "test-deployment", "mine") resp := d.Handle(context.TODO(), ar) require.True(t, resp.Allowed) @@ -83,7 +94,28 @@ func TestIgnoresDeploymentModifiedByControllerWhenActive(t *testing.T) { func TestCallsDeployForNewDeploymentWhenIdle(t *testing.T) { ar := createAdmissionRequest(false) - d, mm := setupAdmission(t) + d, mm := setupAdmission(t, "test-deployment", "default") + + resp := d.Handle(context.TODO(), ar) + require.True(t, resp.Allowed) + mm.StateMachineMock.AssertCalled(t, "Deploy") +} + +func TestAddsRegExpWordBoundaryAndFailsMatchWhenNotPresent(t *testing.T) { + ar := createAdmissionRequest(false) + + // a regexp without a word boundary would match, check we add + // the word boundary when not present + d, mm := setupAdmission(t, "test-", "default") + + resp := d.Handle(context.TODO(), ar) + require.True(t, resp.Allowed) + mm.StateMachineMock.AssertNotCalled(t, "Deploy") +} + +func TestCallsDeployForNewDeploymentWhenIdleAndUsingRegularExpressions(t *testing.T) { + ar := createAdmissionRequest(false) + d, mm := setupAdmission(t, "test-(.*)", "default") resp := d.Handle(context.TODO(), ar) require.True(t, resp.Allowed) @@ -92,7 +124,7 @@ func TestCallsDeployForNewDeploymentWhenIdle(t *testing.T) { func TestCallsDeployForNewDeploymentWhenFailed(t *testing.T) { ar := createAdmissionRequest(false) - d, mm := setupAdmission(t) + d, mm := setupAdmission(t, "test-deployment", "default") testutils.ClearMockCall(&mm.StateMachineMock.Mock, "CurrentState") mm.StateMachineMock.On("CurrentState").Return(interfaces.StateFail) @@ -104,7 +136,7 @@ func TestCallsDeployForNewDeploymentWhenFailed(t *testing.T) { func TestReturnsAllowedWhenReleaseNotFound(t *testing.T) { ar := createAdmissionRequest(false) - d, mm := setupAdmission(t) + d, mm := setupAdmission(t, "test-deployment", "default") testutils.ClearMockCall(&mm.StoreMock.Mock, "ListReleases") mm.StoreMock.On("ListReleases", &interfaces.ListOptions{"kubernetes"}).Return( @@ -119,7 +151,7 @@ func TestReturnsAllowedWhenReleaseNotFound(t *testing.T) { func TestReturnsDeniedWhenReleaseActive(t *testing.T) { ar := createAdmissionRequest(false) - d, mm := setupAdmission(t) + d, mm := setupAdmission(t, "test-deployment", "default") testutils.ClearMockCall(&mm.StateMachineMock.Mock, "CurrentState") mm.StateMachineMock.On("CurrentState").Return(interfaces.StateMonitor) diff --git a/plugins/interfaces/runtime.go b/plugins/interfaces/runtime.go index 777c1ae..4fd3626 100644 --- a/plugins/interfaces/runtime.go +++ b/plugins/interfaces/runtime.go @@ -11,6 +11,7 @@ const ( RuntimeDeploymentNoAction RuntimeDeploymentStatus = "runtime_deployment_no_action" RuntimeDeploymentNotFound RuntimeDeploymentStatus = "runtime_deployment_not_found" RuntimeDeploymentInternalError RuntimeDeploymentStatus = "runtime_deployment_internal_error" + RuntimeDeploymentVersionLabel = "consul-release-controller-version" ) // RuntimeBaseConfig is the base configuration that all runtime plugins must implement diff --git a/plugins/kubernetes/plugin.go b/plugins/kubernetes/plugin.go index 902e936..039b8d8 100644 --- a/plugins/kubernetes/plugin.go +++ b/plugins/kubernetes/plugin.go @@ -108,6 +108,13 @@ func (p *Plugin) InitPrimary(ctx context.Context) (interfaces.RuntimeDeploymentS primaryDeployment.Name = primaryName primaryDeployment.ResourceVersion = "" + // add labels to ensure the deployment is not picked up by the validating webhook + if primaryDeployment.Labels == nil { + primaryDeployment.Labels = map[string]string{} + } + + primaryDeployment.Labels[interfaces.RuntimeDeploymentVersionLabel] = "1" + // save the new primary err = p.kubeClient.UpsertDeployment(ctx, primaryDeployment) if err != nil { @@ -163,6 +170,13 @@ func (p *Plugin) PromoteCandidate(ctx context.Context) (interfaces.RuntimeDeploy primary.Name = primaryName primary.ResourceVersion = "" + // add labels to ensure the deployment is not picked up by the validating webhook + if primary.Labels == nil { + primary.Labels = map[string]string{} + } + + primary.Labels[interfaces.RuntimeDeploymentVersionLabel] = "1" + // save the new deployment err = p.kubeClient.UpsertDeployment(ctx, primary) if err != nil { @@ -255,6 +269,9 @@ func (p *Plugin) RestoreOriginal(ctx context.Context) error { cd.Name = p.config.Deployment cd.ResourceVersion = "" + // remove the ownership label so that it can be updated as normal + delete(cd.Labels, interfaces.RuntimeDeploymentVersionLabel) + p.log.Debug("Clone primary to create original deployment", "name", p.config.Deployment, "namespace", p.config.Namespace) err = p.kubeClient.UpsertDeployment(ctx, cd) diff --git a/plugins/kubernetes/plugin_test.go b/plugins/kubernetes/plugin_test.go index 81633dc..b1dea56 100644 --- a/plugins/kubernetes/plugin_test.go +++ b/plugins/kubernetes/plugin_test.go @@ -17,7 +17,7 @@ import ( ) var one = int32(1) -var dep = &appsv1.Deployment{ +var mockDep = &appsv1.Deployment{ ObjectMeta: v1.ObjectMeta{ Name: "test-deployment", Namespace: "testnamespace", @@ -26,7 +26,7 @@ var dep = &appsv1.Deployment{ Spec: appsv1.DeploymentSpec{Replicas: &one}, } -var cloneDep = &appsv1.Deployment{ +var mockCloneDep = &appsv1.Deployment{ ObjectMeta: v1.ObjectMeta{ Name: "test-deployment-primary", Namespace: "testnamespace", @@ -35,7 +35,7 @@ var cloneDep = &appsv1.Deployment{ Spec: appsv1.DeploymentSpec{Replicas: &one}, } -func setupPlugin(t *testing.T) (*Plugin, *clients.KubernetesMock) { +func setupPlugin(t *testing.T) (*Plugin, *clients.KubernetesMock, *appsv1.Deployment, *appsv1.Deployment) { retryTimeout = 10 * time.Millisecond retryInterval = 1 * time.Millisecond @@ -52,11 +52,11 @@ func setupPlugin(t *testing.T) (*Plugin, *clients.KubernetesMock) { p.config.Deployment = "test-deployment" p.config.Namespace = "testnamespace" - return p, km + return p, km, mockDep.DeepCopy(), mockCloneDep.DeepCopy() } func TestInitPrimaryDoesNothingWhenPrimaryExists(t *testing.T) { - p, km := setupPlugin(t) + p, km, dep, cloneDep := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Return(cloneDep, nil) @@ -72,7 +72,7 @@ func TestInitPrimaryDoesNothingWhenPrimaryExists(t *testing.T) { } func TestInitPrimaryDoesNothingWhenCandidateDoesNotExist(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Return(nil, fmt.Errorf("Primary not found")) @@ -84,7 +84,7 @@ func TestInitPrimaryDoesNothingWhenCandidateDoesNotExist(t *testing.T) { } func TestInitPrimaryCreatesPrimaryWhenCandidateExists(t *testing.T) { - p, km := setupPlugin(t) + p, km, dep, cloneDep := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Once().Return(nil, fmt.Errorf("Primary not found")) @@ -97,10 +97,14 @@ func TestInitPrimaryCreatesPrimaryWhenCandidateExists(t *testing.T) { require.Equal(t, interfaces.RuntimeDeploymentUpdate, status) km.AssertCalled(t, "UpsertDeployment", mock.Anything, mock.Anything) + + // check that the runtimedeploymentversion label is added to ensure the validating webhook ignores this deployment + depArg := getUpsertDeployment(km.Mock) + require.Equal(t, depArg.Labels[interfaces.RuntimeDeploymentVersionLabel], "1") } func TestPromoteCandidateDoesNothingWhenCandidateNotExists(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetHealthyDeployment", mock.Anything, "test-deployment", "testnamespace").Return(nil, clients.ErrDeploymentNotFound) @@ -113,7 +117,7 @@ func TestPromoteCandidateDoesNothingWhenCandidateNotExists(t *testing.T) { } func TestPromoteCandidateDeletesExistingPrimaryAndUpserts(t *testing.T) { - p, km := setupPlugin(t) + p, km, dep, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetHealthyDeployment", mock.Anything, "test-deployment", "testnamespace").Once().Return(dep, nil) @@ -127,10 +131,14 @@ func TestPromoteCandidateDeletesExistingPrimaryAndUpserts(t *testing.T) { km.AssertCalled(t, "DeleteDeployment", mock.Anything, "test-deployment-primary", "testnamespace") km.AssertCalled(t, "UpsertDeployment", mock.Anything, mock.Anything) + + // check that the runtimedeploymentversion label is added to ensure the validating webhook ignores this deployment + depArg := getUpsertDeployment(km.Mock) + require.Equal(t, depArg.Labels[interfaces.RuntimeDeploymentVersionLabel], "1") } func TestRemoveCandidateDoesNothingWhenCandidateNotFound(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetDeployment", mock.Anything, "test-deployment", "testnamespace").Once().Return(nil, clients.ErrDeploymentNotFound) @@ -142,7 +150,7 @@ func TestRemoveCandidateDoesNothingWhenCandidateNotFound(t *testing.T) { } func TestRemoveCandidateScalesWhenCandidateFound(t *testing.T) { - p, km := setupPlugin(t) + p, km, dep, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetDeployment", mock.Anything, "test-deployment", "testnamespace").Once().Return(dep, nil) @@ -156,7 +164,7 @@ func TestRemoveCandidateScalesWhenCandidateFound(t *testing.T) { } func TestRestoreDoesNothingWhenNoPrimaryFound(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Once().Return(nil, clients.ErrDeploymentNotFound) @@ -166,7 +174,7 @@ func TestRestoreDoesNothingWhenNoPrimaryFound(t *testing.T) { } func TestRestoreCallsDeleteWhenPrimaryFound(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, cloneDep := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Once().Return(cloneDep, nil) @@ -176,10 +184,14 @@ func TestRestoreCallsDeleteWhenPrimaryFound(t *testing.T) { err := p.RestoreOriginal(context.Background()) require.NoError(t, err) + + // check that the labels are removed + depArg := getUpsertDeployment(km.Mock) + require.Equal(t, depArg.Labels[interfaces.RuntimeDeploymentVersionLabel], "") } func TestRestoreProceedesWhenExistingCandidateNotFound(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, cloneDep := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("GetDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Once().Return(cloneDep, nil) @@ -192,7 +204,7 @@ func TestRestoreProceedesWhenExistingCandidateNotFound(t *testing.T) { } func TestRemovePrimaryCallsDelete(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("DeleteDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Once().Return(nil) @@ -202,7 +214,7 @@ func TestRemovePrimaryCallsDelete(t *testing.T) { } func TestRemovePrimaryReturnsErrorWhenDeleteIsGenericError(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("DeleteDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Once().Return(fmt.Errorf("test")) @@ -212,7 +224,7 @@ func TestRemovePrimaryReturnsErrorWhenDeleteIsGenericError(t *testing.T) { } func TestRemovePrimaryReturnsNoErrorWhenDeleteIsNotFoundError(t *testing.T) { - p, km := setupPlugin(t) + p, km, _, _ := setupPlugin(t) testutils.ClearMockCall(&km.Mock, "GetDeployment") km.On("DeleteDeployment", mock.Anything, "test-deployment-primary", "testnamespace").Once().Return(clients.ErrDeploymentNotFound) @@ -220,3 +232,15 @@ func TestRemovePrimaryReturnsNoErrorWhenDeleteIsNotFoundError(t *testing.T) { err := p.RemovePrimary(context.Background()) require.NoError(t, err) } + +func getUpsertDeployment(mock mock.Mock) *appsv1.Deployment { + for _, c := range mock.Calls { + if c.Method == "UpsertDeployment" { + if dep, ok := c.Arguments.Get(1).(*appsv1.Deployment); ok { + return dep + } + } + } + + return nil +} diff --git a/plugins/statemachine/statemachine.go b/plugins/statemachine/statemachine.go index fffed4b..3fa88d2 100644 --- a/plugins/statemachine/statemachine.go +++ b/plugins/statemachine/statemachine.go @@ -145,6 +145,7 @@ func New(r *models.Release, pluginProvider interfaces.Provider) (*StateMachine, interfaces.StateDestroy, }, Dst: interfaces.StateFail}, {Name: interfaces.EventDestroy, Src: []string{ + interfaces.StateFail, interfaces.StateIdle, interfaces.StateDeploy, interfaces.StateMonitor,