From bd284eb76d9a3c985e0d9905c6662a97d5fae654 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 May 2023 01:14:32 +0000 Subject: [PATCH 1/7] chore(deps): update dependency open-policy-agent/conftest to v0.42.1 in testing/dockerfile (#3392) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- testing/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/Dockerfile b/testing/Dockerfile index 7636e4738a..fd4c6c1a79 100644 --- a/testing/Dockerfile +++ b/testing/Dockerfile @@ -16,7 +16,7 @@ RUN case $(uname -m) in x86_64|amd64) ARCH="amd64" ;; aarch64|arm64|armv7l) ARCH # Install conftest # renovate: datasource=github-releases depName=open-policy-agent/conftest -ENV CONFTEST_VERSION=0.42.0 +ENV CONFTEST_VERSION=0.42.1 SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN case $(uname -m) in x86_64|amd64) ARCH="x86_64" ;; aarch64|arm64|armv7l) ARCH="arm64" ;; esac && \ curl -LOs https://github.com/open-policy-agent/conftest/releases/download/v${CONFTEST_VERSION}/conftest_${CONFTEST_VERSION}_Linux_${ARCH}.tar.gz && \ From aedea76bff83cfca9ae1144278f35998093f445a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 18:26:04 -0500 Subject: [PATCH 2/7] build(deps): bump github.com/cloudflare/circl in /e2e (#3400) Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.1.0 to 1.3.3. - [Release notes](https://github.com/cloudflare/circl/releases) - [Commits](https://github.com/cloudflare/circl/compare/v1.1.0...v1.3.3) --- updated-dependencies: - dependency-name: github.com/cloudflare/circl dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- e2e/go.mod | 2 +- e2e/go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/e2e/go.mod b/e2e/go.mod index e204b4e8d5..bce52f4385 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -9,7 +9,7 @@ require ( require ( github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect - github.com/cloudflare/circl v1.1.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/e2e/go.sum b/e2e/go.sum index cc799adfd3..8cc48c4fc2 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -1,8 +1,9 @@ github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= From b3782ee62cf48bf4cb9e77bfc7b561cbe0a88bc4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 23:59:45 +0000 Subject: [PATCH 3/7] chore(deps): update module github.com/cloudflare/circl to v1.3.3 [security] (#3403) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 63e38b6ff7..4bd866d845 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cloudflare/circl v1.1.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fatih/color v1.13.0 // indirect diff --git a/go.sum b/go.sum index 2c88dd8d53..0372fdf171 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,9 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= From 3c27fbbfc03edd2bc01183a87869c8d36723c718 Mon Sep 17 00:00:00 2001 From: Dylan Page Date: Thu, 11 May 2023 22:34:14 -0400 Subject: [PATCH 4/7] fix(ci): only push docker dev tag on main (#3393) - Disables pushing a dev tag within an unmerged pr commit event. - Also disabled pushing at all unless the event comes from the main branch --- .github/workflows/atlantis-image.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/atlantis-image.yml b/.github/workflows/atlantis-image.yml index 12935fee61..c9c376a468 100644 --- a/.github/workflows/atlantis-image.yml +++ b/.github/workflows/atlantis-image.yml @@ -31,8 +31,8 @@ jobs: env: # Set docker repo to either the fork or the main repo where the branch exists DOCKER_REPO: ghcr.io/${{ github.repository }} - # Push if not a pull request or this is a fork - PUSH: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} + # Push if not a pull request and references the main branch + PUSH: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }} steps: - uses: actions/checkout@v3 @@ -75,9 +75,9 @@ jobs: type=semver,pattern={{version}},prefix=v,enable=${{ matrix.image_type == 'alpine' }} type=semver,pattern={{major}}.{{minor}},prefix=v,suffix=${{ env.SUFFIX }} # dev - type=raw,value=dev,suffix=${{ env.SUFFIX }}-{{ sha }} - type=raw,event=push,value=dev,enable=${{ github.ref == format('refs/heads/{0}', 'main') && matrix.image_type == 'alpine' }},suffix= type=raw,event=push,value=dev,enable={{is_default_branch}},suffix=${{ env.SUFFIX }} + type=raw,event=push,value=dev,enable={{is_default_branch}},suffix=${{ env.SUFFIX }}-{{ sha }} + type=raw,event=push,value=dev,enable=${{ github.ref == format('refs/heads/{0}', 'main') && matrix.image_type == 'alpine' }},suffix= # prerelease type=raw,event=tag,value=prerelease-latest,enable=${{ startsWith(github.ref, 'refs/tags/') && contains(github.ref, 'pre') && matrix.image_type == 'alpine' }},suffix= type=raw,event=tag,value=prerelease-latest,enable=${{ startsWith(github.ref, 'refs/tags/') && contains(github.ref, 'pre') }},suffix=${{ env.SUFFIX }} From 6520d71255ce67ee19e7cc57f78a3e4a2c6a68f6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 22:25:19 -0500 Subject: [PATCH 5/7] chore(deps): update alpine docker tag to v3.18.0 in dockerfile (#3404) * chore(deps): update alpine docker tag to v3.18.0 in dockerfile * Update Dockerfile --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2ee399fb00..f9fd6cac1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 # what distro is the image being built for -ARG ALPINE_TAG=3.17.3 +ARG ALPINE_TAG=3.18.0 ARG DEBIAN_TAG=11.7-slim # Stage 1: build artifact and download deps @@ -178,8 +178,8 @@ RUN apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/ma curl~=8.0 \ unzip~=6.0 \ bash~=5.2 \ - openssh~=9.1_p1 \ - libcap~=2.66 \ + openssh~=9.3_p1 \ + libcap~=2.68 \ dumb-init~=1.2 \ gcompat~=1.1 From 7a4e9474d5a3fed9cc8b1ef19a7544b65e9d0236 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 22:27:08 -0500 Subject: [PATCH 6/7] build(deps): bump yaml from 2.2.1 to 2.2.2 (#3352) Bumps [yaml](https://github.com/eemeli/yaml) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/eemeli/yaml/releases) - [Commits](https://github.com/eemeli/yaml/compare/v2.2.1...v2.2.2) --- updated-dependencies: - dependency-name: yaml dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5138e40c71..6052a95080 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1734,6 +1734,6 @@ which@^2.0.1: isexe "^2.0.0" yaml@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.1.tgz#3014bf0482dcd15147aa8e56109ce8632cd60ce4" - integrity sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw== + version "2.2.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" + integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== From 4e867fc36e24af1122cdd6be858a3464bc22edfa Mon Sep 17 00:00:00 2001 From: oysteingraendsen <75323242+oysteingraendsen@users.noreply.github.com> Date: Fri, 12 May 2023 19:24:33 +0200 Subject: [PATCH 7/7] feat: when using order group, abort plan/apply if any fail (#3323) * feat: when using order group, abort plan/apply if any fail * feat: add 'abort_on_execution_order_fail' flag on repo level * feat: use runProjectCmdsParallelGroups in version_command_runner * chore: add plan tests --------- Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- .../docs/repo-level-atlantis-yaml.md | 8 +- server/core/config/raw/repo_cfg.go | 10 + server/core/config/raw/repo_cfg_test.go | 47 ++- server/core/config/valid/repo_cfg.go | 1 + server/events/apply_command_runner.go | 2 +- server/events/apply_command_runner_test.go | 293 ++++++++++++++ server/events/command/project_context.go | 2 + server/events/plan_command_runner.go | 4 +- server/events/plan_command_runner_test.go | 359 ++++++++++++++++++ server/events/project_command_builder.go | 10 + .../events/project_command_context_builder.go | 18 +- .../project_command_context_builder_test.go | 26 +- .../events/project_command_pool_executor.go | 5 + server/events/version_command_runner.go | 2 +- 14 files changed, 753 insertions(+), 34 deletions(-) diff --git a/runatlantis.io/docs/repo-level-atlantis-yaml.md b/runatlantis.io/docs/repo-level-atlantis-yaml.md index 2a0d079392..d291622790 100644 --- a/runatlantis.io/docs/repo-level-atlantis-yaml.md +++ b/runatlantis.io/docs/repo-level-atlantis-yaml.md @@ -50,6 +50,7 @@ automerge: true delete_source_branch_on_merge: true parallel_plan: true parallel_apply: true +abort_on_execution_order_fail: true projects: - name: my-project-name branch: /main/ @@ -64,6 +65,7 @@ projects: plan_requirements: [mergeable, approved, undiverged] apply_requirements: [mergeable, approved, undiverged] import_requirements: [mergeable, approved, undiverged] + execution_order_group: 1 workflow: myworkflow workflows: myworkflow: @@ -259,6 +261,7 @@ to be allowed to set this key. See [Server-Side Repo Config Use Cases](server-si ### Order of planning/applying ```yaml version: 3 +abort_on_execution_order_fail: true projects: - dir: project1 execution_order_group: 2 @@ -268,7 +271,10 @@ projects: With this config above, Atlantis runs planning/applying for project2 first, then for project1. Several projects can have same `execution_order_group`. Any order in one group isn't guaranteed. `parallel_plan` and `parallel_apply` respect these order groups, so parallel planning/applying works -in each group one by one. +in each group one by one. + +If any plan/apply fails and `abort_on_execution_order_fail` is set to true on a repo level, all the +following groups will be aborted. For this example, if project2 fails then project1 will not run. ### Custom Backend Config See [Custom Workflow Use Cases: Custom Backend Config](custom-workflows.html#custom-backend-config) diff --git a/server/core/config/raw/repo_cfg.go b/server/core/config/raw/repo_cfg.go index 5c1b46391e..e2bad4add1 100644 --- a/server/core/config/raw/repo_cfg.go +++ b/server/core/config/raw/repo_cfg.go @@ -25,6 +25,9 @@ const DefaultDeleteSourceBranchOnMerge = false // DefaultEmojiReaction is the default emoji reaction for repos const DefaultEmojiReaction = "" +// DefaultAbortOnExcecutionOrderFail being false is the default setting for abort on execution group failiures +const DefaultAbortOnExcecutionOrderFail = false + // RepoCfg is the raw schema for repo-level atlantis.yaml config. type RepoCfg struct { Version *int `yaml:"version,omitempty"` @@ -37,6 +40,7 @@ type RepoCfg struct { DeleteSourceBranchOnMerge *bool `yaml:"delete_source_branch_on_merge,omitempty"` EmojiReaction *string `yaml:"emoji_reaction,omitempty"` AllowedRegexpPrefixes []string `yaml:"allowed_regexp_prefixes,omitempty"` + AbortOnExcecutionOrderFail *bool `yaml:"abort_on_execution_order_fail,omitempty"` } func (r RepoCfg) Validate() error { @@ -88,6 +92,11 @@ func (r RepoCfg) ToValid() valid.RepoCfg { emojiReaction = *r.EmojiReaction } + abortOnExcecutionOrderFail := DefaultAbortOnExcecutionOrderFail + if r.AbortOnExcecutionOrderFail != nil { + abortOnExcecutionOrderFail = *r.AbortOnExcecutionOrderFail + } + return valid.RepoCfg{ Version: *r.Version, Projects: validProjects, @@ -99,5 +108,6 @@ func (r RepoCfg) ToValid() valid.RepoCfg { DeleteSourceBranchOnMerge: r.DeleteSourceBranchOnMerge, AllowedRegexpPrefixes: r.AllowedRegexpPrefixes, EmojiReaction: emojiReaction, + AbortOnExcecutionOrderFail: abortOnExcecutionOrderFail, } } diff --git a/server/core/config/raw/repo_cfg_test.go b/server/core/config/raw/repo_cfg_test.go index 5a78960a99..7c138053f3 100644 --- a/server/core/config/raw/repo_cfg_test.go +++ b/server/core/config/raw/repo_cfg_test.go @@ -259,43 +259,48 @@ func TestConfig_ToValid(t *testing.T) { }, }, { - description: "automerge and parallel_apply omitted", + description: "automerge, parallel_apply and abort_on_execution_order_fail omitted", input: raw.RepoCfg{ Version: Int(2), }, exp: valid.RepoCfg{ - Version: 2, - Automerge: false, - ParallelApply: false, - Workflows: map[string]valid.Workflow{}, + Version: 2, + Automerge: false, + ParallelApply: false, + AbortOnExcecutionOrderFail: false, + Workflows: map[string]valid.Workflow{}, }, }, { - description: "automerge and parallel_apply true", + description: "automerge, parallel_apply and abort_on_execution_order_fail true", input: raw.RepoCfg{ - Version: Int(2), - Automerge: Bool(true), - ParallelApply: Bool(true), + Version: Int(2), + Automerge: Bool(true), + ParallelApply: Bool(true), + AbortOnExcecutionOrderFail: Bool(true), }, exp: valid.RepoCfg{ - Version: 2, - Automerge: true, - ParallelApply: true, - Workflows: map[string]valid.Workflow{}, + Version: 2, + Automerge: true, + ParallelApply: true, + AbortOnExcecutionOrderFail: true, + Workflows: map[string]valid.Workflow{}, }, }, { - description: "automerge and parallel_apply false", + description: "automerge, parallel_apply and abort_on_execution_order_fail false", input: raw.RepoCfg{ - Version: Int(2), - Automerge: Bool(false), - ParallelApply: Bool(false), + Version: Int(2), + Automerge: Bool(false), + ParallelApply: Bool(false), + AbortOnExcecutionOrderFail: Bool(false), }, exp: valid.RepoCfg{ - Version: 2, - Automerge: false, - ParallelApply: false, - Workflows: map[string]valid.Workflow{}, + Version: 2, + Automerge: false, + ParallelApply: false, + AbortOnExcecutionOrderFail: false, + Workflows: map[string]valid.Workflow{}, }, }, { diff --git a/server/core/config/valid/repo_cfg.go b/server/core/config/valid/repo_cfg.go index 06391b4c53..4c74eea60a 100644 --- a/server/core/config/valid/repo_cfg.go +++ b/server/core/config/valid/repo_cfg.go @@ -26,6 +26,7 @@ type RepoCfg struct { RepoLocking *bool EmojiReaction string AllowedRegexpPrefixes []string + AbortOnExcecutionOrderFail bool } func (r RepoCfg) FindProjectsByDirWorkspace(repoRelDir string, workspace string) []Project { diff --git a/server/events/apply_command_runner.go b/server/events/apply_command_runner.go index 6dc89bb30d..c8189d0349 100644 --- a/server/events/apply_command_runner.go +++ b/server/events/apply_command_runner.go @@ -161,7 +161,7 @@ func (a *ApplyCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) { var result command.Result if a.isParallelEnabled(projectCmds) { ctx.Log.Info("Running applies in parallel") - result = runProjectCmdsParallelGroups(projectCmds, a.prjCmdRunner.Apply, a.parallelPoolSize) + result = runProjectCmdsParallelGroups(ctx, projectCmds, a.prjCmdRunner.Apply, a.parallelPoolSize) } else { result = runProjectCmds(projectCmds, a.prjCmdRunner.Apply) } diff --git a/server/events/apply_command_runner_test.go b/server/events/apply_command_runner_test.go index 713935d6cc..90e94067ee 100644 --- a/server/events/apply_command_runner_test.go +++ b/server/events/apply_command_runner_test.go @@ -215,3 +215,296 @@ func TestApplyCommandRunner_IsSilenced(t *testing.T) { }) } } + +func TestApplyCommandRunner_ExecutionOrder(t *testing.T) { + logger := logging.NewNoopLogger(t) + RegisterMockTestingT(t) + + cases := []struct { + Description string + ProjectContexts []command.ProjectContext + ProjectResults []command.ProjectResult + RunnerInvokeMatch []*EqMatcher + ExpComment string + }{ + { + Description: "When first apply fails, the second don't run", + ProjectContexts: []command.ProjectContext{ + { + ExecutionOrderGroup: 0, + ProjectName: "First", + ParallelApplyEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 1, + ProjectName: "Second", + ParallelApplyEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + { + Command: command.Apply, + Error: errors.New("Shabang!"), + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + }, + ExpComment: "Ran Apply for 2 projects:\n\n" + + "1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n\n### 1. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " + + "2. dir: `` workspace: ``\n**Apply Error**\n```\nShabang!\n```\n\n---", + }, + { + Description: "When first apply fails, the second not will run", + ProjectContexts: []command.ProjectContext{ + { + ExecutionOrderGroup: 0, + ProjectName: "First", + ParallelApplyEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 1, + ProjectName: "Second", + ParallelApplyEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Apply, + Error: errors.New("Shabang!"), + }, + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Never(), + }, + ExpComment: "Ran Apply for dir: `` workspace: ``\n\n**Apply Error**\n```\nShabang!\n```", + }, + { + Description: "When both in a group of two succeeds, the following two will run", + ProjectContexts: []command.ProjectContext{ + { + ExecutionOrderGroup: 0, + ProjectName: "First", + ParallelApplyEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 0, + ProjectName: "Second", + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 1, + ProjectName: "Third", + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 1, + ProjectName: "Fourth", + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + { + Command: command.Apply, + Error: errors.New("Shabang!"), + }, + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + Never(), + Never(), + }, + ExpComment: "Ran Apply for 2 projects:\n\n" + + "1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n\n### 1. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " + + "2. dir: `` workspace: ``\n**Apply Error**\n```\nShabang!\n```\n\n---", + }, + { + Description: "When one out of two fails, the following two will not run", + ProjectContexts: []command.ProjectContext{ + { + ExecutionOrderGroup: 0, + ProjectName: "First", + ParallelApplyEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 0, + ProjectName: "Second", + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 1, + ProjectName: "Third", + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 1, + AbortOnExcecutionOrderFail: true, + ProjectName: "Fourth", + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + { + Command: command.Apply, + Error: errors.New("Shabang!"), + }, + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + Once(), + Once(), + }, + ExpComment: "Ran Apply for 4 projects:\n\n" + + "1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n\n### 1. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " + + "2. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " + + "3. dir: `` workspace: ``\n**Apply Error**\n```\nShabang!\n```\n\n---\n### " + + "4. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---", + }, + { + Description: "Don't block when parallel is not set", + ProjectContexts: []command.ProjectContext{ + { + ExecutionOrderGroup: 0, + ProjectName: "First", + AbortOnExcecutionOrderFail: true, + }, + { + ExecutionOrderGroup: 1, + ProjectName: "Second", + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Apply, + Error: errors.New("Shabang!"), + }, + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + }, + ExpComment: "Ran Apply for 2 projects:\n\n" + + "1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n\n### 1. dir: `` workspace: ``\n**Apply Error**\n```\nShabang!\n```\n\n---\n### " + + "2. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---", + }, + { + Description: "Don't block when abortOnExcecutionOrderFail is not set", + ProjectContexts: []command.ProjectContext{ + { + ExecutionOrderGroup: 0, + ProjectName: "First", + }, + { + ExecutionOrderGroup: 1, + ProjectName: "Second", + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Apply, + Error: errors.New("Shabang!"), + }, + { + Command: command.Apply, + ApplySuccess: "Great success!", + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + }, + ExpComment: "Ran Apply for 2 projects:\n\n" + + "1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n\n### 1. dir: `` workspace: ``\n**Apply Error**\n```\nShabang!\n```\n\n---\n### " + + "2. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---", + }, + } + + for _, c := range cases { + t.Run(c.Description, func(t *testing.T) { + vcsClient := setup(t) + + scopeNull, _, _ := metrics.NewLoggingScope(logger, "atlantis") + + pull := &github.PullRequest{ + State: github.String("open"), + } + modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} + + cmd := &events.CommentCommand{Name: command.Apply} + + ctx := &command.Context{ + User: testdata.User, + Log: logging.NewNoopLogger(t), + Scope: scopeNull, + Pull: modelPull, + HeadRepo: testdata.GithubRepo, + Trigger: command.CommentTrigger, + } + + When(githubGetter.GetPullRequest(testdata.GithubRepo, testdata.Pull.Num)).ThenReturn(pull, nil) + When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, modelPull.BaseRepo, testdata.GithubRepo, nil) + + When(projectCommandBuilder.BuildApplyCommands(ctx, cmd)).ThenReturn(c.ProjectContexts, nil) + for i := range c.ProjectContexts { + When(projectCommandRunner.Apply(c.ProjectContexts[i])).ThenReturn(c.ProjectResults[i]) + } + + applyCommandRunner.Run(ctx, cmd) + + for i := range c.ProjectContexts { + projectCommandRunner.VerifyWasCalled(c.RunnerInvokeMatch[i]).Apply(c.ProjectContexts[i]) + + } + + vcsClient.VerifyWasCalledOnce().CreateComment( + testdata.GithubRepo, modelPull.Num, c.ExpComment, "apply", + ) + }) + } +} diff --git a/server/events/command/project_context.go b/server/events/command/project_context.go index 8d299981f9..ffefded5f0 100644 --- a/server/events/command/project_context.go +++ b/server/events/command/project_context.go @@ -111,6 +111,8 @@ type ProjectContext struct { JobID string // The index of order group. Before planning/applying it will use to sort projects. Default is 0. ExecutionOrderGroup int + // If plans/applies should be aborted if any prior plan/apply fails + AbortOnExcecutionOrderFail bool } // SetProjectScopeTags adds ProjectContext tags to a new returned scope. diff --git a/server/events/plan_command_runner.go b/server/events/plan_command_runner.go index f648f1a5c7..8fbd1409e3 100644 --- a/server/events/plan_command_runner.go +++ b/server/events/plan_command_runner.go @@ -130,7 +130,7 @@ func (p *PlanCommandRunner) runAutoplan(ctx *command.Context) { var result command.Result if p.isParallelEnabled(projectCmds) { ctx.Log.Info("Running plans in parallel") - result = runProjectCmdsParallelGroups(projectCmds, p.prjCmdRunner.Plan, p.parallelPoolSize) + result = runProjectCmdsParallelGroups(ctx, projectCmds, p.prjCmdRunner.Plan, p.parallelPoolSize) } else { result = runProjectCmds(projectCmds, p.prjCmdRunner.Plan) } @@ -250,7 +250,7 @@ func (p *PlanCommandRunner) run(ctx *command.Context, cmd *CommentCommand) { var result command.Result if p.isParallelEnabled(projectCmds) { ctx.Log.Info("Running plans in parallel") - result = runProjectCmdsParallelGroups(projectCmds, p.prjCmdRunner.Plan, p.parallelPoolSize) + result = runProjectCmdsParallelGroups(ctx, projectCmds, p.prjCmdRunner.Plan, p.parallelPoolSize) } else { result = runProjectCmds(projectCmds, p.prjCmdRunner.Plan) } diff --git a/server/events/plan_command_runner_test.go b/server/events/plan_command_runner_test.go index 6e5c510566..6023421b48 100644 --- a/server/events/plan_command_runner_test.go +++ b/server/events/plan_command_runner_test.go @@ -1,8 +1,10 @@ package events_test import ( + "errors" "testing" + "github.com/google/go-github/v52/github" . "github.com/petergtz/pegomock" "github.com/runatlantis/atlantis/server/core/db" "github.com/runatlantis/atlantis/server/events" @@ -150,3 +152,360 @@ func TestPlanCommandRunner_IsSilenced(t *testing.T) { }) } } + +func TestPlanCommandRunner_ExecutionOrder(t *testing.T) { + logger := logging.NewNoopLogger(t) + RegisterMockTestingT(t) + + cases := []struct { + Description string + ProjectContexts []command.ProjectContext + ProjectResults []command.ProjectResult + RunnerInvokeMatch []*EqMatcher + PrevPlanStored bool + }{ + { + Description: "When first plan fails, the second don't run", + ProjectContexts: []command.ProjectContext{ + { + CommandName: command.Plan, + ExecutionOrderGroup: 0, + Workspace: "first", + ProjectName: "First", + ParallelPlanEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 1, + Workspace: "second", + ProjectName: "Second", + ParallelPlanEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + { + Command: command.Plan, + Error: errors.New("Shabang!"), + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + }, + }, + { + Description: "When first fails, the second will not run", + ProjectContexts: []command.ProjectContext{ + { + CommandName: command.Plan, + ExecutionOrderGroup: 0, + ProjectName: "First", + ParallelPlanEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 1, + ProjectName: "Second", + ParallelPlanEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Plan, + Error: errors.New("Shabang!"), + }, + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{}, + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Never(), + }, + }, + { + Description: "When first fails by autorun, the second will not run", + ProjectContexts: []command.ProjectContext{ + { + CommandName: command.Plan, + AutoplanEnabled: true, + ExecutionOrderGroup: 0, + ProjectName: "First", + ParallelPlanEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + AutoplanEnabled: true, + ExecutionOrderGroup: 1, + ProjectName: "Second", + ParallelPlanEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Plan, + Error: errors.New("Shabang!"), + }, + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{}, + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Never(), + }, + }, + { + Description: "When both in a group of two succeeds, the following two will run", + ProjectContexts: []command.ProjectContext{ + { + CommandName: command.Plan, + ExecutionOrderGroup: 0, + ProjectName: "First", + ParallelPlanEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 0, + ProjectName: "Second", + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 1, + ProjectName: "Third", + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 1, + ProjectName: "Fourth", + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + { + Command: command.Plan, + Error: errors.New("Shabang!"), + }, + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + Never(), + Never(), + }, + }, + { + Description: "When one out of two fails, the following two will not run", + ProjectContexts: []command.ProjectContext{ + { + CommandName: command.Plan, + ExecutionOrderGroup: 0, + ProjectName: "First", + ParallelPlanEnabled: true, + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 0, + ProjectName: "Second", + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 1, + ProjectName: "Third", + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 1, + AbortOnExcecutionOrderFail: true, + ProjectName: "Fourth", + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + { + Command: command.Plan, + Error: errors.New("Shabang!"), + }, + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + Once(), + Once(), + }, + }, + { + Description: "Don't block when parallel is not set", + ProjectContexts: []command.ProjectContext{ + { + CommandName: command.Plan, + ExecutionOrderGroup: 0, + ProjectName: "First", + AbortOnExcecutionOrderFail: true, + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 1, + ProjectName: "Second", + AbortOnExcecutionOrderFail: true, + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Plan, + Error: errors.New("Shabang!"), + }, + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + }, + }, + { + Description: "Don't block when abortOnExcecutionOrderFail is not set", + ProjectContexts: []command.ProjectContext{ + { + CommandName: command.Plan, + ExecutionOrderGroup: 0, + ProjectName: "First", + }, + { + CommandName: command.Plan, + ExecutionOrderGroup: 1, + ProjectName: "Second", + }, + }, + ProjectResults: []command.ProjectResult{ + { + Command: command.Plan, + Error: errors.New("Shabang!"), + }, + { + Command: command.Plan, + PlanSuccess: &models.PlanSuccess{ + TerraformOutput: "true", + }, + }, + }, + RunnerInvokeMatch: []*EqMatcher{ + Once(), + Once(), + }, + }, + } + + for _, c := range cases { + t.Run(c.Description, func(t *testing.T) { + // vcsClient := setup(t) + + tmp := t.TempDir() + db, err := db.New(tmp) + Ok(t, err) + + vcsClient := setup(t, func(tc *TestConfig) { + tc.backend = db + }) + + scopeNull, _, _ := metrics.NewLoggingScope(logger, "atlantis") + + pull := &github.PullRequest{ + State: github.String("open"), + } + modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} + + cmd := &events.CommentCommand{Name: command.Plan} + + ctx := &command.Context{ + User: testdata.User, + Log: logging.NewNoopLogger(t), + Scope: scopeNull, + Pull: modelPull, + HeadRepo: testdata.GithubRepo, + Trigger: command.CommentTrigger, + } + + When(githubGetter.GetPullRequest(testdata.GithubRepo, testdata.Pull.Num)).ThenReturn(pull, nil) + When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, modelPull.BaseRepo, testdata.GithubRepo, nil) + + When(projectCommandBuilder.BuildPlanCommands(ctx, cmd)).ThenReturn(c.ProjectContexts, nil) + // When(projectCommandBuilder.BuildPlanCommands(ctx, cmd)).Then(func(args []Param) ReturnValues { + // return ReturnValues{[]command.ProjectContext{{CommandName: command.Plan}}, nil} + // }) + for i := range c.ProjectContexts { + When(projectCommandRunner.Plan(c.ProjectContexts[i])).ThenReturn(c.ProjectResults[i]) + } + + planCommandRunner.Run(ctx, cmd) + type RepoModel interface{ models.Repo } + + for i := range c.ProjectContexts { + projectCommandRunner.VerifyWasCalled(c.RunnerInvokeMatch[i]).Plan(c.ProjectContexts[i]) + } + + vcsClient.VerifyWasCalledOnce().CreateComment( + AnyRepo(), EqInt(modelPull.Num), AnyString(), EqString("plan"), + ) + }) + } +} diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index bf1508d1c8..12e64127c2 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -36,6 +36,8 @@ const ( DefaultParallelPlanEnabled = false // DefaultDeleteSourceBranchOnMerge being false is the default setting whether or not to remove a source branch on merge DefaultDeleteSourceBranchOnMerge = false + // DefaultAbortOnExcecutionOrderFail being false is the default setting for abort on execution group failiures + DefaultAbortOnExcecutionOrderFail = false ) func NewInstrumentedProjectCommandBuilder( @@ -384,6 +386,7 @@ func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Contex repoCfg.ParallelApply, repoCfg.ParallelPlan, verbose, + repoCfg.AbortOnExcecutionOrderFail, p.TerraformExecutor, )...) } @@ -407,10 +410,12 @@ func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Contex automerge := DefaultAutomergeEnabled parallelApply := DefaultParallelApplyEnabled parallelPlan := DefaultParallelPlanEnabled + abortOnExcecutionOrderFail := DefaultAbortOnExcecutionOrderFail if hasRepoCfg { automerge = repoCfg.Automerge parallelApply = repoCfg.ParallelApply parallelPlan = repoCfg.ParallelPlan + abortOnExcecutionOrderFail = repoCfg.AbortOnExcecutionOrderFail } pCfg := p.GlobalCfg.DefaultProjCfg(ctx.Log, ctx.Pull.BaseRepo.ID(), mp.Path, pWorkspace) @@ -426,6 +431,7 @@ func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Contex parallelApply, parallelPlan, verbose, + abortOnExcecutionOrderFail, p.TerraformExecutor, )...) } @@ -700,10 +706,12 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *command.Conte automerge := DefaultAutomergeEnabled parallelApply := DefaultParallelApplyEnabled parallelPlan := DefaultParallelPlanEnabled + abortOnExcecutionOrderFail := DefaultAbortOnExcecutionOrderFail if repoCfgPtr != nil { automerge = repoCfgPtr.Automerge parallelApply = repoCfgPtr.ParallelApply parallelPlan = repoCfgPtr.ParallelPlan + abortOnExcecutionOrderFail = *&repoCfgPtr.AbortOnExcecutionOrderFail } if len(matchingProjects) > 0 { @@ -728,6 +736,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *command.Conte parallelApply, parallelPlan, verbose, + abortOnExcecutionOrderFail, p.TerraformExecutor, )...) } @@ -751,6 +760,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *command.Conte parallelApply, parallelPlan, verbose, + abortOnExcecutionOrderFail, p.TerraformExecutor, )...) } diff --git a/server/events/project_command_context_builder.go b/server/events/project_command_context_builder.go index 6670fbd3b4..bc338a6c83 100644 --- a/server/events/project_command_context_builder.go +++ b/server/events/project_command_context_builder.go @@ -38,7 +38,7 @@ type ProjectCommandContextBuilder interface { prjCfg valid.MergedProjectCfg, commentFlags []string, repoDir string, - automerge, parallelApply, parallelPlan, verbose bool, terraformClient terraform.Client, + automerge, parallelApply, parallelPlan, verbose, abortOnExcecutionOrderFail bool, terraformClient terraform.Client, ) []command.ProjectContext } @@ -58,12 +58,13 @@ func (cb *CommandScopedStatsProjectCommandContextBuilder) BuildProjectContext( prjCfg valid.MergedProjectCfg, commentFlags []string, repoDir string, - automerge, parallelApply, parallelPlan, verbose bool, terraformClient terraform.Client, + automerge, parallelApply, parallelPlan, verbose, abortOnExcecutionOrderFail bool, + terraformClient terraform.Client, ) (projectCmds []command.ProjectContext) { cb.ProjectCounter.Inc(1) cmds := cb.ProjectCommandContextBuilder.BuildProjectContext( - ctx, cmdName, subCmdName, prjCfg, commentFlags, repoDir, automerge, parallelApply, parallelPlan, verbose, terraformClient, + ctx, cmdName, subCmdName, prjCfg, commentFlags, repoDir, automerge, parallelApply, parallelPlan, verbose, abortOnExcecutionOrderFail, terraformClient, ) projectCmds = []command.ProjectContext{} @@ -91,7 +92,8 @@ func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext( prjCfg valid.MergedProjectCfg, commentFlags []string, repoDir string, - automerge, parallelApply, parallelPlan, verbose bool, terraformClient terraform.Client, + automerge, parallelApply, parallelPlan, verbose, abortOnExcecutionOrderFail bool, + terraformClient terraform.Client, ) (projectCmds []command.ProjectContext) { ctx.Log.Debug("Building project command context for %s", cmdName) @@ -139,6 +141,7 @@ func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext( parallelApply, parallelPlan, verbose, + abortOnExcecutionOrderFail, ctx.Scope, ctx.PullRequestStatus, ) @@ -160,7 +163,8 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext( prjCfg valid.MergedProjectCfg, commentFlags []string, repoDir string, - automerge, parallelApply, parallelPlan, verbose bool, terraformClient terraform.Client, + automerge, parallelApply, parallelPlan, verbose, abortOnExcecutionOrderFail bool, + terraformClient terraform.Client, ) (projectCmds []command.ProjectContext) { ctx.Log.Debug("PolicyChecks are enabled") @@ -181,6 +185,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext( parallelApply, parallelPlan, verbose, + abortOnExcecutionOrderFail, terraformClient, ) @@ -202,6 +207,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext( parallelApply, parallelPlan, verbose, + abortOnExcecutionOrderFail, ctx.Scope, ctx.PullRequestStatus, )) @@ -225,6 +231,7 @@ func newProjectCommandContext(ctx *command.Context, parallelApplyEnabled bool, parallelPlanEnabled bool, verbose bool, + abortOnExcecutionOrderFail bool, scope tally.Scope, pullStatus models.PullReqStatus, ) command.ProjectContext { @@ -287,6 +294,7 @@ func newProjectCommandContext(ctx *command.Context, PullReqStatus: pullStatus, JobID: uuid.New().String(), ExecutionOrderGroup: projCfg.ExecutionOrderGroup, + AbortOnExcecutionOrderFail: abortOnExcecutionOrderFail, } } diff --git a/server/events/project_command_context_builder_test.go b/server/events/project_command_context_builder_test.go index ec4cac9af5..8d9cb7edc3 100644 --- a/server/events/project_command_context_builder_test.go +++ b/server/events/project_command_context_builder_test.go @@ -62,7 +62,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) { }, } - result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, false, false, false, terraformClient) + result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, false, false, false, false, terraformClient) assert.Equal(t, models.ErroredPolicyCheckStatus, result[0].ProjectPlanStatus) }) @@ -81,7 +81,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) { }, } - result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, false, false, false, terraformClient) + result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, false, false, false, false, terraformClient) assert.Equal(t, models.ErroredPolicyCheckStatus, result[0].ProjectPlanStatus) }) @@ -101,9 +101,29 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) { }, } - result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, true, false, false, terraformClient) + result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, true, false, false, false, terraformClient) assert.True(t, result[0].ParallelApplyEnabled) assert.False(t, result[0].ParallelPlanEnabled) }) + + t.Run("when AbortOnExcecutionOrderFail is set to true", func(t *testing.T) { + projCfg.Name = "Apply Comment" + When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, "", []string{})).ThenReturn(expectedPlanCmt) + When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "", false)).ThenReturn(expectedApplyCmt) + pullStatus.Projects = []models.ProjectStatus{ + { + Status: models.ErroredPlanStatus, + RepoRelDir: "dir2", + }, + { + Status: models.ErroredPolicyCheckStatus, + RepoRelDir: "dir1", + }, + } + + result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, false, false, false, true, terraformClient) + + assert.True(t, result[0].AbortOnExcecutionOrderFail) + }) } diff --git a/server/events/project_command_pool_executor.go b/server/events/project_command_pool_executor.go index 1039c7db19..c3b19114d6 100644 --- a/server/events/project_command_pool_executor.go +++ b/server/events/project_command_pool_executor.go @@ -72,6 +72,7 @@ func splitByExecutionOrderGroup(cmds []command.ProjectContext) [][]command.Proje } func runProjectCmdsParallelGroups( + ctx *command.Context, cmds []command.ProjectContext, runnerFunc prjCmdRunnerFunc, poolSize int, @@ -81,6 +82,10 @@ func runProjectCmdsParallelGroups( for _, group := range groups { res := runProjectCmdsParallel(group, runnerFunc, poolSize) results = append(results, res.ProjectResults...) + if res.HasErrors() && group[0].AbortOnExcecutionOrderFail { + ctx.Log.Info("abort on execution order when failed") + break + } } return command.Result{ProjectResults: results} diff --git a/server/events/version_command_runner.go b/server/events/version_command_runner.go index 101ba28570..08f27de8c1 100644 --- a/server/events/version_command_runner.go +++ b/server/events/version_command_runner.go @@ -47,7 +47,7 @@ func (v *VersionCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) { var result command.Result if v.isParallelEnabled(projectCmds) { ctx.Log.Info("Running version in parallel") - result = runProjectCmdsParallel(projectCmds, v.prjCmdRunner.Version, v.parallelPoolSize) + result = runProjectCmdsParallelGroups(ctx, projectCmds, v.prjCmdRunner.Version, v.parallelPoolSize) } else { result = runProjectCmds(projectCmds, v.prjCmdRunner.Version) }