From 9c63a97db75b0f8a5c5929f39978256fa290cb8a Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Mon, 29 Jan 2024 16:15:07 +0000 Subject: [PATCH 1/6] Added warning when trying to deploy bundle with running resources --- bundle/deploy/check_running_resources.go | 151 ++++++++++++++++++ bundle/deploy/check_running_resources_test.go | 125 +++++++++++++++ bundle/phases/deploy.go | 2 + 3 files changed, 278 insertions(+) create mode 100644 bundle/deploy/check_running_resources.go create mode 100644 bundle/deploy/check_running_resources_test.go diff --git a/bundle/deploy/check_running_resources.go b/bundle/deploy/check_running_resources.go new file mode 100644 index 0000000000..a74d358553 --- /dev/null +++ b/bundle/deploy/check_running_resources.go @@ -0,0 +1,151 @@ +package deploy + +import ( + "context" + "errors" + "fmt" + "strconv" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/databricks/databricks-sdk-go/service/pipelines" + "github.com/hashicorp/terraform-exec/tfexec" + tfjson "github.com/hashicorp/terraform-json" + "golang.org/x/sync/errgroup" +) + +type ErrResourceIsRunning struct{} + +func (ErrResourceIsRunning) Error() string { + return "resource is running" +} + +type checkRunningResources struct { +} + +func (l *checkRunningResources) Name() string { + return "check-running-resources" +} + +func (l *checkRunningResources) Apply(ctx context.Context, b *bundle.Bundle) error { + + tf := b.Terraform + if tf == nil { + return fmt.Errorf("terraform not initialized") + } + + err := tf.Init(ctx, tfexec.Upgrade(true)) + if err != nil { + return fmt.Errorf("terraform init: %w", err) + } + + state, err := b.Terraform.Show(ctx) + if err != nil { + return err + } + + isRunning := isAnyResourceRunning(ctx, b.WorkspaceClient(), state) + if !isRunning { + return nil + } + + ans, err := cmdio.AskYesOrNo(ctx, `Some of the bundle resources are still running. +Deploying the bundle can disrupt any jobs or pipelines in progress. +Do you want to continue?`) + + if err != nil { + return err + } + if !ans { + return errors.New("deployment aborted") + } + + return nil +} + +func CheckRunningResource() *checkRunningResources { + return &checkRunningResources{} +} + +func isAnyResourceRunning(ctx context.Context, w *databricks.WorkspaceClient, state *tfjson.State) bool { + if state.Values == nil || state.Values.RootModule == nil { + return false + } + + errs, errCtx := errgroup.WithContext(ctx) + + for _, resource := range state.Values.RootModule.Resources { + // Limit to resources. + if resource.Mode != tfjson.ManagedResourceMode { + continue + } + + value, ok := resource.AttributeValues["id"] + if !ok { + continue + } + id, ok := value.(string) + if !ok { + continue + } + + switch resource.Type { + case "databricks_job": + errs.Go(func() error { + isRunning, err := IsJobRunning(errCtx, w, id) + // If there's an error retrieving the job, we assume it's not running + if err != nil { + return err + } + if isRunning { + return &ErrResourceIsRunning{} + } + return nil + }) + case "databricks_pipeline": + errs.Go(func() error { + isRunning, err := IsPipelineRunning(errCtx, w, id) + // If there's an error retrieving the pipeline, we assume it's not running + if err != nil { + return nil + } + if isRunning { + return &ErrResourceIsRunning{} + } + return nil + }) + } + } + + err := errs.Wait() + return err != nil +} + +func IsJobRunning(ctx context.Context, w *databricks.WorkspaceClient, jobId string) (bool, error) { + id, err := strconv.Atoi(jobId) + if err != nil { + return false, err + } + + runs, err := w.Jobs.ListRunsAll(ctx, jobs.ListRunsRequest{JobId: int64(id), ActiveOnly: true}) + if err != nil { + return false, err + } + + return len(runs) > 0, nil +} + +func IsPipelineRunning(ctx context.Context, w *databricks.WorkspaceClient, pipelineId string) (bool, error) { + resp, err := w.Pipelines.Get(ctx, pipelines.GetPipelineRequest{PipelineId: pipelineId}) + if err != nil { + return false, err + } + switch resp.State { + case pipelines.PipelineStateIdle, pipelines.PipelineStateFailed, pipelines.PipelineStateDeleted: + return false, nil + default: + return true, nil + } +} diff --git a/bundle/deploy/check_running_resources_test.go b/bundle/deploy/check_running_resources_test.go new file mode 100644 index 0000000000..77311f7ef8 --- /dev/null +++ b/bundle/deploy/check_running_resources_test.go @@ -0,0 +1,125 @@ +package deploy + +import ( + "context" + "errors" + "testing" + + "github.com/databricks/databricks-sdk-go/experimental/mocks" + "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/databricks/databricks-sdk-go/service/pipelines" + tfjson "github.com/hashicorp/terraform-json" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestIsAnyResourceRunningWithEmptyState(t *testing.T) { + mock := mocks.NewMockWorkspaceClient(t) + state := &tfjson.State{} + isRunning := isAnyResourceRunning(context.Background(), mock.WorkspaceClient, state) + require.False(t, isRunning) +} + +func TestIsAnyResourceRunningWithJob(t *testing.T) { + m := mocks.NewMockWorkspaceClient(t) + state := &tfjson.State{ + Values: &tfjson.StateValues{ + RootModule: &tfjson.StateModule{ + Resources: []*tfjson.StateResource{ + { + Type: "databricks_job", + AttributeValues: map[string]interface{}{ + "id": "123", + }, + Mode: tfjson.ManagedResourceMode, + }, + }, + }, + }, + } + + jobsApi := m.GetMockJobsAPI() + jobsApi.EXPECT().ListRunsAll(mock.Anything, jobs.ListRunsRequest{ + JobId: 123, + ActiveOnly: true, + }).Return([]jobs.BaseRun{ + {RunId: 1234}, + }, nil).Once() + + isRunning := isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.True(t, isRunning) + + jobsApi.EXPECT().ListRunsAll(mock.Anything, jobs.ListRunsRequest{ + JobId: 123, + ActiveOnly: true, + }).Return([]jobs.BaseRun{}, nil).Once() + + isRunning = isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.False(t, isRunning) +} + +func TestIsAnyResourceRunningWithPipeline(t *testing.T) { + m := mocks.NewMockWorkspaceClient(t) + state := &tfjson.State{ + Values: &tfjson.StateValues{ + RootModule: &tfjson.StateModule{ + Resources: []*tfjson.StateResource{ + { + Type: "databricks_pipeline", + AttributeValues: map[string]interface{}{ + "id": "123", + }, + Mode: tfjson.ManagedResourceMode, + }, + }, + }, + }, + } + + pipelineApi := m.GetMockPipelinesAPI() + pipelineApi.EXPECT().Get(mock.Anything, pipelines.GetPipelineRequest{ + PipelineId: "123", + }).Return(&pipelines.GetPipelineResponse{ + PipelineId: "123", + State: pipelines.PipelineStateRunning, + }, nil).Once() + + isRunning := isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.True(t, isRunning) + + pipelineApi.EXPECT().Get(mock.Anything, pipelines.GetPipelineRequest{ + PipelineId: "123", + }).Return(&pipelines.GetPipelineResponse{ + PipelineId: "123", + State: pipelines.PipelineStateIdle, + }, nil).Once() + isRunning = isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.False(t, isRunning) +} + +func TestIsAnyResourceRunningWithAPIFailure(t *testing.T) { + m := mocks.NewMockWorkspaceClient(t) + state := &tfjson.State{ + Values: &tfjson.StateValues{ + RootModule: &tfjson.StateModule{ + Resources: []*tfjson.StateResource{ + { + Type: "databricks_pipeline", + AttributeValues: map[string]interface{}{ + "id": "123", + }, + Mode: tfjson.ManagedResourceMode, + }, + }, + }, + }, + } + + pipelineApi := m.GetMockPipelinesAPI() + pipelineApi.EXPECT().Get(mock.Anything, pipelines.GetPipelineRequest{ + PipelineId: "123", + }).Return(nil, errors.New("API failure")).Once() + + isRunning := isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.False(t, isRunning) +} diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 20fe2e4135..7cf61263a6 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -5,6 +5,7 @@ import ( "github.com/databricks/cli/bundle/artifacts" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator" + "github.com/databricks/cli/bundle/deploy" "github.com/databricks/cli/bundle/deploy/files" "github.com/databricks/cli/bundle/deploy/lock" "github.com/databricks/cli/bundle/deploy/metadata" @@ -22,6 +23,7 @@ func Deploy() bundle.Mutator { lock.Acquire(), bundle.Defer( bundle.Seq( + deploy.CheckRunningResource(), mutator.ValidateGitDetails(), libraries.MatchWithArtifacts(), artifacts.CleanUp(), From d9a289b9a8bfd3d18075e7cb476160b72a5bdc59 Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Wed, 31 Jan 2024 10:17:02 +0000 Subject: [PATCH 2/6] add --fail-if-running flag --- bundle/config/bundle.go | 4 ++++ bundle/deploy/check_running_resources.go | 3 +++ cmd/bundle/deploy.go | 3 +++ 3 files changed, 10 insertions(+) diff --git a/bundle/config/bundle.go b/bundle/config/bundle.go index 933e88bfad..94b28edfd4 100644 --- a/bundle/config/bundle.go +++ b/bundle/config/bundle.go @@ -43,4 +43,8 @@ type Bundle struct { // Overrides the compute used for jobs and other supported assets. ComputeID string `json:"compute_id,omitempty"` + + // FailIfRunning specifies whether to fail the deployment if there are + // running jobs or pipelines in the workspace. Defaults to false. + FailIfRunning bool `json:"fail_if_running,omitempty"` } diff --git a/bundle/deploy/check_running_resources.go b/bundle/deploy/check_running_resources.go index a74d358553..a00bee8f42 100644 --- a/bundle/deploy/check_running_resources.go +++ b/bundle/deploy/check_running_resources.go @@ -30,6 +30,9 @@ func (l *checkRunningResources) Name() string { } func (l *checkRunningResources) Apply(ctx context.Context, b *bundle.Bundle) error { + if !b.Config.Bundle.FailIfRunning { + return nil + } tf := b.Terraform if tf == nil { diff --git a/cmd/bundle/deploy.go b/cmd/bundle/deploy.go index 8818bbbf4d..b5ad92bd57 100644 --- a/cmd/bundle/deploy.go +++ b/cmd/bundle/deploy.go @@ -15,9 +15,11 @@ func newDeployCommand() *cobra.Command { var force bool var forceLock bool + var failIfRunning bool var computeID string cmd.Flags().BoolVar(&force, "force", false, "Force-override Git branch validation.") cmd.Flags().BoolVar(&forceLock, "force-lock", false, "Force acquisition of deployment lock.") + cmd.Flags().BoolVar(&failIfRunning, "fail-if-running", false, "Fail if there are running jobs or pipelines in the deployment.") cmd.Flags().StringVarP(&computeID, "compute-id", "c", "", "Override compute in the deployment with the given compute ID.") cmd.RunE = func(cmd *cobra.Command, args []string) error { @@ -25,6 +27,7 @@ func newDeployCommand() *cobra.Command { b.Config.Bundle.Force = force b.Config.Bundle.Lock.Force = forceLock + b.Config.Bundle.FailIfRunning = failIfRunning b.Config.Bundle.ComputeID = computeID return bundle.Apply(cmd.Context(), b, bundle.Seq( From 2f1f6bf131f2b0c9ac10d354ed3943182bcc1c65 Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Wed, 31 Jan 2024 10:21:01 +0000 Subject: [PATCH 3/6] do not ask for input --- bundle/deploy/check_running_resources.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/bundle/deploy/check_running_resources.go b/bundle/deploy/check_running_resources.go index a00bee8f42..02e45a2f23 100644 --- a/bundle/deploy/check_running_resources.go +++ b/bundle/deploy/check_running_resources.go @@ -7,7 +7,6 @@ import ( "strconv" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/libs/cmdio" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" @@ -50,19 +49,8 @@ func (l *checkRunningResources) Apply(ctx context.Context, b *bundle.Bundle) err } isRunning := isAnyResourceRunning(ctx, b.WorkspaceClient(), state) - if !isRunning { - return nil - } - - ans, err := cmdio.AskYesOrNo(ctx, `Some of the bundle resources are still running. -Deploying the bundle can disrupt any jobs or pipelines in progress. -Do you want to continue?`) - - if err != nil { - return err - } - if !ans { - return errors.New("deployment aborted") + if isRunning { + return errors.New("some of the bundle resources are still running, deployment aborted") } return nil From c72662fe8ce6704526ae9b2abbc36f160f52db3b Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Wed, 31 Jan 2024 12:17:47 +0000 Subject: [PATCH 4/6] move state pull earlier in chain --- bundle/phases/deploy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 7cf61263a6..5c65755091 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -23,6 +23,7 @@ func Deploy() bundle.Mutator { lock.Acquire(), bundle.Defer( bundle.Seq( + terraform.StatePull(), deploy.CheckRunningResource(), mutator.ValidateGitDetails(), libraries.MatchWithArtifacts(), @@ -33,7 +34,6 @@ func Deploy() bundle.Mutator { permissions.ApplyWorkspaceRootPermissions(), terraform.Interpolate(), terraform.Write(), - terraform.StatePull(), bundle.Defer( terraform.Apply(), bundle.Seq( From fdcf66d4163b5afcc7646c358b2cfb8cf1be2e5b Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Thu, 1 Feb 2024 10:48:07 +0000 Subject: [PATCH 5/6] return error --- bundle/deploy/check_running_resources.go | 27 ++++++++++--------- bundle/deploy/check_running_resources_test.go | 24 ++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/bundle/deploy/check_running_resources.go b/bundle/deploy/check_running_resources.go index 02e45a2f23..c81b8ac091 100644 --- a/bundle/deploy/check_running_resources.go +++ b/bundle/deploy/check_running_resources.go @@ -2,7 +2,6 @@ package deploy import ( "context" - "errors" "fmt" "strconv" @@ -15,10 +14,13 @@ import ( "golang.org/x/sync/errgroup" ) -type ErrResourceIsRunning struct{} +type ErrResourceIsRunning struct { + resourceType string + resourceId string +} -func (ErrResourceIsRunning) Error() string { - return "resource is running" +func (e ErrResourceIsRunning) Error() string { + return fmt.Sprintf("%s %s is running", e.resourceType, e.resourceId) } type checkRunningResources struct { @@ -48,9 +50,9 @@ func (l *checkRunningResources) Apply(ctx context.Context, b *bundle.Bundle) err return err } - isRunning := isAnyResourceRunning(ctx, b.WorkspaceClient(), state) - if isRunning { - return errors.New("some of the bundle resources are still running, deployment aborted") + err = checkAnyResourceRunning(ctx, b.WorkspaceClient(), state) + if err != nil { + return fmt.Errorf("deployment aborted, err: %w", err) } return nil @@ -60,9 +62,9 @@ func CheckRunningResource() *checkRunningResources { return &checkRunningResources{} } -func isAnyResourceRunning(ctx context.Context, w *databricks.WorkspaceClient, state *tfjson.State) bool { +func checkAnyResourceRunning(ctx context.Context, w *databricks.WorkspaceClient, state *tfjson.State) error { if state.Values == nil || state.Values.RootModule == nil { - return false + return nil } errs, errCtx := errgroup.WithContext(ctx) @@ -91,7 +93,7 @@ func isAnyResourceRunning(ctx context.Context, w *databricks.WorkspaceClient, st return err } if isRunning { - return &ErrResourceIsRunning{} + return &ErrResourceIsRunning{resourceType: "job", resourceId: id} } return nil }) @@ -103,15 +105,14 @@ func isAnyResourceRunning(ctx context.Context, w *databricks.WorkspaceClient, st return nil } if isRunning { - return &ErrResourceIsRunning{} + return &ErrResourceIsRunning{resourceType: "pipeline", resourceId: id} } return nil }) } } - err := errs.Wait() - return err != nil + return errs.Wait() } func IsJobRunning(ctx context.Context, w *databricks.WorkspaceClient, jobId string) (bool, error) { diff --git a/bundle/deploy/check_running_resources_test.go b/bundle/deploy/check_running_resources_test.go index 77311f7ef8..893468f4cb 100644 --- a/bundle/deploy/check_running_resources_test.go +++ b/bundle/deploy/check_running_resources_test.go @@ -16,8 +16,8 @@ import ( func TestIsAnyResourceRunningWithEmptyState(t *testing.T) { mock := mocks.NewMockWorkspaceClient(t) state := &tfjson.State{} - isRunning := isAnyResourceRunning(context.Background(), mock.WorkspaceClient, state) - require.False(t, isRunning) + err := checkAnyResourceRunning(context.Background(), mock.WorkspaceClient, state) + require.NoError(t, err) } func TestIsAnyResourceRunningWithJob(t *testing.T) { @@ -46,16 +46,16 @@ func TestIsAnyResourceRunningWithJob(t *testing.T) { {RunId: 1234}, }, nil).Once() - isRunning := isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) - require.True(t, isRunning) + err := checkAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.Error(t, err) jobsApi.EXPECT().ListRunsAll(mock.Anything, jobs.ListRunsRequest{ JobId: 123, ActiveOnly: true, }).Return([]jobs.BaseRun{}, nil).Once() - isRunning = isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) - require.False(t, isRunning) + err = checkAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.NoError(t, err) } func TestIsAnyResourceRunningWithPipeline(t *testing.T) { @@ -84,8 +84,8 @@ func TestIsAnyResourceRunningWithPipeline(t *testing.T) { State: pipelines.PipelineStateRunning, }, nil).Once() - isRunning := isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) - require.True(t, isRunning) + err := checkAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.Error(t, err) pipelineApi.EXPECT().Get(mock.Anything, pipelines.GetPipelineRequest{ PipelineId: "123", @@ -93,8 +93,8 @@ func TestIsAnyResourceRunningWithPipeline(t *testing.T) { PipelineId: "123", State: pipelines.PipelineStateIdle, }, nil).Once() - isRunning = isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) - require.False(t, isRunning) + err = checkAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.NoError(t, err) } func TestIsAnyResourceRunningWithAPIFailure(t *testing.T) { @@ -120,6 +120,6 @@ func TestIsAnyResourceRunningWithAPIFailure(t *testing.T) { PipelineId: "123", }).Return(nil, errors.New("API failure")).Once() - isRunning := isAnyResourceRunning(context.Background(), m.WorkspaceClient, state) - require.False(t, isRunning) + err := checkAnyResourceRunning(context.Background(), m.WorkspaceClient, state) + require.NoError(t, err) } From 30c31763157c0409f378311c75628fafcb379a5e Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Thu, 1 Feb 2024 14:18:41 +0000 Subject: [PATCH 6/6] fixes --- bundle/config/bundle.go | 8 ++------ bundle/config/deployment.go | 10 ++++++++++ bundle/deploy/check_running_resources.go | 2 +- bundle/deploy/check_running_resources_test.go | 4 ++-- bundle/deploy/lock/acquire.go | 4 ++-- bundle/deploy/lock/release.go | 2 +- cmd/bundle/deploy.go | 11 +++++++---- cmd/bundle/destroy.go | 2 +- 8 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 bundle/config/deployment.go diff --git a/bundle/config/bundle.go b/bundle/config/bundle.go index 94b28edfd4..21278151f9 100644 --- a/bundle/config/bundle.go +++ b/bundle/config/bundle.go @@ -25,9 +25,6 @@ type Bundle struct { // For example, where to find the binary, which version to use, etc. Terraform *Terraform `json:"terraform,omitempty" bundle:"readonly"` - // Lock configures locking behavior on deployment. - Lock Lock `json:"lock" bundle:"readonly"` - // Force-override Git branch validation. Force bool `json:"force,omitempty" bundle:"readonly"` @@ -44,7 +41,6 @@ type Bundle struct { // Overrides the compute used for jobs and other supported assets. ComputeID string `json:"compute_id,omitempty"` - // FailIfRunning specifies whether to fail the deployment if there are - // running jobs or pipelines in the workspace. Defaults to false. - FailIfRunning bool `json:"fail_if_running,omitempty"` + // Deployment section specifies deployment related configuration for bundle + Deployment Deployment `json:"deployment"` } diff --git a/bundle/config/deployment.go b/bundle/config/deployment.go new file mode 100644 index 0000000000..f89c7b3eee --- /dev/null +++ b/bundle/config/deployment.go @@ -0,0 +1,10 @@ +package config + +type Deployment struct { + // FailOnActiveRuns specifies whether to fail the deployment if there are + // running jobs or pipelines in the workspace. Defaults to false. + FailOnActiveRuns bool `json:"fail_on_active_runs,omitempty"` + + // Lock configures locking behavior on deployment. + Lock Lock `json:"lock" bundle:"readonly"` +} diff --git a/bundle/deploy/check_running_resources.go b/bundle/deploy/check_running_resources.go index c81b8ac091..deb7775c6b 100644 --- a/bundle/deploy/check_running_resources.go +++ b/bundle/deploy/check_running_resources.go @@ -31,7 +31,7 @@ func (l *checkRunningResources) Name() string { } func (l *checkRunningResources) Apply(ctx context.Context, b *bundle.Bundle) error { - if !b.Config.Bundle.FailIfRunning { + if !b.Config.Bundle.Deployment.FailOnActiveRuns { return nil } diff --git a/bundle/deploy/check_running_resources_test.go b/bundle/deploy/check_running_resources_test.go index 893468f4cb..7dc1fb8657 100644 --- a/bundle/deploy/check_running_resources_test.go +++ b/bundle/deploy/check_running_resources_test.go @@ -47,7 +47,7 @@ func TestIsAnyResourceRunningWithJob(t *testing.T) { }, nil).Once() err := checkAnyResourceRunning(context.Background(), m.WorkspaceClient, state) - require.Error(t, err) + require.ErrorContains(t, err, "job 123 is running") jobsApi.EXPECT().ListRunsAll(mock.Anything, jobs.ListRunsRequest{ JobId: 123, @@ -85,7 +85,7 @@ func TestIsAnyResourceRunningWithPipeline(t *testing.T) { }, nil).Once() err := checkAnyResourceRunning(context.Background(), m.WorkspaceClient, state) - require.Error(t, err) + require.ErrorContains(t, err, "pipeline 123 is running") pipelineApi.EXPECT().Get(mock.Anything, pipelines.GetPipelineRequest{ PipelineId: "123", diff --git a/bundle/deploy/lock/acquire.go b/bundle/deploy/lock/acquire.go index 1335f7800c..69e6663fc7 100644 --- a/bundle/deploy/lock/acquire.go +++ b/bundle/deploy/lock/acquire.go @@ -35,7 +35,7 @@ func (m *acquire) init(b *bundle.Bundle) error { func (m *acquire) Apply(ctx context.Context, b *bundle.Bundle) error { // Return early if locking is disabled. - if !b.Config.Bundle.Lock.IsEnabled() { + if !b.Config.Bundle.Deployment.Lock.IsEnabled() { log.Infof(ctx, "Skipping; locking is disabled") return nil } @@ -45,7 +45,7 @@ func (m *acquire) Apply(ctx context.Context, b *bundle.Bundle) error { return err } - force := b.Config.Bundle.Lock.Force + force := b.Config.Bundle.Deployment.Lock.Force log.Infof(ctx, "Acquiring deployment lock (force: %v)", force) err = b.Locker.Lock(ctx, force) if err != nil { diff --git a/bundle/deploy/lock/release.go b/bundle/deploy/lock/release.go index 52d2719433..68d4e0f93b 100644 --- a/bundle/deploy/lock/release.go +++ b/bundle/deploy/lock/release.go @@ -30,7 +30,7 @@ func (m *release) Name() string { func (m *release) Apply(ctx context.Context, b *bundle.Bundle) error { // Return early if locking is disabled. - if !b.Config.Bundle.Lock.IsEnabled() { + if !b.Config.Bundle.Deployment.Lock.IsEnabled() { log.Infof(ctx, "Skipping; locking is disabled") return nil } diff --git a/cmd/bundle/deploy.go b/cmd/bundle/deploy.go index b5ad92bd57..a83c268bc9 100644 --- a/cmd/bundle/deploy.go +++ b/cmd/bundle/deploy.go @@ -15,21 +15,24 @@ func newDeployCommand() *cobra.Command { var force bool var forceLock bool - var failIfRunning bool + var failOnActiveRuns bool var computeID string cmd.Flags().BoolVar(&force, "force", false, "Force-override Git branch validation.") cmd.Flags().BoolVar(&forceLock, "force-lock", false, "Force acquisition of deployment lock.") - cmd.Flags().BoolVar(&failIfRunning, "fail-if-running", false, "Fail if there are running jobs or pipelines in the deployment.") + cmd.Flags().BoolVar(&failOnActiveRuns, "fail-on-active-runs", false, "Fail if there are running jobs or pipelines in the deployment.") cmd.Flags().StringVarP(&computeID, "compute-id", "c", "", "Override compute in the deployment with the given compute ID.") cmd.RunE = func(cmd *cobra.Command, args []string) error { b := bundle.Get(cmd.Context()) b.Config.Bundle.Force = force - b.Config.Bundle.Lock.Force = forceLock - b.Config.Bundle.FailIfRunning = failIfRunning + b.Config.Bundle.Deployment.Lock.Force = forceLock b.Config.Bundle.ComputeID = computeID + if cmd.Flag("fail-on-active-runs").Changed { + b.Config.Bundle.Deployment.FailOnActiveRuns = failOnActiveRuns + } + return bundle.Apply(cmd.Context(), b, bundle.Seq( phases.Initialize(), phases.Build(), diff --git a/cmd/bundle/destroy.go b/cmd/bundle/destroy.go index 22d998abe2..dad199bf91 100644 --- a/cmd/bundle/destroy.go +++ b/cmd/bundle/destroy.go @@ -30,7 +30,7 @@ func newDestroyCommand() *cobra.Command { b := bundle.Get(ctx) // If `--force-lock` is specified, force acquisition of the deployment lock. - b.Config.Bundle.Lock.Force = forceDestroy + b.Config.Bundle.Deployment.Lock.Force = forceDestroy // If `--auto-approve`` is specified, we skip confirmation checks b.AutoApprove = autoApprove