From 29fef94be7a46d453f8e907c6520d24a59e9e465 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 02:02:52 +0000 Subject: [PATCH 01/35] chore(deps): lock file maintenance in server/events/testdata/test-repos/workspace-configured/main.tf (#3759) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 66 +++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/yarn.lock b/yarn.lock index c90a52fa8b..d679ac3889 100644 --- a/yarn.lock +++ b/yarn.lock @@ -134,9 +134,9 @@ "@algolia/requester-common" "4.19.1" "@babel/parser@^7.20.15", "@babel/parser@^7.21.3": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.11.tgz#becf8ee33aad2a35ed5607f521fe6e72a615f905" - integrity sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g== + version "7.22.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" + integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== "@docsearch/css@3.5.2", "@docsearch/css@^3.5.1": version "3.5.2" @@ -396,9 +396,9 @@ "@types/node" "*" "@types/linkify-it@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" - integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.3.tgz#15a0712296c5041733c79efe233ba17ae5a7587b" + integrity sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g== "@types/markdown-it-emoji@^2.0.2": version "2.0.2" @@ -434,9 +434,9 @@ integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== "@types/node@*": - version "20.5.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.7.tgz#4b8ecac87fbefbc92f431d09c30e176fc0a7c377" - integrity sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA== + version "20.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16" + integrity sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg== "@types/web-bluetooth@^0.0.17": version "0.0.17" @@ -444,9 +444,9 @@ integrity sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA== "@vitejs/plugin-vue@^4.2.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.3.3.tgz#3b2337f64495f95cfea5b1497d2d3f4a0b3382b2" - integrity sha512-ssxyhIAZqB0TrpUg6R0cBpCuMk9jTIlO1GNSKKQD6S8VjnXi6JXKfUXjSsxey9IwQiaRGsO1WnW9Rkl1L6AJVw== + version "4.3.4" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz#a289dff38e01949fe7be581d5542cabaeb961dec" + integrity sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw== "@vue/compiler-core@3.3.4": version "3.3.4" @@ -1001,9 +1001,9 @@ cac@^6.7.14: integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520: - version "1.0.30001524" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz#1e14bce4f43c41a7deaeb5ebfe86664fe8dadb80" - integrity sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA== + version "1.0.30001532" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz#c6a4d5d2da6d2b967f0ee5e12e7f680db6ad2fca" + integrity sha512-FbDFnNat3nMnrROzqrsg314zhqN5LGQ1kyyMk2opcrwGbVGpHRhgCWtAgD5YJUqNAiQ+dklreil/c3Qf1dfCTw== chalk@^5.0.0, chalk@^5.3.0: version "5.3.0" @@ -1088,9 +1088,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== electron-to-chromium@^1.4.477: - version "1.4.503" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz#7bd43927ea9b4198697672d28d8fbd0da016a7a1" - integrity sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA== + version "1.4.513" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.513.tgz#41a50bf749aa7d8058ffbf7a131fc3327a7b1675" + integrity sha512-cOB0xcInjm+E5qIssHeXJ29BaUyWpMyFKT5RB3bsLENDheCja0wMkHJyiPl0NBE/VzDI7JDuNEQWhe6RitEUcw== emoji-regex@^10.2.1: version "10.2.1" @@ -1198,9 +1198,9 @@ fill-range@^7.0.1: to-regex-range "^5.0.1" fraction.js@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.1.tgz#14b4cc886575a5684f8d5fd5759c5db376bb7bb8" - integrity sha512-/KxoyCnPM0GwYI4NN0Iag38Tqt+od3/mLuguepLgCAKPn0ZhC544nssAW0tG2/00zXEYl9W+7hwAIpLHo6Oc7Q== + version "4.3.6" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d" + integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg== fs-extra@^11.1.1: version "11.1.1" @@ -1566,9 +1566,9 @@ postcss-value-parser@^4.2.0: integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.1.10, postcss@^8.4.25, postcss@^8.4.27: - version "8.4.28" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.28.tgz#c6cc681ed00109072816e1557f889ef51cf950a5" - integrity sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw== + version "8.4.29" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd" + integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw== dependencies: nanoid "^3.3.6" picocolors "^1.0.0" @@ -1619,9 +1619,9 @@ reusify@^1.0.4: integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rollup@^3.26.2, rollup@^3.27.1: - version "3.28.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.28.1.tgz#fb44aa6d5e65c7e13fd5bcfff266d0c4ea9ba433" - integrity sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw== + version "3.29.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.1.tgz#ba53a179d46ac3cd79e162dca6ab70d93cd26f78" + integrity sha512-c+ebvQz0VIH4KhhCpDsI+Bik0eT8ZFEVZEYw0cGMVqIP8zc+gnwl7iXCamTw7vzv2MeuZFZfdx5JJIq+ehzDlg== optionalDependencies: fsevents "~2.3.2" @@ -1785,9 +1785,9 @@ vite@~4.4.2: fsevents "~2.3.2" vue-demi@>=0.14.5: - version "0.14.5" - resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.5.tgz#676d0463d1a1266d5ab5cba932e043d8f5f2fbd9" - integrity sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA== + version "0.14.6" + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.6.tgz#dc706582851dc1cdc17a0054f4fec2eb6df74c92" + integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w== vue-router@^4.2.4: version "4.2.4" @@ -1840,6 +1840,6 @@ which@^2.0.1: isexe "^2.0.0" yaml@^2.1.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" - integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== + version "2.3.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" + integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== From cd7ae114ebed9bd2e0ff4bbeabbb182e9ff57c25 Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:57:50 +0100 Subject: [PATCH 02/35] feat: add ability to track git untracked files (#3724) --- cmd/server.go | 5 + runatlantis.io/docs/custom-workflows.md | 52 +- runatlantis.io/docs/server-configuration.md | 10 + .../events/events_controller_e2e_test.go | 1 + server/events/mock_workingdir_test.go | 54 ++ server/events/mocks/mock_working_dir.go | 54 ++ server/events/project_command_builder.go | 124 +++-- .../project_command_builder_internal_test.go | 4 + server/events/project_command_builder_test.go | 472 ++++++++++++++---- server/events/project_finder.go | 2 +- server/events/working_dir.go | 27 +- server/server.go | 1 + server/user_config.go | 1 + 13 files changed, 654 insertions(+), 153 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index b9949a3908..e79012408c 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -97,6 +97,7 @@ const ( GitlabTokenFlag = "gitlab-token" GitlabUserFlag = "gitlab-user" GitlabWebhookSecretFlag = "gitlab-webhook-secret" // nolint: gosec + IncludeGitUntrackedFiles = "include-git-untracked-files" APISecretFlag = "api-secret" HidePrevPlanComments = "hide-prev-plan-comments" QuietPolicyChecks = "quiet-policy-checks" @@ -475,6 +476,10 @@ var boolFlags = map[string]boolFlag{ "VCS support is limited to: GitHub.", defaultValue: false, }, + IncludeGitUntrackedFiles: { + description: "Include git untracked files in the Atlantis modified file scope.", + defaultValue: false, + }, ParallelPlanFlag: { description: "Run plan operations in parallel.", defaultValue: false, diff --git a/runatlantis.io/docs/custom-workflows.md b/runatlantis.io/docs/custom-workflows.md index 3ba12e1325..a6ee35ab6a 100644 --- a/runatlantis.io/docs/custom-workflows.md +++ b/runatlantis.io/docs/custom-workflows.md @@ -161,16 +161,17 @@ workflows: - run: terraform apply $PLANFILE ``` -### cdktf -Here are the requirements to enable [cdktf](https://developer.hashicorp.com/terraform/cdktf) - -- A custom image with `cdktf` installed -- The autoplan file updated to trigger off of `**/cdk.tf.json` -- The output of `cdktf synth` has to be committed to the pull request -- Optional: Use `pre_workflow_hooks` to run `cdktf synth` as a double check +### CDKTF +Here are the requirements to enable [CDKTF](https://developer.hashicorp.com/terraform/cdktf) + +- A custom image with `CDKTF` installed +- Add `**/cdk.tf.json` to the list of Atlantis autoplan files. +- Set the `atlantis-include-git-untracked-files` flag so that the Terraform files dynamically generated +by CDKTF will be add to the Atlantis modified file list. +- Use `pre_workflow_hooks` to run `cdktf synth` - Optional: There isn't a requirement to use a repo `atlantis.yaml` but one can be leveraged if needed. -#### custom image +#### Custom Image ```dockerfile # Dockerfile @@ -179,11 +180,12 @@ FROM ghcr.io/runatlantis/atlantis:v0.19.7 RUN apk add npm && npm i -g cdktf-cli ``` -#### server config +#### Server Config ```bash # env variables ATLANTIS_AUTOPLAN_FILE_LIST="**/*.tf,**/*.tfvars,**/*.tfvars.json,**/cdk.tf.json" +ATLANTIS_INCLUDE_GIT_UNTRACKED_FILES=true ``` OR @@ -192,9 +194,10 @@ OR ```yaml # config.yaml autoplan-file-list: "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/cdk.tf.json" +include-git-untracked-files: true ``` -#### server repo config +#### Server Repo Config Use `pre_workflow_hooks` @@ -204,32 +207,39 @@ Use `pre_workflow_hooks` repos: - id: /.*cdktf.*/ pre_workflow_hooks: - - run: npm i && cdktf get && cdktf synth + - run: npm i && cdktf get && cdktf synth --output ci-cdktf.out ``` -#### repo structure +**Note:** don't use the default `cdktf.out` directory that CDKTF uses, as this should be in the `.gitignore` list of the +repo, so that locally generated files are not checked in. + +#### Repo Structure -This is the git repo structure after running `cdktf synth`. The `cdk.tf.json` files contain the HCL that atlantis can run. +This is the git repo structure after running `cdktf synth`. The `cdk.tf.json` files contain the Terraform configuration +that atlantis can run. ```bash $ tree --gitignore . ├── cdktf.json -├── cdktf.out +├── ci-cdktf.out │ ├── manifest.json │ └── stacks │ └── eks │ └── cdk.tf.json ``` -#### workflow +#### Workflow -1. Container orchestrator (k8s/fargate/ecs/etc) uses the custom docker image of atlantis with `cdktf` installed with the `--autoplan-file-list` to trigger on json files -1. PR branch is pushed up containing `cdktf` changes and generated hcl json -1. Atlantis checks out the branch in the repo -1. Atlantis runs the `npm i && cdktf get && cdktf synth` command in the repo root as a step in `pre_workflow_hooks` (as a double check described above) -1. Atlantis detects the change to the generated hcl json files in a number of `dir`s -1. Atlantis then runs `terraform` workflows in the respective `dir`s as usual +1. Container orchestrator (k8s/fargate/ecs/etc) uses the custom docker image of atlantis with `cdktf` installed with +the `--autoplan-file-list` to trigger on `cdk.tf.json` files and `--include-git-untracked-files` set to include the +CDKTF dynamically generated Terraform files in the Atlantis plan. +1. PR branch is pushed up containing `cdktf` code changes. +1. Atlantis checks out the branch in the repo. +1. Atlantis runs the `npm i && cdktf get && cdktf synth` command in the repo root as a step in `pre_workflow_hooks`, +generating the `cdk.tf.json` Terraform files. +1. Atlantis detects the `cdk.tf.json` untracked files in a number of directories. +1. Atlantis then runs `terraform` workflows in the respective directories as usual. ### Terragrunt Atlantis supports running custom commands in place of the default Atlantis diff --git a/runatlantis.io/docs/server-configuration.md b/runatlantis.io/docs/server-configuration.md index 98a83f4e7d..ccdd328330 100644 --- a/runatlantis.io/docs/server-configuration.md +++ b/runatlantis.io/docs/server-configuration.md @@ -626,6 +626,16 @@ This is useful when you have many projects and want to keep the pull request cle Hide previous plan comments to declutter PRs. This is only supported in GitHub and GitLab currently. This is not enabled by default. +### `--include-git-untracked-files` + ```bash + atlantis server --include-git-untracked-files + # or + ATLANTIS_INCLUDE_GIT_UNTRACKED_FILES=true + ``` + Include git untracked files in the Atlantis modified file list. + Used for example with CDKTF pre-workflow hooks that dynamically generate + Terraform files. + ### `--locking-db-type` ```bash atlantis server --locking-db-type="" diff --git a/server/controllers/events/events_controller_e2e_test.go b/server/controllers/events/events_controller_e2e_test.go index 551851ab3a..14b1f02c0d 100644 --- a/server/controllers/events/events_controller_e2e_test.go +++ b/server/controllers/events/events_controller_e2e_test.go @@ -1323,6 +1323,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", false, false, + false, statsScope, logger, terraformClient, diff --git a/server/events/mock_workingdir_test.go b/server/events/mock_workingdir_test.go index 046b9a2034..27a0695ea5 100644 --- a/server/events/mock_workingdir_test.go +++ b/server/events/mock_workingdir_test.go @@ -93,6 +93,25 @@ func (mock *MockWorkingDir) DeletePlan(r models.Repo, p models.PullRequest, work return ret0 } +func (mock *MockWorkingDir) GetGitUntrackedFiles(r models.Repo, p models.PullRequest, workspace string) ([]string, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockWorkingDir().") + } + params := []pegomock.Param{r, p, workspace} + result := pegomock.GetGenericMockFrom(mock).Invoke("GetGitUntrackedFiles", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []string + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]string) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + func (mock *MockWorkingDir) GetPullDir(r models.Repo, p models.PullRequest) (string, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockWorkingDir().") @@ -335,6 +354,41 @@ func (c *MockWorkingDir_DeletePlan_OngoingVerification) GetAllCapturedArguments( return } +func (verifier *VerifierMockWorkingDir) GetGitUntrackedFiles(r models.Repo, p models.PullRequest, workspace string) *MockWorkingDir_GetGitUntrackedFiles_OngoingVerification { + params := []pegomock.Param{r, p, workspace} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetGitUntrackedFiles", params, verifier.timeout) + return &MockWorkingDir_GetGitUntrackedFiles_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MockWorkingDir_GetGitUntrackedFiles_OngoingVerification struct { + mock *MockWorkingDir + methodInvocations []pegomock.MethodInvocation +} + +func (c *MockWorkingDir_GetGitUntrackedFiles_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, string) { + r, p, workspace := c.GetAllCapturedArguments() + return r[len(r)-1], p[len(p)-1], workspace[len(workspace)-1] +} + +func (c *MockWorkingDir_GetGitUntrackedFiles_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]models.Repo, len(c.methodInvocations)) + for u, param := range params[0] { + _param0[u] = param.(models.Repo) + } + _param1 = make([]models.PullRequest, len(c.methodInvocations)) + for u, param := range params[1] { + _param1[u] = param.(models.PullRequest) + } + _param2 = make([]string, len(c.methodInvocations)) + for u, param := range params[2] { + _param2[u] = param.(string) + } + } + return +} + func (verifier *VerifierMockWorkingDir) GetPullDir(r models.Repo, p models.PullRequest) *MockWorkingDir_GetPullDir_OngoingVerification { params := []pegomock.Param{r, p} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetPullDir", params, verifier.timeout) diff --git a/server/events/mocks/mock_working_dir.go b/server/events/mocks/mock_working_dir.go index e22c2ee921..8f051f1937 100644 --- a/server/events/mocks/mock_working_dir.go +++ b/server/events/mocks/mock_working_dir.go @@ -93,6 +93,25 @@ func (mock *MockWorkingDir) DeletePlan(r models.Repo, p models.PullRequest, work return ret0 } +func (mock *MockWorkingDir) GetGitUntrackedFiles(r models.Repo, p models.PullRequest, workspace string) ([]string, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockWorkingDir().") + } + params := []pegomock.Param{r, p, workspace} + result := pegomock.GetGenericMockFrom(mock).Invoke("GetGitUntrackedFiles", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []string + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]string) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + func (mock *MockWorkingDir) GetPullDir(r models.Repo, p models.PullRequest) (string, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockWorkingDir().") @@ -335,6 +354,41 @@ func (c *MockWorkingDir_DeletePlan_OngoingVerification) GetAllCapturedArguments( return } +func (verifier *VerifierMockWorkingDir) GetGitUntrackedFiles(r models.Repo, p models.PullRequest, workspace string) *MockWorkingDir_GetGitUntrackedFiles_OngoingVerification { + params := []pegomock.Param{r, p, workspace} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetGitUntrackedFiles", params, verifier.timeout) + return &MockWorkingDir_GetGitUntrackedFiles_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MockWorkingDir_GetGitUntrackedFiles_OngoingVerification struct { + mock *MockWorkingDir + methodInvocations []pegomock.MethodInvocation +} + +func (c *MockWorkingDir_GetGitUntrackedFiles_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, string) { + r, p, workspace := c.GetAllCapturedArguments() + return r[len(r)-1], p[len(p)-1], workspace[len(workspace)-1] +} + +func (c *MockWorkingDir_GetGitUntrackedFiles_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]models.Repo, len(c.methodInvocations)) + for u, param := range params[0] { + _param0[u] = param.(models.Repo) + } + _param1 = make([]models.PullRequest, len(c.methodInvocations)) + for u, param := range params[1] { + _param1[u] = param.(models.PullRequest) + } + _param2 = make([]string, len(c.methodInvocations)) + for u, param := range params[2] { + _param2[u] = param.(string) + } + } + return +} + func (verifier *VerifierMockWorkingDir) GetPullDir(r models.Repo, p models.PullRequest) *MockWorkingDir_GetPullDir_OngoingVerification { params := []pegomock.Param{r, p} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetPullDir", params, verifier.timeout) diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index 2b56d60a6b..2347c9072a 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -53,6 +53,7 @@ func NewInstrumentedProjectCommandBuilder( AutoplanFileList string, RestrictFileList bool, SilenceNoProjects bool, + IncludeGitUntrackedFiles bool, scope tally.Scope, logger logging.SimpleLogging, terraformClient terraform.Client, @@ -83,6 +84,7 @@ func NewInstrumentedProjectCommandBuilder( AutoplanFileList, RestrictFileList, SilenceNoProjects, + IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -111,27 +113,29 @@ func NewProjectCommandBuilder( AutoplanFileList string, RestrictFileList bool, SilenceNoProjects bool, + IncludeGitUntrackedFiles bool, scope tally.Scope, logger logging.SimpleLogging, terraformClient terraform.Client, ) *DefaultProjectCommandBuilder { return &DefaultProjectCommandBuilder{ - ParserValidator: parserValidator, - ProjectFinder: projectFinder, - VCSClient: vcsClient, - WorkingDir: workingDir, - WorkingDirLocker: workingDirLocker, - GlobalCfg: globalCfg, - PendingPlanFinder: pendingPlanFinder, - SkipCloneNoChanges: skipCloneNoChanges, - EnableRegExpCmd: EnableRegExpCmd, - EnableAutoMerge: EnableAutoMerge, - EnableParallelPlan: EnableParallelPlan, - EnableParallelApply: EnableParallelApply, - AutoDetectModuleFiles: AutoDetectModuleFiles, - AutoplanFileList: AutoplanFileList, - RestrictFileList: RestrictFileList, - SilenceNoProjects: SilenceNoProjects, + ParserValidator: parserValidator, + ProjectFinder: projectFinder, + VCSClient: vcsClient, + WorkingDir: workingDir, + WorkingDirLocker: workingDirLocker, + GlobalCfg: globalCfg, + PendingPlanFinder: pendingPlanFinder, + SkipCloneNoChanges: skipCloneNoChanges, + EnableRegExpCmd: EnableRegExpCmd, + EnableAutoMerge: EnableAutoMerge, + EnableParallelPlan: EnableParallelPlan, + EnableParallelApply: EnableParallelApply, + AutoDetectModuleFiles: AutoDetectModuleFiles, + AutoplanFileList: AutoplanFileList, + RestrictFileList: RestrictFileList, + SilenceNoProjects: SilenceNoProjects, + IncludeGitUntrackedFiles: IncludeGitUntrackedFiles, ProjectCommandContextBuilder: NewProjectCommandContextBuilder( policyChecksSupported, commentBuilder, @@ -184,7 +188,7 @@ type ProjectStateCommandBuilder interface { BuildStateRmCommands(ctx *command.Context, comment *CommentCommand) ([]command.ProjectContext, error) } -//go:generate pegomock generate --package mocks -o mocks/mock_project_command_builder.go ProjectCommandBuilder +//go:generate pegomock generate github.com/runatlantis/atlantis/server/events --package mocks -o mocks/mock_project_command_builder.go ProjectCommandBuilder // ProjectCommandBuilder builds commands that run on individual projects. type ProjectCommandBuilder interface { @@ -200,25 +204,46 @@ type ProjectCommandBuilder interface { // This class combines the data from the comment and any atlantis.yaml file or // Atlantis server config and then generates a set of contexts. type DefaultProjectCommandBuilder struct { - ParserValidator *config.ParserValidator - ProjectFinder ProjectFinder - VCSClient vcs.Client - WorkingDir WorkingDir - WorkingDirLocker WorkingDirLocker - GlobalCfg valid.GlobalCfg - PendingPlanFinder *DefaultPendingPlanFinder + // Parses and validates server-side repo config files and repo-level atlantis.yaml files. + ParserValidator *config.ParserValidator + // Determines which projects were modified in a given pull request. + ProjectFinder ProjectFinder + // Used to make API calls to a VCS host like GitHub or GitLab. + VCSClient vcs.Client + // Handles the workspace on disk for running commands. + WorkingDir WorkingDir + // Used to prevent multiple commands from executing at the same time for a single repo, pull, and workspace. + WorkingDirLocker WorkingDirLocker + // The final parsed version of the server-side repo config. + GlobalCfg valid.GlobalCfg + // Finds unapplied plans. + PendingPlanFinder *DefaultPendingPlanFinder + // Builds project command contexts for Atlantis commands. ProjectCommandContextBuilder ProjectCommandContextBuilder - SkipCloneNoChanges bool - EnableRegExpCmd bool - EnableAutoMerge bool - EnableParallelPlan bool - EnableParallelApply bool - AutoDetectModuleFiles string - AutoplanFileList string - EnableDiffMarkdownFormat bool - RestrictFileList bool - SilenceNoProjects bool - TerraformExecutor terraform.Client + // User config option: Skip cloning the repo during autoplan if there are no changes to Terraform projects. + SkipCloneNoChanges bool + // User config option: Enable the use of regular expressions to run plan/apply commands against defined project names. + EnableRegExpCmd bool + // User config option: Automatically merge pull requests after all plans have been successfully applied. + EnableAutoMerge bool + // User config option: Whether to run plan operations in parallel. + EnableParallelPlan bool + // User config option: Whether to run apply operations in parallel. + EnableParallelApply bool + // User config option: Enables auto-planning of projects when a module dependency in the same repository has changed. + AutoDetectModuleFiles string + // User config option: List of file patterns to to to check if a directory contains modified files. + AutoplanFileList string + // User config option: Format Terraform plan output into a markdown-diff friendy format for color-coding purposes. + EnableDiffMarkdownFormat bool + // User config option: Block plan requests from projects outside the files modified in the pull request. + RestrictFileList bool + // User config option: Ignore PR if none of the modified files are part of a project. + SilenceNoProjects bool + // User config option: Include git untracked files in the modified file list. + IncludeGitUntrackedFiles bool + // Handles the actual running of Terraform commands. + TerraformExecutor terraform.Client } // See ProjectCommandBuilder.BuildAutoplanCommands. @@ -241,8 +266,11 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *command.Contex // See ProjectCommandBuilder.BuildPlanCommands. func (p *DefaultProjectCommandBuilder) BuildPlanCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) { if !cmd.IsForSpecificProject() { + ctx.Log.Debug("Building plan command for all affected projects") return p.buildAllCommandsByCfg(ctx, cmd.CommandName(), cmd.SubName, cmd.Flags, cmd.Verbose) } + ctx.Log.Debug("Building plan command for specific project with directory: '%v', workspace: '%v', project: '%v'", + cmd.RepoRelDir, cmd.Workspace, cmd.ProjectName) pcc, err := p.buildProjectPlanCommand(ctx, cmd) return pcc, err } @@ -296,7 +324,17 @@ func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Contex if err != nil { return nil, err } - ctx.Log.Debug("%d files were modified in this pull request", len(modifiedFiles)) + + if p.IncludeGitUntrackedFiles { + ctx.Log.Debug(("'include-git-untracked-files' option is set, getting untracked files")) + untrackedFiles, err := p.WorkingDir.GetGitUntrackedFiles(ctx.HeadRepo, ctx.Pull, DefaultWorkspace) + if err != nil { + return nil, err + } + modifiedFiles = append(modifiedFiles, untrackedFiles...) + } + + ctx.Log.Debug("%d files were modified in this pull request. Modified files: %v", len(modifiedFiles), modifiedFiles) if p.SkipCloneNoChanges && p.VCSClient.SupportsSingleFileDownload(ctx.Pull.BaseRepo) { repoCfgFile := p.GlobalCfg.RepoConfigFile(ctx.Pull.BaseRepo.ID()) @@ -479,12 +517,25 @@ func (p *DefaultProjectCommandBuilder) buildProjectPlanCommand(ctx *command.Cont } if p.RestrictFileList { + ctx.Log.Debug("'restrict-file-list' option is set, checking modified files") modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.Pull.BaseRepo, ctx.Pull) if err != nil { return nil, err } + if p.IncludeGitUntrackedFiles { + ctx.Log.Debug(("'include-git-untracked-files' option is set, getting untracked files")) + untrackedFiles, err := p.WorkingDir.GetGitUntrackedFiles(ctx.HeadRepo, ctx.Pull, workspace) + if err != nil { + return nil, err + } + modifiedFiles = append(modifiedFiles, untrackedFiles...) + } + + ctx.Log.Debug("%d files were modified in this pull request. Modified files: %v", len(modifiedFiles), modifiedFiles) + if cmd.RepoRelDir != "" { + ctx.Log.Debug("Command directory specified: %s", cmd.RepoRelDir) foundDir := false for _, f := range modifiedFiles { @@ -499,6 +550,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectPlanCommand(ctx *command.Cont } if cmd.ProjectName != "" { + ctx.Log.Debug("Command project name specified: %s", cmd.ProjectName) var notFoundFiles = []string{} var repoConfig valid.RepoCfg diff --git a/server/events/project_command_builder_internal_test.go b/server/events/project_command_builder_internal_test.go index b0a4c8760d..e8804c5142 100644 --- a/server/events/project_command_builder_internal_test.go +++ b/server/events/project_command_builder_internal_test.go @@ -672,6 +672,7 @@ projects: "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", false, false, + false, statsScope, logger, terraformClient, @@ -886,6 +887,7 @@ projects: "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", false, false, + false, statsScope, logger, terraformClient, @@ -1134,6 +1136,7 @@ workflows: "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", false, false, + false, statsScope, logger, terraformClient, @@ -1289,6 +1292,7 @@ projects: "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", false, true, + false, statsScope, logger, terraformClient, diff --git a/server/events/project_command_builder_test.go b/server/events/project_command_builder_test.go index 36ed6426a0..6a3e73798d 100644 --- a/server/events/project_command_builder_test.go +++ b/server/events/project_command_builder_test.go @@ -22,6 +22,30 @@ import ( . "github.com/runatlantis/atlantis/testing" ) +var defaultUserConfig = struct { + SkipCloneNoChanges bool + EnableRegExpCmd bool + EnableAutoMerge bool + EnableParallelPlan bool + EnableParallelApply bool + AutoDetectModuleFiles string + AutoplanFileList string + RestrictFileList bool + SilenceNoProjects bool + IncludeGitUntrackedFiles bool +}{ + SkipCloneNoChanges: false, + EnableRegExpCmd: false, + EnableAutoMerge: false, + EnableParallelPlan: false, + EnableParallelApply: false, + AutoDetectModuleFiles: "", + AutoplanFileList: "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", + RestrictFileList: false, + SilenceNoProjects: false, + IncludeGitUntrackedFiles: true, +} + func TestDefaultProjectCommandBuilder_BuildAutoplanCommands(t *testing.T) { // expCtxFields define the ctx fields we're going to assert on. // Since we're focused on autoplanning here, we don't validate all the @@ -122,6 +146,7 @@ projects: logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig terraformClient := terraform_mocks.NewMockClient() When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) @@ -159,15 +184,16 @@ projects: valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - false, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -473,6 +499,7 @@ projects: logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig for _, c := range cases { // NOTE: we're testing both plan and apply here. @@ -513,15 +540,16 @@ projects: valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - true, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, c.EnableAutoMergeUserCfg, c.EnableParallelPlanUserCfg, c.EnableParallelApplyUserCfg, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, c.Silenced, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -663,6 +691,8 @@ projects: logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig + userConfig.RestrictFileList = true for _, c := range cases { t.Run(c.Description+"_"+command.Plan.String(), func(t *testing.T) { @@ -699,15 +729,16 @@ projects: valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - true, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - true, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -929,6 +960,8 @@ projects: logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig + for name, c := range cases { t.Run(name, func(t *testing.T) { RegisterMockTestingT(t) @@ -964,15 +997,16 @@ projects: valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, c.ParallelPlanEnabledUserCfg, c.ParallelApplyEnabledUserCfg, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -1044,6 +1078,7 @@ func TestDefaultProjectCommandBuilder_BuildMultiApply(t *testing.T) { ThenReturn(tmpDir, nil) logger := logging.NewNoopLogger(t) + userConfig := defaultUserConfig globalCfgArgs := valid.GlobalCfgArgs{ AllowRepoCfg: false, @@ -1066,15 +1101,16 @@ func TestDefaultProjectCommandBuilder_BuildMultiApply(t *testing.T) { valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - false, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -1145,6 +1181,8 @@ projects: } logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig + terraformClient := terraform_mocks.NewMockClient() When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) @@ -1158,15 +1196,16 @@ projects: valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - false, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -1212,6 +1251,7 @@ func TestDefaultProjectCommandBuilder_EscapeArgs(t *testing.T) { logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig for _, c := range cases { t.Run(strings.Join(c.ExtraArgs, " "), func(t *testing.T) { @@ -1246,15 +1286,16 @@ func TestDefaultProjectCommandBuilder_EscapeArgs(t *testing.T) { valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - false, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -1366,6 +1407,7 @@ projects: logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig for name, testCase := range testCases { t.Run(name, func(t *testing.T) { @@ -1415,15 +1457,16 @@ projects: valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - false, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -1482,6 +1525,9 @@ parallel_plan: true`, }, } + userConfig := defaultUserConfig + userConfig.SkipCloneNoChanges = true + for _, c := range cases { RegisterMockTestingT(t) vcsClient := vcsmocks.NewMockClient() @@ -1512,15 +1558,16 @@ parallel_plan: true`, valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - true, - false, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -1552,6 +1599,7 @@ func TestDefaultProjectCommandBuilder_WithPolicyCheckEnabled_BuildAutoplanComman logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig workingDir := mocks.NewMockWorkingDir() When(workingDir.Clone(Any[models.Repo](), Any[models.PullRequest](), Any[string]())).ThenReturn(tmpDir, false, nil) @@ -1580,15 +1628,16 @@ func TestDefaultProjectCommandBuilder_WithPolicyCheckEnabled_BuildAutoplanComman globalCfg, &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - false, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -1650,6 +1699,7 @@ func TestDefaultProjectCommandBuilder_BuildVersionCommand(t *testing.T) { logger := logging.NewNoopLogger(t) scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig globalCfgArgs := valid.GlobalCfgArgs{ AllowRepoCfg: false, @@ -1670,15 +1720,16 @@ func TestDefaultProjectCommandBuilder_BuildVersionCommand(t *testing.T) { valid.NewGlobalCfgFromArgs(globalCfgArgs), &events.DefaultPendingPlanFinder{}, &events.CommentParser{ExecutableName: "atlantis"}, - false, - false, - false, - false, - false, - "", - "**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl", - false, - false, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, scope, logger, terraformClient, @@ -1708,3 +1759,238 @@ func TestDefaultProjectCommandBuilder_BuildVersionCommand(t *testing.T) { Equals(t, "project2", ctxs[3].RepoRelDir) Equals(t, "workspace2", ctxs[3].Workspace) } + +// Test +func TestDefaultProjectCommandBuilder_BuildPlanCommands_Single_With_RestrictFileList_And_IncludeGitUntrackedFiles(t *testing.T) { + testDir1 := "directory-1" + testDir2 := "directory-2" + + cases := []struct { + Description string + AtlantisYAML string + DirectoryStructure map[string]interface{} + ModifiedFiles []string + UntrackedFiles []string + Cmd events.CommentCommand + ExpRepoRelDir string + ExpErr string + }{ + { + Description: "planning a git untracked file project in a modified directory", + Cmd: events.CommentCommand{ + Name: command.Plan, + RepoRelDir: testDir1 + "/ci-cdktf.out/stacks/test", + Workspace: "default", + }, + DirectoryStructure: map[string]interface{}{ + testDir1: map[string]interface{}{ + "main.ts": nil, + }, + }, + ModifiedFiles: []string{testDir1 + "/main.ts"}, + UntrackedFiles: []string{testDir1 + "/ci-cdktf.out/stacks/test/cdk.tf.json"}, + ExpRepoRelDir: testDir1 + "/ci-cdktf.out/stacks/test", + }, + { + Description: "planning a git untracked file project outside a modified directory", + Cmd: events.CommentCommand{ + Name: command.Plan, + RepoRelDir: testDir2 + "/ci-cdktf.out/stacks/test", + Workspace: "default", + }, + DirectoryStructure: map[string]interface{}{ + testDir1: map[string]interface{}{ + "main.ts": nil, + }, + }, + ModifiedFiles: []string{testDir1 + "/main.ts"}, + UntrackedFiles: []string{testDir1 + "/ci-cdktf.out/stacks/test/cdk.tf.json"}, + ExpErr: "the dir \"" + testDir2 + "/ci-cdktf.out/stacks/test\" is not in the plan list of this pull request", + }, + } + + globalCfgArgs := valid.GlobalCfgArgs{ + AllowRepoCfg: true, + MergeableReq: false, + ApprovedReq: false, + UnDivergedReq: false, + } + + logger := logging.NewNoopLogger(t) + scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig + userConfig.RestrictFileList = true + userConfig.IncludeGitUntrackedFiles = true + + for _, c := range cases { + t.Run(c.Description+"_"+command.Plan.String(), func(t *testing.T) { + RegisterMockTestingT(t) + tmpDir := DirStructure(t, c.DirectoryStructure) + + workingDir := mocks.NewMockWorkingDir() + When(workingDir.Clone(Any[models.Repo](), Any[models.PullRequest](), Any[string]())).ThenReturn(tmpDir, false, nil) + When(workingDir.GetWorkingDir(Any[models.Repo](), Any[models.PullRequest](), Any[string]())).ThenReturn(tmpDir, nil) + When(workingDir.GetGitUntrackedFiles(Any[models.Repo](), Any[models.PullRequest](), Any[string]())).ThenReturn(c.UntrackedFiles, nil) + vcsClient := vcsmocks.NewMockClient() + When(vcsClient.GetModifiedFiles(Any[models.Repo](), Any[models.PullRequest]())).ThenReturn(c.ModifiedFiles, nil) + if c.AtlantisYAML != "" { + err := os.WriteFile(filepath.Join(tmpDir, valid.DefaultAtlantisFile), []byte(c.AtlantisYAML), 0600) + Ok(t, err) + } + + terraformClient := terraform_mocks.NewMockClient() + When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) + + builder := events.NewProjectCommandBuilder( + false, // policyChecksSupported + &config.ParserValidator{}, + &events.DefaultProjectFinder{}, + vcsClient, + workingDir, + events.NewDefaultWorkingDirLocker(), + valid.NewGlobalCfgFromArgs(globalCfgArgs), + &events.DefaultPendingPlanFinder{}, + &events.CommentParser{ExecutableName: "atlantis"}, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, + scope, + logger, + terraformClient, + ) + + var actCtxs []command.ProjectContext + var err error + actCtxs, err = builder.BuildPlanCommands(&command.Context{ + Log: logger, + Scope: scope, + }, &c.Cmd) + if c.ExpErr != "" { + ErrEquals(t, c.ExpErr, err) + return + } + Ok(t, err) + Equals(t, 1, len(actCtxs)) + actCtx := actCtxs[0] + Equals(t, c.ExpRepoRelDir, actCtx.RepoRelDir) + }) + } +} + +func TestDefaultProjectCommandBuilder_BuildPlanCommands_with_IncludeGitUntrackedFiles(t *testing.T) { + testDir1 := "directory-1" + + cases := []struct { + Description string + AtlantisYAML string + DirectoryStructure map[string]interface{} + ModifiedFiles []string + UntrackedFiles []string + Cmd events.CommentCommand + ExpRepoRelDir string + ExpErr string + }{ + { + Description: "planning with a git untracked file", + Cmd: events.CommentCommand{ + Name: command.Plan, + }, + DirectoryStructure: map[string]interface{}{ + testDir1: map[string]interface{}{ + "main.ts": nil, + "ci-cdktf.out": map[string]interface{}{ + "stacks": map[string]interface{}{ + "test": map[string]interface{}{ + "cdk.tf.json": nil, + }, + }, + }, + }, + }, + ModifiedFiles: []string{testDir1 + "/main.ts"}, + UntrackedFiles: []string{testDir1 + "/ci-cdktf.out/stacks/test/cdk.tf.json"}, + ExpRepoRelDir: testDir1 + "/ci-cdktf.out/stacks/test", + }, + } + + globalCfgArgs := valid.GlobalCfgArgs{ + AllowRepoCfg: true, + MergeableReq: false, + ApprovedReq: false, + UnDivergedReq: false, + } + + logger := logging.NewNoopLogger(t) + scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") + userConfig := defaultUserConfig + userConfig.IncludeGitUntrackedFiles = true + userConfig.AutoplanFileList = "**/cdk.tf.json" + + for _, c := range cases { + t.Run(c.Description+"_"+command.Plan.String(), func(t *testing.T) { + RegisterMockTestingT(t) + tmpDir := DirStructure(t, c.DirectoryStructure) + + workingDir := mocks.NewMockWorkingDir() + When(workingDir.Clone(Any[models.Repo](), Any[models.PullRequest](), Any[string]())).ThenReturn(tmpDir, false, nil) + When(workingDir.GetWorkingDir(Any[models.Repo](), Any[models.PullRequest](), Any[string]())).ThenReturn(tmpDir, nil) + When(workingDir.GetGitUntrackedFiles(Any[models.Repo](), Any[models.PullRequest](), Any[string]())).ThenReturn(c.UntrackedFiles, nil) + vcsClient := vcsmocks.NewMockClient() + When(vcsClient.GetModifiedFiles(Any[models.Repo](), Any[models.PullRequest]())).ThenReturn(c.ModifiedFiles, nil) + if c.AtlantisYAML != "" { + err := os.WriteFile(filepath.Join(tmpDir, valid.DefaultAtlantisFile), []byte(c.AtlantisYAML), 0600) + Ok(t, err) + } + + terraformClient := terraform_mocks.NewMockClient() + When(terraformClient.ListAvailableVersions(Any[logging.SimpleLogging]())).ThenReturn([]string{}, nil) + + builder := events.NewProjectCommandBuilder( + false, // policyChecksSupported + &config.ParserValidator{}, + &events.DefaultProjectFinder{}, + vcsClient, + workingDir, + events.NewDefaultWorkingDirLocker(), + valid.NewGlobalCfgFromArgs(globalCfgArgs), + &events.DefaultPendingPlanFinder{}, + &events.CommentParser{ExecutableName: "atlantis"}, + userConfig.SkipCloneNoChanges, + userConfig.EnableRegExpCmd, + userConfig.EnableAutoMerge, + userConfig.EnableParallelPlan, + userConfig.EnableParallelApply, + userConfig.AutoDetectModuleFiles, + userConfig.AutoplanFileList, + userConfig.RestrictFileList, + userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, + scope, + logger, + terraformClient, + ) + + var actCtxs []command.ProjectContext + var err error + actCtxs, err = builder.BuildPlanCommands(&command.Context{ + Log: logger, + Scope: scope, + }, &c.Cmd) + if c.ExpErr != "" { + ErrEquals(t, c.ExpErr, err) + return + } + Ok(t, err) + Equals(t, 1, len(actCtxs)) + actCtx := actCtxs[0] + Equals(t, c.ExpRepoRelDir, actCtx.RepoRelDir) + }) + } +} diff --git a/server/events/project_finder.go b/server/events/project_finder.go index 661495faf6..0321701b45 100644 --- a/server/events/project_finder.go +++ b/server/events/project_finder.go @@ -144,7 +144,7 @@ func (p *DefaultProjectFinder) DetermineProjects(log logging.SimpleLogging, modi if len(modifiedTerraformFiles) == 0 { return projects } - log.Info("filtered modified files to %d .tf or terragrunt.hcl files: %v", + log.Info("filtered modified files to %d file(s) in the autoplan file list: %v", len(modifiedTerraformFiles), modifiedTerraformFiles) var dirs []string diff --git a/server/events/working_dir.go b/server/events/working_dir.go index db016dce17..65fd28a304 100644 --- a/server/events/working_dir.go +++ b/server/events/working_dir.go @@ -32,8 +32,8 @@ const workingDirPrefix = "repos" var cloneLocks sync.Map -//go:generate pegomock generate --package mocks -o mocks/mock_working_dir.go WorkingDir -//go:generate pegomock generate --package events WorkingDir +//go:generate pegomock generate github.com/runatlantis/atlantis/server/events --package mocks -o mocks/mock_working_dir.go WorkingDir +//go:generate pegomock generate github.com/runatlantis/atlantis/server/events --package events WorkingDir // WorkingDir handles the workspace on disk for running commands. type WorkingDir interface { @@ -56,6 +56,8 @@ type WorkingDir interface { SetSafeToReClone() // DeletePlan deletes the plan for this repo, pull, workspace path and project name DeletePlan(r models.Repo, p models.PullRequest, workspace string, path string, projectName string) error + // GetGitUntrackedFiles returns a list of Git untracked files in the working dir. + GetGitUntrackedFiles(r models.Repo, p models.PullRequest, workspace string) ([]string, error) } // FileWorkspace implements WorkingDir with the file system. @@ -382,3 +384,24 @@ func (w *FileWorkspace) DeletePlan(r models.Repo, p models.PullRequest, workspac w.Logger.Info("Deleting plan: " + planPath) return os.Remove(planPath) } + +// getGitUntrackedFiles returns a list of Git untracked files in the working dir. +func (w *FileWorkspace) GetGitUntrackedFiles(r models.Repo, p models.PullRequest, workspace string) ([]string, error) { + workingDir, err := w.GetWorkingDir(r, p, workspace) + if err != nil { + return nil, err + } + + w.Logger.Debug("Checking for Git untracked files in directory: '%s'", workingDir) + cmd := exec.Command("git", "ls-files", "--others", "--exclude-standard") + cmd.Dir = workingDir + + output, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + + untrackedFiles := strings.Split(string(output), "\n")[:] + w.Logger.Debug("Untracked files: '%s'", strings.Join(untrackedFiles, ",")) + return untrackedFiles, nil +} diff --git a/server/server.go b/server/server.go index 380af5baa0..c93ed170d7 100644 --- a/server/server.go +++ b/server/server.go @@ -596,6 +596,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { userConfig.AutoplanFileList, userConfig.RestrictFileList, userConfig.SilenceNoProjects, + userConfig.IncludeGitUntrackedFiles, statsScope, logger, terraformClient, diff --git a/server/user_config.go b/server/user_config.go index 5552df6a2a..7104b2df5a 100644 --- a/server/user_config.go +++ b/server/user_config.go @@ -60,6 +60,7 @@ type UserConfig struct { GitlabToken string `mapstructure:"gitlab-token"` GitlabUser string `mapstructure:"gitlab-user"` GitlabWebhookSecret string `mapstructure:"gitlab-webhook-secret"` + IncludeGitUntrackedFiles bool `mapstructure:"include-git-untracked-files"` APISecret string `mapstructure:"api-secret"` HidePrevPlanComments bool `mapstructure:"hide-prev-plan-comments"` LockingDBType string `mapstructure:"locking-db-type"` From b459645d031f9f02143c74fa90e105dbc1521715 Mon Sep 17 00:00:00 2001 From: Simon Heather <32168619+X-Guardian@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:00:12 +0100 Subject: [PATCH 03/35] fix: Running 'atlantis unlock' on a PR Causes The Whole Working Directory to be Deleted (#3751) * Running 'atlantis unlock' on a PR Causes The Whole Working Directory to be Deleted * Update events controller tests * Fix events controller tests --------- Co-authored-by: PePe Amengual --- .../controllers/events/events_controller.go | 3 +- .../events/events_controller_test.go | 32 +++++--- server/events/delete_lock_command.go | 35 +++------ server/events/delete_lock_command_test.go | 77 +++++++++++++++++-- server/events/unlock_command_runner.go | 8 +- 5 files changed, 110 insertions(+), 45 deletions(-) diff --git a/server/controllers/events/events_controller.go b/server/controllers/events/events_controller.go index 5268ec6322..10b8daf1f5 100644 --- a/server/controllers/events/events_controller.go +++ b/server/controllers/events/events_controller.go @@ -587,7 +587,8 @@ func (e *VCSEventsController) handleCommentEvent(logger logging.SimpleLogging, b } } - logger.Debug("executing command") + logger.Info("Running comment command '%v' on repo '%v', pull request: %v for user '%v'.", + parseResult.Command.Name, baseRepo.FullName, pullNum, user.Username) if !e.TestingMode { // Respond with success and then actually execute the command asynchronously. // We use a goroutine so that this function returns and the connection is diff --git a/server/controllers/events/events_controller_test.go b/server/controllers/events/events_controller_test.go index ae6b36e0bd..d018880aaf 100644 --- a/server/controllers/events/events_controller_test.go +++ b/server/controllers/events/events_controller_test.go @@ -31,6 +31,7 @@ import ( events_controllers "github.com/runatlantis/atlantis/server/controllers/events" "github.com/runatlantis/atlantis/server/controllers/events/mocks" "github.com/runatlantis/atlantis/server/events" + "github.com/runatlantis/atlantis/server/events/command" emocks "github.com/runatlantis/atlantis/server/events/mocks" "github.com/runatlantis/atlantis/server/events/models" vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks" @@ -347,15 +348,17 @@ func TestPost_GithubCommentResponse(t *testing.T) { func TestPost_GitlabCommentSuccess(t *testing.T) { t.Log("when the event is a gitlab comment with a valid command we call the command handler") - e, _, gl, _, _, cr, _, _, _ := setup(t) + e, _, gl, _, _, cr, _, _, cp := setup(t) req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil)) req.Header.Set(gitlabHeader, "value") + cmd := events.CommentCommand{} When(gl.ParseAndValidate(req, secret)).ThenReturn(gitlab.MergeCommentEvent{}, nil) + When(cp.Parse(Any[string](), Eq(models.Gitlab))).ThenReturn(events.CommentParseResult{Command: &cmd}) w := httptest.NewRecorder() e.Post(w, req) ResponseContains(t, w, http.StatusOK, "Processing...") - cr.VerifyWasCalledOnce().RunCommentCommand(models.Repo{}, &models.Repo{}, nil, models.User{}, 0, nil) + cr.VerifyWasCalledOnce().RunCommentCommand(models.Repo{}, &models.Repo{}, nil, models.User{}, 0, &cmd) } func TestPost_GithubCommentSuccess(t *testing.T) { @@ -378,17 +381,18 @@ func TestPost_GithubCommentSuccess(t *testing.T) { } func TestPost_GithubCommentReaction(t *testing.T) { - t.Log("when the event is a github comment with a valid command we call the command handler") + t.Log("when the event is a github comment with a valid command we call the ReactToComment handler") e, v, _, _, p, _, _, vcsClient, cp := setup(t) req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil)) req.Header.Set(githubHeader, "issue_comment") - event := `{"action": "created", "comment": {"body": "@atlantis-bot help", "id": 1}}` + testComment := "atlantis plan" + event := fmt.Sprintf(`{"action": "created", "comment": {"body": "%v", "id": 1}}`, testComment) When(v.Validate(req, secret)).ThenReturn([]byte(event), nil) baseRepo := models.Repo{} user := models.User{} - cmd := events.CommentCommand{} + cmd := events.CommentCommand{Name: command.Plan} When(p.ParseGithubIssueCommentEvent(Any[*github.IssueCommentEvent]())).ThenReturn(baseRepo, user, 1, nil) - When(cp.Parse("", models.Github)).ThenReturn(events.CommentParseResult{Command: &cmd}) + When(cp.Parse(testComment, models.Github)).ThenReturn(events.CommentParseResult{Command: &cmd}) w := httptest.NewRecorder() e.Post(w, req) ResponseContains(t, w, http.StatusOK, "Processing...") @@ -398,10 +402,12 @@ func TestPost_GithubCommentReaction(t *testing.T) { func TestPost_GilabCommentReaction(t *testing.T) { t.Log("when the event is a gitlab comment with a valid command we call the ReactToComment handler") - e, _, gl, _, _, _, _, vcsClient, _ := setup(t) + e, _, gl, _, _, _, _, vcsClient, cp := setup(t) req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil)) req.Header.Set(gitlabHeader, "value") + cmd := events.CommentCommand{} When(gl.ParseAndValidate(req, secret)).ThenReturn(gitlab.MergeCommentEvent{}, nil) + When(cp.Parse(Any[string](), Eq(models.Gitlab))).ThenReturn(events.CommentParseResult{Command: &cmd}) w := httptest.NewRecorder() e.Post(w, req) ResponseContains(t, w, http.StatusOK, "Processing...") @@ -685,12 +691,14 @@ func TestPost_AzureDevopsPullRequestWebhookTestIgnoreEvent(t *testing.T) { func TestPost_AzureDevopsPullRequestCommentPassingIgnores(t *testing.T) { t.Log("when the event should not be ignored it should pass through all ignore statements without error") - e, _, _, ado, _, _, _, _, _ := setup(t) + e, _, _, ado, _, _, _, _, cp := setup(t) + testComment := "atlantis plan" repo := models.Repo{} + cmd := events.CommentCommand{Name: command.Plan} When(e.Parser.ParseAzureDevopsRepo(Any[*azuredevops.GitRepository]())).ThenReturn(repo, nil) - - payload := `{ + When(cp.Parse(testComment, models.AzureDevops)).ThenReturn(events.CommentParseResult{Command: &cmd}) + payload := fmt.Sprintf(`{ "subscriptionId": "11111111-1111-1111-1111-111111111111", "notificationId": 1, "id": "22222222-2222-2222-2222-222222222222", @@ -703,14 +711,14 @@ func TestPost_AzureDevopsPullRequestCommentPassingIgnores(t *testing.T) { "comment": { "id": 1, "commentType": "text", - "content": "test" + "content": "%v" }, "pullRequest": { "pullRequestId": 1, "repository": {} } } - }` + }`, testComment) t.Run("Testing to see if comment passes ignore conditions", func(t *testing.T) { req, _ := http.NewRequest("GET", "", strings.NewReader(payload)) diff --git a/server/events/delete_lock_command.go b/server/events/delete_lock_command.go index ccd7f5d146..bf66370bb7 100644 --- a/server/events/delete_lock_command.go +++ b/server/events/delete_lock_command.go @@ -53,37 +53,22 @@ func (l *DefaultDeleteLockCommand) DeleteLocksByPull(repoFullName string, pullNu return numLocks, err } if numLocks == 0 { - l.Logger.Debug("No locks found for pull") + l.Logger.Debug("No locks found for repo '%v', pull request: %v", repoFullName, pullNum) return numLocks, nil } + // The locks controller currently has no implementation of Atlantis project names, so this is hardcoded to an empty string. + projectName := "" + for i := 0; i < numLocks; i++ { lock := locks[i] - l.deleteWorkingDir(lock) - } - - return numLocks, nil -} -func (l *DefaultDeleteLockCommand) deleteWorkingDir(lock models.ProjectLock) { - // NOTE: Because BaseRepo was added to the PullRequest model later, previous - // installations of Atlantis will have locks in their DB that do not have - // this field on PullRequest. We skip deleting the working dir in this case. - if lock.Pull.BaseRepo == (models.Repo{}) { - l.Logger.Debug("Not deleting the working dir.") - return - } - unlock, err := l.WorkingDirLocker.TryLock(lock.Pull.BaseRepo.FullName, lock.Pull.Num, lock.Workspace, lock.Project.Path) - if err != nil { - l.Logger.Err("unable to obtain working dir lock when trying to delete old plans: %s", err) - } else { - defer unlock() - // nolint: vetshadow - if err := l.WorkingDir.DeleteForWorkspace(lock.Pull.BaseRepo, lock.Pull, lock.Workspace); err != nil { - l.Logger.Err("unable to delete workspace: %s", err) + err := l.WorkingDir.DeletePlan(lock.Pull.BaseRepo, lock.Pull, lock.Workspace, lock.Project.Path, projectName) + if err != nil { + l.Logger.Warn("Failed to delete plan: %s", err) + return numLocks, err } } - if err := l.Backend.UpdateProjectStatus(lock.Pull, lock.Workspace, lock.Project.Path, models.DiscardedPlanStatus); err != nil { - l.Logger.Err("unable to delete project status: %s", err) - } + + return numLocks, nil } diff --git a/server/events/delete_lock_command_test.go b/server/events/delete_lock_command_test.go index ade6e64362..87d8478337 100644 --- a/server/events/delete_lock_command_test.go +++ b/server/events/delete_lock_command_test.go @@ -113,17 +113,84 @@ func TestDeleteLocksByPull_None(t *testing.T) { workingDir.VerifyWasCalled(Never()).DeletePlan(Any[models.Repo](), Any[models.PullRequest](), Any[string](), Any[string](), Any[string]()) } -func TestDeleteLocksByPull_OldFormat(t *testing.T) { - t.Log("If the lock doesn't have BaseRepo set it is deleted successfully") +func TestDeleteLocksByPull_SingleSuccess(t *testing.T) { + t.Log("If a single lock is successfully deleted") repoName := "reponame" pullNum := 2 + path := "." + workspace := "default" + projectName := "" + RegisterMockTestingT(t) l := lockmocks.NewMockLocker() - When(l.UnlockByPull(repoName, pullNum)).ThenReturn([]models.ProjectLock{{}}, nil) + workingDir := events.NewMockWorkingDir() + pull := models.PullRequest{ + BaseRepo: models.Repo{FullName: repoName}, + Num: pullNum, + } + When(l.UnlockByPull(repoName, pullNum)).ThenReturn([]models.ProjectLock{ + { + Pull: pull, + Workspace: workspace, + Project: models.Project{ + Path: path, + RepoFullName: pull.BaseRepo.FullName, + }, + }, + }, nil, + ) dlc := events.DefaultDeleteLockCommand{ - Locker: l, - Logger: logging.NewNoopLogger(t), + Locker: l, + Logger: logging.NewNoopLogger(t), + WorkingDir: workingDir, + } + _, err := dlc.DeleteLocksByPull(repoName, pullNum) + Ok(t, err) + workingDir.VerifyWasCalled(Once()).DeletePlan(pull.BaseRepo, pull, workspace, path, projectName) +} + +func TestDeleteLocksByPull_MultipleSuccess(t *testing.T) { + t.Log("If multiple locks are successfully deleted") + repoName := "reponame" + pullNum := 2 + path1 := "path1" + path2 := "path2" + workspace := "default" + projectName := "" + + RegisterMockTestingT(t) + l := lockmocks.NewMockLocker() + workingDir := events.NewMockWorkingDir() + pull := models.PullRequest{ + BaseRepo: models.Repo{FullName: repoName}, + Num: pullNum, + } + When(l.UnlockByPull(repoName, pullNum)).ThenReturn([]models.ProjectLock{ + { + Pull: pull, + Workspace: workspace, + Project: models.Project{ + Path: path1, + RepoFullName: pull.BaseRepo.FullName, + }, + }, + { + Pull: pull, + Workspace: workspace, + Project: models.Project{ + Path: path2, + RepoFullName: pull.BaseRepo.FullName, + }, + }, + }, nil, + ) + dlc := events.DefaultDeleteLockCommand{ + Locker: l, + Logger: logging.NewNoopLogger(t), + WorkingDir: workingDir, } _, err := dlc.DeleteLocksByPull(repoName, pullNum) Ok(t, err) + workingDir.VerifyWasCalled(Once()).DeletePlan(pull.BaseRepo, pull, workspace, path1, projectName) + workingDir.VerifyWasCalled(Once()).DeletePlan(pull.BaseRepo, pull, workspace, path2, projectName) } diff --git a/server/events/unlock_command_runner.go b/server/events/unlock_command_runner.go index 1a7407397d..012da284ee 100644 --- a/server/events/unlock_command_runner.go +++ b/server/events/unlock_command_runner.go @@ -32,6 +32,7 @@ func (u *UnlockCommandRunner) Run( baseRepo := ctx.Pull.BaseRepo pullNum := ctx.Pull.Num + ctx.Log.Info("Unlocking all locks") vcsMessage := "All Atlantis locks for this PR have been unlocked and plans discarded" numLocks, err := u.deleteLockCommand.DeleteLocksByPull(baseRepo.FullName, pullNum) if err != nil { @@ -40,8 +41,11 @@ func (u *UnlockCommandRunner) Run( } // if there are no locks to delete, no errors, and SilenceNoProjects is enabled, don't comment - if err == nil && numLocks == 0 && u.SilenceNoProjects { - return + if err == nil && numLocks == 0 { + ctx.Log.Info("No locks to delete") + if u.SilenceNoProjects { + return + } } if commentErr := u.vcsClient.CreateComment(baseRepo, pullNum, vcsMessage, command.Unlock.String()); commentErr != nil { From 6a0512a579318f2e45656836fb88d362b1cd4947 Mon Sep 17 00:00:00 2001 From: Luke Massa Date: Mon, 11 Sep 2023 11:13:26 -0400 Subject: [PATCH 04/35] feat: add portability to command invocation unit tests (#3758) * Add portability to command invocation unit tests * Fix linting --- .../runtime/post_workflow_hook_runner_test.go | 7 +++-- .../runtime/pre_workflow_hook_runner_test.go | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/server/core/runtime/post_workflow_hook_runner_test.go b/server/core/runtime/post_workflow_hook_runner_test.go index f4db6fcf93..bbf22b4394 100644 --- a/server/core/runtime/post_workflow_hook_runner_test.go +++ b/server/core/runtime/post_workflow_hook_runner_test.go @@ -1,6 +1,7 @@ package runtime_test import ( + "fmt" "strings" "testing" @@ -18,6 +19,8 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) { defaultShell := "sh" defaultShellArgs := "-c" + defautShellCommandNotFoundErrorFormat := commandNotFoundErrorFormat(defaultShell) + defaultUnterminatedStringError := unterminatedStringError(defaultShell, defaultShellArgs) cases := []struct { Command string @@ -63,7 +66,7 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) { Command: "echo 'a", Shell: defaultShell, ShellArgs: defaultShellArgs, - ExpOut: "sh: 1: Syntax error: Unterminated quoted string\r\n", + ExpOut: defaultUnterminatedStringError, ExpErr: "exit status 2: running \"sh -c echo 'a\" in", ExpDescription: "", }, @@ -79,7 +82,7 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) { Command: "lkjlkj", Shell: defaultShell, ShellArgs: defaultShellArgs, - ExpOut: "sh: 1: lkjlkj: not found\r\n", + ExpOut: fmt.Sprintf(defautShellCommandNotFoundErrorFormat, "lkjlkj"), ExpErr: "exit status 127: running \"sh -c lkjlkj\" in", ExpDescription: "", }, diff --git a/server/core/runtime/pre_workflow_hook_runner_test.go b/server/core/runtime/pre_workflow_hook_runner_test.go index e51d49e58e..ad8659cfa2 100644 --- a/server/core/runtime/pre_workflow_hook_runner_test.go +++ b/server/core/runtime/pre_workflow_hook_runner_test.go @@ -1,6 +1,8 @@ package runtime_test import ( + "fmt" + goruntime "runtime" "strings" "testing" @@ -14,10 +16,31 @@ import ( . "github.com/runatlantis/atlantis/testing" ) +func commandNotFoundErrorFormat(shell string) string { + // TODO: Add more GOOSs. Also I haven't done too much testing + // maybe the output here depends on other factors as well + if goruntime.GOOS == "darwin" { + return fmt.Sprintf("%s: %%s: command not found\r\n", shell) + } + return fmt.Sprintf("%s: 1: %%s: not found\r\n", shell) + +} + +func unterminatedStringError(shell, shellArgs string) string { + // TODO: Add more GOOSs. Also I haven't done too much testing + // maybe the output here depends on other factors as well + if goruntime.GOOS == "darwin" { + return fmt.Sprintf("%s: %s: line 0: unexpected EOF while looking for matching `''\r\n%s: %s: line 1: syntax error: unexpected end of file\r\n", shell, shellArgs, shell, shellArgs) + } + return fmt.Sprintf("%s: 1: Syntax error: Unterminated quoted string\r\n", shell) +} + func TestPreWorkflowHookRunner_Run(t *testing.T) { defaultShell := "sh" defaultShellArgs := "-c" + defautShellCommandNotFoundErrorFormat := commandNotFoundErrorFormat(defaultShell) + defaultUnterminatedStringError := unterminatedStringError(defaultShell, defaultShellArgs) cases := []struct { Command string @@ -63,7 +86,7 @@ func TestPreWorkflowHookRunner_Run(t *testing.T) { Command: "echo 'a", Shell: defaultShell, ShellArgs: defaultShellArgs, - ExpOut: "sh: 1: Syntax error: Unterminated quoted string\r\n", + ExpOut: defaultUnterminatedStringError, ExpErr: "exit status 2: running \"sh -c echo 'a\" in", ExpDescription: "", }, @@ -79,7 +102,7 @@ func TestPreWorkflowHookRunner_Run(t *testing.T) { Command: "lkjlkj", Shell: defaultShell, ShellArgs: defaultShellArgs, - ExpOut: "sh: 1: lkjlkj: not found\r\n", + ExpOut: fmt.Sprintf(defautShellCommandNotFoundErrorFormat, "lkjlkj"), ExpErr: "exit status 127: running \"sh -c lkjlkj\" in", ExpDescription: "", }, From ca5aa856766042af0e7e53adbc0eb872b5024671 Mon Sep 17 00:00:00 2001 From: "Mulail Mohamed (Muley)" Date: Mon, 11 Sep 2023 22:19:52 +0700 Subject: [PATCH 05/35] docs(using-atlantis): add usage for -destroy flag in atlantis plan (#3755) * docs(using-atlantis): add usage for -destroy flag in atlantis plan Signed-off-by: Abdulla Mulail Mohamed * docs(using-atlantis): add closing to caution note Signed-off-by: Abdulla Mulail Mohamed * Update runatlantis.io/docs/using-atlantis.md Co-authored-by: PePe Amengual * Update runatlantis.io/docs/using-atlantis.md Co-authored-by: PePe Amengual * chore(atlantis): add example Signed-off-by: Abdulla Mulail Mohamed --------- Signed-off-by: Abdulla Mulail Mohamed Co-authored-by: PePe Amengual --- runatlantis.io/docs/using-atlantis.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/runatlantis.io/docs/using-atlantis.md b/runatlantis.io/docs/using-atlantis.md index de03dde278..8009440a14 100644 --- a/runatlantis.io/docs/using-atlantis.md +++ b/runatlantis.io/docs/using-atlantis.md @@ -79,6 +79,19 @@ atlantis plan -d dir -- -var foo='bar' ``` If you always need to append a certain flag, see [Custom Workflow Use Cases](custom-workflows.html#adding-extra-arguments-to-terraform-commands). +### Using the -destroy Flag + +#### Example +To perform a destructive plan that will destroy resources you can use the `-destroy` flag like this: + +```bash +atlantis plan -- -destroy +atlantis plan -d dir -- -destroy +``` +::: warning NOTE +The `-destroy` flag generates a destroy plan, If this plan is applied it can result in data loss or service disruptions. Ensure that you have thoroughly reviewed your Terraform configuration and intend to remove the specified resources before using this flag. +::: + --- ## atlantis apply ```bash From 79dfa83612cd65c4f97d35dd0d972a3e0029d67b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 02:30:42 +0000 Subject: [PATCH 06/35] fix(deps): update module github.com/bradleyfalzon/ghinstallation/v2 to v2.7.0 in go.mod (#3760) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 714d307e05..3125809113 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/alicebob/miniredis/v2 v2.30.5 - github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 + github.com/bradleyfalzon/ghinstallation/v2 v2.7.0 github.com/briandowns/spinner v1.23.0 github.com/cactus/go-statsd-client/v5 v5.1.0 github.com/go-ozzo/ozzo-validation v3.6.0+incompatible @@ -85,7 +85,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/go-github/v53 v53.2.0 // indirect + github.com/google/go-github/v55 v55.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 66fdcdf133..73803cdea5 100644 --- a/go.sum +++ b/go.sum @@ -23,7 +23,6 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -76,8 +75,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 h1:IRY7Xy588KylkoycsUhFpW7cdGpy5Y5BPsz4IfuJtGk= -github.com/bradleyfalzon/ghinstallation/v2 v2.6.0/go.mod h1:oQ3etOwN3TRH4EwgW5/7MxSVMGlMlzG/O8TU7eYdoSk= +github.com/bradleyfalzon/ghinstallation/v2 v2.7.0 h1:ranXaC3Zz/F6G/f0Joj3LrFp2OzOKfJZev5Q7OaMc88= +github.com/bradleyfalzon/ghinstallation/v2 v2.7.0/go.mod h1:ymxfmloxXBFXvvF1KpeUhOQM6Dfz9NYtfvTiJyk82UE= github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= @@ -202,13 +201,12 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= -github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= github.com/google/go-github/v54 v54.0.0 h1:OZdXwow4EAD5jEo5qg+dGFH2DpkyZvVsAehjvJuUL/c= github.com/google/go-github/v54 v54.0.0/go.mod h1:Sw1LXWHhXRZtzJ9LI5fyJg9wbQzYvFhW8W5P2yaAQ7s= +github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= +github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -593,7 +591,6 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -664,6 +661,7 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -672,6 +670,7 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -686,6 +685,7 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -839,7 +839,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From aa4c739ce4c2304cd31c98f96202d8c8e393d6aa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 01:14:49 +0000 Subject: [PATCH 07/35] chore(deps): update docker/build-push-action action to v5 in .github/workflows/testing-env-image.yml (#3762) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/atlantis-image.yml | 2 +- .github/workflows/testing-env-image.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/atlantis-image.yml b/.github/workflows/atlantis-image.yml index 66ab9607e6..335e17056a 100644 --- a/.github/workflows/atlantis-image.yml +++ b/.github/workflows/atlantis-image.yml @@ -107,7 +107,7 @@ jobs: - name: "Build ${{ env.PUSH == 'true' && 'and push' || '' }} ${{ env.DOCKER_REPO }} image" if: contains(fromJson('["push", "pull_request"]'), github.event_name) - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.github/workflows/testing-env-image.yml b/.github/workflows/testing-env-image.yml index e28e7e99b8..fa6b521d07 100644 --- a/.github/workflows/testing-env-image.yml +++ b/.github/workflows/testing-env-image.yml @@ -41,7 +41,7 @@ jobs: - run: echo "TODAY=$(date +"%Y.%m.%d")" >> $GITHUB_ENV - name: Build and push testing-env:${{env.TODAY}} image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: cache-from: type=gha cache-to: type=gha,mode=max From b6830d3f1805a937c8345475d9e98e38d25b74ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 03:51:33 +0000 Subject: [PATCH 08/35] fix(deps): update module github.com/go-playground/validator/v10 to v10.15.4 in go.mod (#3763) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3125809113..6dfc6a6da3 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/briandowns/spinner v1.23.0 github.com/cactus/go-statsd-client/v5 v5.1.0 github.com/go-ozzo/ozzo-validation v3.6.0+incompatible - github.com/go-playground/validator/v10 v10.15.3 + github.com/go-playground/validator/v10 v10.15.4 github.com/go-test/deep v1.1.0 github.com/golang-jwt/jwt/v5 v5.0.0 github.com/google/go-github/v54 v54.0.0 diff --git a/go.sum b/go.sum index 73803cdea5..2c38c0d0db 100644 --- a/go.sum +++ b/go.sum @@ -146,8 +146,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= -github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.15.4 h1:zMXza4EpOdooxPel5xDqXEdXG5r+WggpvnAKMsalBjs= +github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= From 73094ce8f55332f4bcb7c96c7462609e1a750bdb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 02:32:06 +0000 Subject: [PATCH 09/35] chore(deps): update docker/login-action action to v3 in .github/workflows/testing-env-image.yml (#3766) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/atlantis-image.yml | 2 +- .github/workflows/testing-env-image.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/atlantis-image.yml b/.github/workflows/atlantis-image.yml index 335e17056a..08bb3a94e2 100644 --- a/.github/workflows/atlantis-image.yml +++ b/.github/workflows/atlantis-image.yml @@ -94,7 +94,7 @@ jobs: # Suffix is not used here since there's no way to disable it above - name: Login to Packages Container registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/testing-env-image.yml b/.github/workflows/testing-env-image.yml index fa6b521d07..cc3b03fa69 100644 --- a/.github/workflows/testing-env-image.yml +++ b/.github/workflows/testing-env-image.yml @@ -33,7 +33,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Login to Packages Container registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} From ef33bac0b9556f637c43cef7acd9f7c62f3dc39d Mon Sep 17 00:00:00 2001 From: Finn Arne Gangstad Date: Fri, 15 Sep 2023 22:26:35 +0200 Subject: [PATCH 10/35] Latest terraform 1.3.* is now 1.3.10 (#3770) --- server/core/terraform/terraform_client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/terraform/terraform_client_test.go b/server/core/terraform/terraform_client_test.go index 73251f4aa5..29fccb4579 100644 --- a/server/core/terraform/terraform_client_test.go +++ b/server/core/terraform/terraform_client_test.go @@ -392,7 +392,7 @@ terraform { "<= 1.0": "1.0.0", // cannot use ~> 1.3 or ~> 1.0 since that is a moving target since it will always // resolve to the latest terraform 1.x - "~> 1.3.0": "1.3.9", + "~> 1.3.0": "1.3.10", } type testCase struct { From fa3134e3232c73e7385c12f10a060a0c807feb1d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 20:53:49 +0000 Subject: [PATCH 11/35] fix(deps): update module go.uber.org/zap to v1.26.0 in go.mod (#3768) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 6dfc6a6da3..671ef113f2 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997 github.com/xanzy/go-gitlab v0.91.1 go.etcd.io/bbolt v1.3.7 - go.uber.org/zap v1.25.0 + go.uber.org/zap v1.26.0 golang.org/x/term v0.12.0 golang.org/x/text v0.13.0 gopkg.in/yaml.v2 v2.4.0 @@ -72,7 +72,6 @@ require ( github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/benbjohnson/clock v1.3.5 // indirect 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 diff --git a/go.sum b/go.sum index 2c38c0d0db..c92c746f8a 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,6 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= -github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -481,8 +479,8 @@ go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= From f08c1389f3b0abb305adc3490cc1e59f0a420499 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 16 Sep 2023 01:26:13 +0000 Subject: [PATCH 12/35] chore(deps): update docker/metadata-action action to v5 in .github/workflows/atlantis-image.yml (#3771) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/atlantis-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/atlantis-image.yml b/.github/workflows/atlantis-image.yml index 08bb3a94e2..0b69b32f5a 100644 --- a/.github/workflows/atlantis-image.yml +++ b/.github/workflows/atlantis-image.yml @@ -62,7 +62,7 @@ jobs: # if it's v0.10.0 and debian, it will do v0.10.0-debian, latest-debian - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 env: SUFFIX: ${{ format('-{0}', matrix.image_type) }} with: From 0dbaa4281e15d825b0b4b8842d5ae4ddeff77a08 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 Sep 2023 03:04:56 +0000 Subject: [PATCH 13/35] chore(deps): update docker/setup-buildx-action action to v3 in .github/workflows/testing-env-image.yml (#3772) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/atlantis-image.yml | 2 +- .github/workflows/testing-env-image.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/atlantis-image.yml b/.github/workflows/atlantis-image.yml index 0b69b32f5a..efb1e64154 100644 --- a/.github/workflows/atlantis-image.yml +++ b/.github/workflows/atlantis-image.yml @@ -48,7 +48,7 @@ jobs: platforms: arm64,arm - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # https://github.com/docker/build-push-action/issues/761#issuecomment-1575006515 with: driver-opts: | diff --git a/.github/workflows/testing-env-image.yml b/.github/workflows/testing-env-image.yml index cc3b03fa69..3004fdc0d6 100644 --- a/.github/workflows/testing-env-image.yml +++ b/.github/workflows/testing-env-image.yml @@ -30,7 +30,7 @@ jobs: platforms: arm64,arm - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to Packages Container registry uses: docker/login-action@v3 From 75d118d5352c7a37be44b01187257deab582f7b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 01:35:39 +0000 Subject: [PATCH 14/35] chore(deps): update docker/setup-qemu-action action to v3 in .github/workflows/testing-env-image.yml (#3773) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/atlantis-image.yml | 2 +- .github/workflows/testing-env-image.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/atlantis-image.yml b/.github/workflows/atlantis-image.yml index efb1e64154..02829ed319 100644 --- a/.github/workflows/atlantis-image.yml +++ b/.github/workflows/atlantis-image.yml @@ -42,7 +42,7 @@ jobs: dockerfile: "Dockerfile" - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:latest platforms: arm64,arm diff --git a/.github/workflows/testing-env-image.yml b/.github/workflows/testing-env-image.yml index 3004fdc0d6..4c37a4587a 100644 --- a/.github/workflows/testing-env-image.yml +++ b/.github/workflows/testing-env-image.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:latest platforms: arm64,arm From 4745b165cbecaa9927a9300df42b76c883a8d437 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 03:05:51 +0000 Subject: [PATCH 15/35] chore(deps): update goreleaser/goreleaser-action action to v5 in .github/workflows/release.yml (#3774) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 056e366757..0f145aa005 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: go-version-file: go.mod - name: Run GoReleaser for stable release - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 if: (!contains(github.ref, 'pre')) with: version: v1.16.2 @@ -45,7 +45,7 @@ jobs: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Run GoReleaser for pre-release - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 if: contains(github.ref, 'pre') with: version: v1.16.2 From 5ff99510d0714e5d6bd06c5c4f6bcad27fdfee76 Mon Sep 17 00:00:00 2001 From: Alex-Mussell Date: Mon, 18 Sep 2023 20:49:53 +0100 Subject: [PATCH 16/35] docs: Update Terragrunt custom workflows docs to implement target plans, imports, and state removals (#3776) --- runatlantis.io/docs/custom-workflows.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/runatlantis.io/docs/custom-workflows.md b/runatlantis.io/docs/custom-workflows.md index a6ee35ab6a..f46981a620 100644 --- a/runatlantis.io/docs/custom-workflows.md +++ b/runatlantis.io/docs/custom-workflows.md @@ -279,9 +279,11 @@ workflows: name: TF_IN_AUTOMATION value: 'true' - run: - command: terragrunt plan -input=false -out=$PLANFILE - output: strip_refreshing - - run: terragrunt show -json $PLANFILE > $SHOWFILE + # Allow for targetted plans/applies as not supported for Terraform wrappers by default + command: terragrunt plan -input=false $(printf '%s' $COMMENT_ARGS | sed 's/,/ /g' | tr -d '\\') -no-color -out $PLANFILE + output: hide + - run: | + terragrunt show $PLANFILE apply: steps: - env: @@ -292,6 +294,23 @@ workflows: name: TF_IN_AUTOMATION value: 'true' - run: terragrunt apply -input=false $PLANFILE + import: + steps: + - env: + name: TERRAGRUNT_TFPATH + command: 'echo "terraform${DEFAULT_TERRAFORM_VERSION}"' + - env: + name: TF_VAR_author + command: 'git show -s --format="%ae" $HEAD_COMMIT' + # Allow for imports as not supported for Terraform wrappers by default + - run: terragrunt import -input=false $(printf '%s' $COMMENT_ARGS | sed 's/,/ /' | tr -d '\\') + state_rm: + steps: + - env: + name: TERRAGRUNT_TFPATH + command: 'echo "terraform${DEFAULT_TERRAFORM_VERSION}"' + # Allow for state removals as not supported for Terraform wrappers by default + - run: terragrunt state rm $(printf '%s' $COMMENT_ARGS | sed 's/,/ /' | tr -d '\\') ``` If using the repo's `atlantis.yaml` file you would use the following config: From 16d080be03b5ea275caa57733543c390286afe43 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 01:56:00 +0000 Subject: [PATCH 17/35] chore(deps): update dependency node to v18.18.0 in .node-version (#3779) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .node-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.node-version b/.node-version index 4a1f488b6c..02c8b485ed 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.17.1 +18.18.0 From 7cf6a670bf0b63c423cca4e8d42e8f2995f5972c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 00:33:39 +0000 Subject: [PATCH 18/35] fix(deps): update module github.com/redis/go-redis/v9 to v9.2.0 in go.mod (#3786) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 671ef113f2..e503311d3c 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/petergtz/pegomock/v4 v4.0.0 github.com/pkg/errors v0.9.1 - github.com/redis/go-redis/v9 v9.1.0 + github.com/redis/go-redis/v9 v9.2.0 github.com/remeh/sizedwaitgroup v1.0.0 github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278 github.com/slack-go/slack v0.12.3 diff --git a/go.sum b/go.sum index c92c746f8a..d09bea7b4a 100644 --- a/go.sum +++ b/go.sum @@ -77,10 +77,10 @@ github.com/bradleyfalzon/ghinstallation/v2 v2.7.0 h1:ranXaC3Zz/F6G/f0Joj3LrFp2Oz github.com/bradleyfalzon/ghinstallation/v2 v2.7.0/go.mod h1:ymxfmloxXBFXvvF1KpeUhOQM6Dfz9NYtfvTiJyk82UE= github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= -github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= -github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cactus/go-statsd-client/v5 v5.0.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= @@ -388,8 +388,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI= +github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= From 83964cc05c529ddc71c25a3761dae890b7e52ae2 Mon Sep 17 00:00:00 2001 From: PePe Amengual Date: Thu, 21 Sep 2023 14:49:33 -0700 Subject: [PATCH 19/35] Updating curl package (#3787) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 66a2a06c89..2e741ce186 100644 --- a/Dockerfile +++ b/Dockerfile @@ -172,7 +172,7 @@ COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh # We place this last as it will bust less docker layer caches when packages update RUN apk add --no-cache \ ca-certificates~=20230506 \ - curl~=8.2 \ + curl~=8.3 \ git~=2.40 \ unzip~=6.0 \ bash~=5.2 \ From c35ba0d69f8643aa218900c2651059d3ab4d9996 Mon Sep 17 00:00:00 2001 From: wolmi Date: Fri, 22 Sep 2023 23:42:58 +0200 Subject: [PATCH 20/35] feat: Add use plugin cache flag (#3720) * feat: Add use plugin cache bool flag * feat: Added use-plugin-cache doc * feat: refactor to reflect terrafrom in plugin cache flag Co-authored-by: Simon Heather <32168619+X-Guardian@users.noreply.github.com> * fix: missing closing in bash block documentation * fix: typo in flag example * feat: improve docs * feat: performance implications documented * feat: increase terraform minor version to 1.3.10 --------- Co-authored-by: Simon Heather <32168619+X-Guardian@users.noreply.github.com> --- Dockerfile | 2 +- cmd/server.go | 5 +++++ runatlantis.io/docs/server-configuration.md | 15 +++++++++++++++ server/server.go | 2 +- server/user_config.go | 1 + 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2e741ce186..b1c6406426 100644 --- a/Dockerfile +++ b/Dockerfile @@ -125,7 +125,7 @@ ENV DEFAULT_TERRAFORM_VERSION=1.5.7 # In the official Atlantis image, we only have the latest of each Terraform version. # Each binary is about 80 MB so we limit it to the 4 latest minor releases or fewer -RUN AVAILABLE_TERRAFORM_VERSIONS="1.2.9 1.3.9 1.4.6 ${DEFAULT_TERRAFORM_VERSION}" && \ +RUN AVAILABLE_TERRAFORM_VERSIONS="1.2.9 1.3.10 1.4.6 ${DEFAULT_TERRAFORM_VERSION}" && \ case "${TARGETPLATFORM}" in \ "linux/amd64") TERRAFORM_ARCH=amd64 ;; \ "linux/arm64") TERRAFORM_ARCH=arm64 ;; \ diff --git a/cmd/server.go b/cmd/server.go index e79012408c..19ed98763e 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -134,6 +134,7 @@ const ( RestrictFileList = "restrict-file-list" TFDownloadFlag = "tf-download" TFDownloadURLFlag = "tf-download-url" + UseTFPluginCache = "use-tf-plugin-cache" VarFileAllowlistFlag = "var-file-allowlist" VCSStatusName = "vcs-status-name" TFEHostnameFlag = "tfe-hostname" @@ -568,6 +569,10 @@ var boolFlags = map[string]boolFlag{ description: "Remove no-changes plan comments from the pull request.", defaultValue: false, }, + UseTFPluginCache: { + description: "Enable the use of the Terraform plugin cache", + defaultValue: true, + }, } var intFlags = map[string]intFlag{ CheckoutDepthFlag: { diff --git a/runatlantis.io/docs/server-configuration.md b/runatlantis.io/docs/server-configuration.md index ccdd328330..2e9cfe52fd 100644 --- a/runatlantis.io/docs/server-configuration.md +++ b/runatlantis.io/docs/server-configuration.md @@ -989,6 +989,21 @@ Setting this to `false` can be useful in an air-gapped environment where a downl ``` A token for Terraform Cloud/Terraform Enterprise integration. See [Terraform Cloud](terraform-cloud.html) for more details. +### `--use-tf-plugin-cache` +```bash +atlantis server --use-tf-plugin-cache=false +# or +ATLANTIS_USE_TF_PLUGIN_CACHE=false +``` +Set to false if you want to disable terraform plugin cache. + +This flag is useful when having multiple projects that need to run a plan and apply in the same PR to avoid the race condition of `plugin_cache_dir` concurrently, this is a terraform known issue, more info: + +- [plugin_cache_dir concurrently discussion](https://github.com/hashicorp/terraform/issues/31964) +- [PR to improve the situation](https://github.com/hashicorp/terraform/pull/33479) + +The effect of the race condition is more evident when using parallel configuration to run plan and apply, by disabling the use of plugin cache will impact in the performance when starting a new plan or apply, but in large atlantis deployments with multiple projects and shared modules the use of `--parallel_plan` and `--parallel_apply` is mandatory for an efficient managment of the PRs. + ### `--var-file-allowlist` ```bash atlantis server --var-file-allowlist='/path/to/tfvars/dir' diff --git a/server/server.go b/server/server.go index c93ed170d7..a2410a1315 100644 --- a/server/server.go +++ b/server/server.go @@ -413,7 +413,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { userConfig.TFDownloadURL, &terraform.DefaultDownloader{}, userConfig.TFDownload, - true, + userConfig.UseTFPluginCache, projectCmdOutputHandler) // The flag.Lookup call is to detect if we're running in a unit test. If we // are, then we don't error out because we don't have/want terraform diff --git a/server/user_config.go b/server/user_config.go index 7104b2df5a..edfc6dd1da 100644 --- a/server/user_config.go +++ b/server/user_config.go @@ -125,6 +125,7 @@ type UserConfig struct { WebPassword string `mapstructure:"web-password"` WriteGitCreds bool `mapstructure:"write-git-creds"` WebsocketCheckOrigin bool `mapstructure:"websocket-check-origin"` + UseTFPluginCache bool `mapstructure:"use-tf-plugin-cache"` } // ToAllowCommandNames parse AllowCommands into a slice of CommandName From fad6f0f9562d74509cc69cb8e8a1f169936652a4 Mon Sep 17 00:00:00 2001 From: Luke Massa Date: Mon, 25 Sep 2023 12:01:16 -0400 Subject: [PATCH 21/35] fix: Do not unnecessarily update apply check if it doesn't exist yet (#3747) * Do not unnecessarily create apply pipeline if it doesn't exist yet * Updates * Fix remaining * Fix test logic * Cleanup more tests * Fix test --------- Co-authored-by: PePe Amengual --- server/events/command_runner_internal_test.go | 38 +++++++------ server/events/plan_command_runner.go | 5 +- server/events/plan_command_runner_test.go | 55 +++++++++++-------- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/server/events/command_runner_internal_test.go b/server/events/command_runner_internal_test.go index 4cf3076a0f..f80c718802 100644 --- a/server/events/command_runner_internal_test.go +++ b/server/events/command_runner_internal_test.go @@ -166,11 +166,12 @@ func TestPlanUpdatePlanCommitStatus(t *testing.T) { func TestPlanUpdateApplyCommitStatus(t *testing.T) { cases := map[string]struct { - cmd command.Name - pullStatus models.PullStatus - expStatus models.CommitStatus - expNumSuccess int - expNumTotal int + cmd command.Name + pullStatus models.PullStatus + expStatus models.CommitStatus + doNotCallUpdateApply bool // In certain situations, we don't expect updateCommitStatus to call the underlying commitStatusUpdater code at all + expNumSuccess int + expNumTotal int }{ "all plans success with no changes": { cmd: command.Apply, @@ -200,9 +201,7 @@ func TestPlanUpdateApplyCommitStatus(t *testing.T) { }, }, }, - expStatus: models.PendingCommitStatus, - expNumSuccess: 1, - expNumTotal: 2, + doNotCallUpdateApply: true, }, "one plan, one apply, one plan success with no changes": { cmd: command.Apply, @@ -219,9 +218,7 @@ func TestPlanUpdateApplyCommitStatus(t *testing.T) { }, }, }, - expStatus: models.PendingCommitStatus, - expNumSuccess: 2, - expNumTotal: 3, + doNotCallUpdateApply: true, }, "one apply error, one apply, one plan success with no changes": { cmd: command.Apply, @@ -251,12 +248,17 @@ func TestPlanUpdateApplyCommitStatus(t *testing.T) { commitStatusUpdater: csu, } cr.updateCommitStatus(&command.Context{}, c.pullStatus, command.Apply) - Equals(t, models.Repo{}, csu.CalledRepo) - Equals(t, models.PullRequest{}, csu.CalledPull) - Equals(t, c.expStatus, csu.CalledStatus) - Equals(t, c.cmd, csu.CalledCommand) - Equals(t, c.expNumSuccess, csu.CalledNumSuccess) - Equals(t, c.expNumTotal, csu.CalledNumTotal) + if c.doNotCallUpdateApply { + Equals(t, csu.Called, false) + } else { + Equals(t, csu.Called, true) + Equals(t, models.Repo{}, csu.CalledRepo) + Equals(t, models.PullRequest{}, csu.CalledPull) + Equals(t, c.expStatus, csu.CalledStatus) + Equals(t, c.cmd, csu.CalledCommand) + Equals(t, c.expNumSuccess, csu.CalledNumSuccess) + Equals(t, c.expNumTotal, csu.CalledNumTotal) + } }) } } @@ -268,9 +270,11 @@ type MockCSU struct { CalledCommand command.Name CalledNumSuccess int CalledNumTotal int + Called bool } func (m *MockCSU) UpdateCombinedCount(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command command.Name, numSuccess int, numTotal int) error { + m.Called = true m.CalledRepo = repo m.CalledPull = pull m.CalledStatus = status diff --git a/server/events/plan_command_runner.go b/server/events/plan_command_runner.go index 9313f14d4c..8563cee13e 100644 --- a/server/events/plan_command_runner.go +++ b/server/events/plan_command_runner.go @@ -315,9 +315,8 @@ func (p *PlanCommandRunner) updateCommitStatus(ctx *command.Context, pullStatus if numErrored > 0 { status = models.FailedCommitStatus } else if numSuccess < len(pullStatus.Projects) { - // If there are plans that haven't been applied yet, we'll use a pending - // status. - status = models.PendingCommitStatus + // If there are plans that haven't been applied yet, no need to update the status + return } } diff --git a/server/events/plan_command_runner_test.go b/server/events/plan_command_runner_test.go index e765f434c5..8cc9f6c851 100644 --- a/server/events/plan_command_runner_test.go +++ b/server/events/plan_command_runner_test.go @@ -517,11 +517,12 @@ func TestPlanCommandRunner_AtlantisApplyStatus(t *testing.T) { ProjectContexts []command.ProjectContext ProjectResults []command.ProjectResult PrevPlanStored bool // stores a previous "No changes" plan in the backend + DoNotUpdateApply bool // certain circumtances we want to skip the call to update apply ExpVCSApplyStatusTotal int ExpVCSApplyStatusSucc int }{ { - Description: "When planning with changes, set the 0/1 apply status", + Description: "When planning with changes, do not change the apply status", ProjectContexts: []command.ProjectContext{ { CommandName: command.Plan, @@ -537,8 +538,7 @@ func TestPlanCommandRunner_AtlantisApplyStatus(t *testing.T) { }, }, }, - ExpVCSApplyStatusTotal: 1, - ExpVCSApplyStatusSucc: 0, + DoNotUpdateApply: true, }, { Description: "When planning with no changes, set the 1/1 apply status", @@ -561,7 +561,7 @@ func TestPlanCommandRunner_AtlantisApplyStatus(t *testing.T) { ExpVCSApplyStatusSucc: 1, }, { - Description: "When planning with no changes and previous plan with no changes, set the 1/2 apply status", + Description: "When planning with no changes and previous plan with no changes do not set the apply status", ProjectContexts: []command.ProjectContext{ { CommandName: command.Plan, @@ -577,9 +577,8 @@ func TestPlanCommandRunner_AtlantisApplyStatus(t *testing.T) { }, }, }, - PrevPlanStored: true, - ExpVCSApplyStatusTotal: 2, - ExpVCSApplyStatusSucc: 1, + DoNotUpdateApply: true, + PrevPlanStored: true, }, { Description: "When planning with no changes and previous 'No changes' plan, set the 2/2 apply status", @@ -603,7 +602,7 @@ func TestPlanCommandRunner_AtlantisApplyStatus(t *testing.T) { ExpVCSApplyStatusSucc: 2, }, { - Description: "When planning again with changes following a previous 'No changes' plan, set the 0/1 apply status", + Description: "When planning again with changes following a previous 'No changes' plan do not set the apply status", ProjectContexts: []command.ProjectContext{ { CommandName: command.Plan, @@ -621,12 +620,11 @@ func TestPlanCommandRunner_AtlantisApplyStatus(t *testing.T) { }, }, }, - PrevPlanStored: true, - ExpVCSApplyStatusTotal: 1, - ExpVCSApplyStatusSucc: 0, + DoNotUpdateApply: true, + PrevPlanStored: true, }, { - Description: "When planning again with changes following a previous 'No changes' plan, while another plan with 'No changes', set the 1/2 apply status.", + Description: "When planning again with changes following a previous 'No changes' plan, while another plan with 'No changes' do not set the apply status.", ProjectContexts: []command.ProjectContext{ { CommandName: command.Plan, @@ -655,9 +653,8 @@ func TestPlanCommandRunner_AtlantisApplyStatus(t *testing.T) { }, }, }, - PrevPlanStored: true, - ExpVCSApplyStatusTotal: 2, - ExpVCSApplyStatusSucc: 1, + DoNotUpdateApply: true, + PrevPlanStored: true, }, { Description: "When planning again with no changes following a previous 'No changes' plan, while another plan also with 'No changes', set the 2/2 apply status.", @@ -748,15 +745,25 @@ func TestPlanCommandRunner_AtlantisApplyStatus(t *testing.T) { if c.ExpVCSApplyStatusSucc != c.ExpVCSApplyStatusTotal { ExpCommitStatus = models.PendingCommitStatus } - - commitUpdater.VerifyWasCalledOnce().UpdateCombinedCount( - Any[models.Repo](), - Any[models.PullRequest](), - Eq[models.CommitStatus](ExpCommitStatus), - Eq[command.Name](command.Apply), - Eq(c.ExpVCSApplyStatusSucc), - Eq(c.ExpVCSApplyStatusTotal), - ) + if c.DoNotUpdateApply { + commitUpdater.VerifyWasCalled(Never()).UpdateCombinedCount( + Any[models.Repo](), + Any[models.PullRequest](), + Any[models.CommitStatus](), + Eq[command.Name](command.Apply), + AnyInt(), + AnyInt(), + ) + } else { + commitUpdater.VerifyWasCalledOnce().UpdateCombinedCount( + Any[models.Repo](), + Any[models.PullRequest](), + Eq[models.CommitStatus](ExpCommitStatus), + Eq[command.Name](command.Apply), + Eq(c.ExpVCSApplyStatusSucc), + Eq(c.ExpVCSApplyStatusTotal), + ) + } }) } } From 80ecc3827354b7feaa7281f9c7aa03d523eff406 Mon Sep 17 00:00:00 2001 From: Ghais Zaher Date: Mon, 25 Sep 2023 19:43:35 +0200 Subject: [PATCH 22/35] feat: disable autoplan label (#3649) * feat: disable autoplan label * documentation * revert unrelated change * fix property * gitlab and github * dd more test * small fixes * add tests for github and gitlab clients * fix: remove unrelated comments * fmt --------- Co-authored-by: PePe Amengual --- cmd/server.go | 5 + cmd/server_test.go | 1 + runatlantis.io/docs/server-configuration.md | 10 ++ server/events/command_runner.go | 10 ++ server/events/command_runner_test.go | 56 ++++++++- server/events/vcs/azuredevops_client.go | 4 + server/events/vcs/bitbucketcloud/client.go | 4 + server/events/vcs/bitbucketserver/client.go | 4 + server/events/vcs/client.go | 3 + server/events/vcs/github_client.go | 15 +++ server/events/vcs/github_client_test.go | 107 ++++++++++++++++++ server/events/vcs/gitlab_client.go | 10 ++ server/events/vcs/gitlab_client_test.go | 59 ++++++++++ server/events/vcs/mocks/mock_client.go | 50 ++++++++ .../events/vcs/not_configured_vcs_client.go | 4 + server/events/vcs/proxy.go | 4 + server/server.go | 1 + server/user_config.go | 1 + 18 files changed, 345 insertions(+), 3 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index 19ed98763e..5aeecbb909 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -72,6 +72,7 @@ const ( DisableApplyAllFlag = "disable-apply-all" DisableApplyFlag = "disable-apply" DisableAutoplanFlag = "disable-autoplan" + DisableAutoplanLabelFlag = "disable-autoplan-label" DisableMarkdownFoldingFlag = "disable-markdown-folding" DisableRepoLockingFlag = "disable-repo-locking" DiscardApprovalOnPlanFlag = "discard-approval-on-plan" @@ -257,6 +258,10 @@ var stringFlags = map[string]stringFlag{ description: "Path to directory to store Atlantis data.", defaultValue: DefaultDataDir, }, + DisableAutoplanLabelFlag: { + description: "Pull request label to disable atlantis auto planning feature only if present.", + defaultValue: "", + }, EmojiReaction: { description: "Emoji Reaction to use to react to comments", defaultValue: DefaultEmojiReaction, diff --git a/cmd/server_test.go b/cmd/server_test.go index a597fced70..b5bfba2574 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -115,6 +115,7 @@ var testFlags = map[string]interface{}{ VCSStatusName: "my-status", WriteGitCredsFlag: true, DisableAutoplanFlag: true, + DisableAutoplanLabelFlag: "no-auto-plan", EnablePolicyChecksFlag: false, EnableRegExpCmdFlag: false, EnableDiffMarkdownFormat: false, diff --git a/runatlantis.io/docs/server-configuration.md b/runatlantis.io/docs/server-configuration.md index 2e9cfe52fd..e4370c7046 100644 --- a/runatlantis.io/docs/server-configuration.md +++ b/runatlantis.io/docs/server-configuration.md @@ -355,6 +355,16 @@ and set `--autoplan-modules` to `false`. ``` Disable atlantis auto planning. +### `--disable-autoplan-label` + ```bash + atlantis server --disable-autoplan-label="no-autoplan" + # or + ATLANTIS_DISABLE_AUTOPLAN_LABEL="no-autoplan" + ``` + Disable atlantis auto planning only on pull requests with the specified label. + + If `disable-autoplan` property is `true`, this flag has no effect. + ### `--disable-markdown-folding` ```bash atlantis server --disable-markdown-folding diff --git a/server/events/command_runner.go b/server/events/command_runner.go index a61d63ee20..24d697392b 100644 --- a/server/events/command_runner.go +++ b/server/events/command_runner.go @@ -15,6 +15,7 @@ package events import ( "fmt" + "github.com/runatlantis/atlantis/server/utils" "strconv" "github.com/google/go-github/v54/github" @@ -98,6 +99,7 @@ type DefaultCommandRunner struct { GitlabMergeRequestGetter GitlabMergeRequestGetter // User config option: Disables autoplan when a pull request is opened or updated. DisableAutoplan bool + DisableAutoplanLabel string EventParser EventParsing // User config option: Fail and do not run the Atlantis command request if any of the pre workflow hooks error FailOnPreWorkflowHookError bool @@ -165,6 +167,14 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo if c.DisableAutoplan { return } + if len(c.DisableAutoplanLabel) > 0 { + labels, err := c.VCSClient.GetPullLabels(baseRepo, pull) + if err != nil { + ctx.Log.Err("Unable to get pull labels. Proceeding with %s command.", err, command.Plan) + } else if utils.SlicesContains(labels, c.DisableAutoplanLabel) { + return + } + } cmd := &CommentCommand{ Name: command.Autoplan, diff --git a/server/events/command_runner_test.go b/server/events/command_runner_test.go index 90995ba296..59b6a6b21d 100644 --- a/server/events/command_runner_test.go +++ b/server/events/command_runner_test.go @@ -496,9 +496,11 @@ func TestRunCommentCommand_DisableApplyAllDisabled(t *testing.T) { vcsClient.VerifyWasCalledOnce().CreateComment(testdata.GithubRepo, modelPull.Num, "**Error:** Running `atlantis apply` without flags is disabled. You must specify which project to apply via the `-d `, `-w ` or `-p ` flags.", "apply") } -func TestRunCommentCommand_DisableDisableAutoplan(t *testing.T) { - t.Log("if \"DisableAutoplan is true\" are disabled and we are silencing return and do not comment with error") +func TestRunCommentCommand_DisableAutoplan(t *testing.T) { + t.Log("if \"DisableAutoplan\" is true, auto plans are disabled and we are silencing return and do not comment with error") setup(t) + modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, BaseBranch: "main"} + ch.DisableAutoplan = true defer func() { ch.DisableAutoplan = false }() @@ -512,8 +514,56 @@ func TestRunCommentCommand_DisableDisableAutoplan(t *testing.T) { }, }, nil) - ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, testdata.Pull, testdata.User) + ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, modelPull, testdata.User) + projectCommandBuilder.VerifyWasCalled(Never()).BuildAutoplanCommands(Any[*command.Context]()) +} + +func TestRunCommentCommand_DisableAutoplanLabel(t *testing.T) { + t.Log("if \"DisableAutoplanLabel\" is present and pull request has that label, auto plans are disabled and we are silencing return and do not comment with error") + vcsClient := setup(t) + modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, BaseBranch: "main"} + + ch.DisableAutoplanLabel = "disable-auto-plan" + defer func() { ch.DisableAutoplanLabel = "" }() + + When(projectCommandBuilder.BuildAutoplanCommands(Any[*command.Context]())). + ThenReturn([]command.ProjectContext{ + { + CommandName: command.Plan, + }, + { + CommandName: command.Plan, + }, + }, nil) + When(ch.VCSClient.GetPullLabels(testdata.GithubRepo, modelPull)).ThenReturn([]string{"disable-auto-plan", "need-help"}, nil) + + ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, modelPull, testdata.User) projectCommandBuilder.VerifyWasCalled(Never()).BuildAutoplanCommands(Any[*command.Context]()) + vcsClient.VerifyWasCalledOnce().GetPullLabels(testdata.GithubRepo, modelPull) +} + +func TestRunCommentCommand_DisableAutoplanLabel_PullNotLabeled(t *testing.T) { + t.Log("if \"DisableAutoplanLabel\" is present but pull request doesn't have that label, auto plans run") + vcsClient := setup(t) + modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, BaseBranch: "main"} + + ch.DisableAutoplanLabel = "disable-auto-plan" + defer func() { ch.DisableAutoplanLabel = "" }() + + When(projectCommandBuilder.BuildAutoplanCommands(Any[*command.Context]())). + ThenReturn([]command.ProjectContext{ + { + CommandName: command.Plan, + }, + { + CommandName: command.Plan, + }, + }, nil) + When(ch.VCSClient.GetPullLabels(testdata.GithubRepo, modelPull)).ThenReturn(nil, nil) + + ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, modelPull, testdata.User) + projectCommandBuilder.VerifyWasCalled(Once()).BuildAutoplanCommands(Any[*command.Context]()) + vcsClient.VerifyWasCalledOnce().GetPullLabels(testdata.GithubRepo, modelPull) } func TestRunCommentCommand_ClosedPull(t *testing.T) { diff --git a/server/events/vcs/azuredevops_client.go b/server/events/vcs/azuredevops_client.go index 2d48e5aba2..5ff3f3ff38 100644 --- a/server/events/vcs/azuredevops_client.go +++ b/server/events/vcs/azuredevops_client.go @@ -424,3 +424,7 @@ func GitStatusContextFromSrc(src string) *azuredevops.GitStatusContext { func (g *AzureDevopsClient) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) { return "", fmt.Errorf("not yet implemented") } + +func (g *AzureDevopsClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) { + return nil, fmt.Errorf("not yet implemented") +} diff --git a/server/events/vcs/bitbucketcloud/client.go b/server/events/vcs/bitbucketcloud/client.go index 38179590fa..fa1751db19 100644 --- a/server/events/vcs/bitbucketcloud/client.go +++ b/server/events/vcs/bitbucketcloud/client.go @@ -281,3 +281,7 @@ func (b *Client) GetFileContent(pull models.PullRequest, fileName string) (bool, func (b *Client) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) { return "", fmt.Errorf("not yet implemented") } + +func (b *Client) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) { + return nil, fmt.Errorf("not yet implemented") +} diff --git a/server/events/vcs/bitbucketserver/client.go b/server/events/vcs/bitbucketserver/client.go index 6df5c7c9a2..943fd3b6c8 100644 --- a/server/events/vcs/bitbucketserver/client.go +++ b/server/events/vcs/bitbucketserver/client.go @@ -365,3 +365,7 @@ func (b *Client) GetFileContent(pull models.PullRequest, fileName string) (bool, func (b *Client) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) { return "", fmt.Errorf("not yet implemented") } + +func (b *Client) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) { + return nil, fmt.Errorf("not yet implemented") +} diff --git a/server/events/vcs/client.go b/server/events/vcs/client.go index 6ab283c8a7..dd2e489f6f 100644 --- a/server/events/vcs/client.go +++ b/server/events/vcs/client.go @@ -49,4 +49,7 @@ type Client interface { GetFileContent(pull models.PullRequest, fileName string) (bool, []byte, error) SupportsSingleFileDownload(repo models.Repo) bool GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) + + // GetPullLabels returns the labels of a pull request + GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) } diff --git a/server/events/vcs/github_client.go b/server/events/vcs/github_client.go index 608deda5d7..76ad3bf421 100644 --- a/server/events/vcs/github_client.go +++ b/server/events/vcs/github_client.go @@ -724,3 +724,18 @@ func (g *GithubClient) GetCloneURL(VCSHostType models.VCSHostType, repo string) } return repository.GetCloneURL(), nil } + +func (g *GithubClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) { + pullDetails, _, err := g.client.PullRequests.Get(g.ctx, repo.Owner, repo.Name, pull.Num) + if err != nil { + return nil, err + } + + var labels []string + + for _, label := range pullDetails.Labels { + labels = append(labels, *label.Name) + } + + return labels, nil +} diff --git a/server/events/vcs/github_client_test.go b/server/events/vcs/github_client_test.go index e42e353027..4f2f8fd3e7 100644 --- a/server/events/vcs/github_client_test.go +++ b/server/events/vcs/github_client_test.go @@ -1474,3 +1474,110 @@ func TestGithubClient_DiscardReviews(t *testing.T) { }) } } + +func TestGithubClient_GetPullLabels(t *testing.T) { + logger := logging.NewNoopLogger(t) + resp := `{ + "url": "https://api.github.com/repos/runatlantis/atlantis/pulls/1", + "id": 167530667, + "merge_commit_sha": "3fe6aa34bc25ac3720e639fcad41b428e83bdb37", + "labels": [ + { + "id": 1303230720, + "node_id": "MDU6TGFiZWwxMzAzMjMwNzIw", + "url": "https://api.github.com/repos/runatlantis/atlantis/labels/docs", + "name": "docs", + "color": "d87165", + "default": false, + "description": "Documentation" + }, + { + "id": 2552271640, + "node_id": "MDU6TGFiZWwyNTUyMjcxNjQw", + "url": "https://api.github.com/repos/runatlantis/atlantis/labels/go", + "name": "go", + "color": "16e2e2", + "default": false, + "description": "Pull requests that update Go code" + }, + { + "id": 2696098981, + "node_id": "MDU6TGFiZWwyNjk2MDk4OTgx", + "url": "https://api.github.com/repos/runatlantis/atlantis/labels/needs%20tests", + "name": "needs tests", + "color": "FBB1DE", + "default": false, + "description": "Change requires tests" + }, + { + "id": 4439792681, + "node_id": "LA_kwDOBy76Zc8AAAABCKHcKQ", + "url": "https://api.github.com/repos/runatlantis/atlantis/labels/work-in-progress", + "name": "work-in-progress", + "color": "B1E20A", + "default": false, + "description": "" + } + ] + }` + testServer := httptest.NewTLSServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case "/api/v3/repos/runatlantis/atlantis/pulls/1": + w.Write([]byte(resp)) // nolint: errcheck + default: + t.Errorf("got unexpected request at %q", r.RequestURI) + http.Error(w, "not found", http.StatusNotFound) + return + } + })) + testServerURL, err := url.Parse(testServer.URL) + Ok(t, err) + client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{}, logger) + Ok(t, err) + defer disableSSLVerification()() + + labels, err := client.GetPullLabels(models.Repo{ + Owner: "runatlantis", + Name: "atlantis", + }, models.PullRequest{ + Num: 1, + }) + Ok(t, err) + Equals(t, []string{"docs", "go", "needs tests", "work-in-progress"}, labels) +} + +func TestGithubClient_GetPullLabels_EmptyResponse(t *testing.T) { + logger := logging.NewNoopLogger(t) + resp := `{ + "url": "https://api.github.com/repos/runatlantis/atlantis/pulls/1", + "id": 167530667, + "merge_commit_sha": "3fe6aa34bc25ac3720e639fcad41b428e83bdb37", + "labels": [] + }` + testServer := httptest.NewTLSServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case "/api/v3/repos/runatlantis/atlantis/pulls/1": + w.Write([]byte(resp)) // nolint: errcheck + default: + t.Errorf("got unexpected request at %q", r.RequestURI) + http.Error(w, "not found", http.StatusNotFound) + return + } + })) + testServerURL, err := url.Parse(testServer.URL) + Ok(t, err) + client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{}, logger) + Ok(t, err) + defer disableSSLVerification()() + + labels, err := client.GetPullLabels(models.Repo{ + Owner: "runatlantis", + Name: "atlantis", + }, models.PullRequest{ + Num: 1, + }) + Ok(t, err) + Equals(t, 0, len(labels)) +} diff --git a/server/events/vcs/gitlab_client.go b/server/events/vcs/gitlab_client.go index 030629c8b8..b98c4513c8 100644 --- a/server/events/vcs/gitlab_client.go +++ b/server/events/vcs/gitlab_client.go @@ -515,3 +515,13 @@ func (g *GitlabClient) GetCloneURL(VCSHostType models.VCSHostType, repo string) } return project.HTTPURLToRepo, nil } + +func (g *GitlabClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) { + mr, _, err := g.Client.MergeRequests.GetMergeRequest(repo.FullName, pull.Num, nil) + + if err != nil { + return nil, err + } + + return mr.Labels, nil +} diff --git a/server/events/vcs/gitlab_client_test.go b/server/events/vcs/gitlab_client_test.go index a7f1a77be6..fdadf5da8b 100644 --- a/server/events/vcs/gitlab_client_test.go +++ b/server/events/vcs/gitlab_client_test.go @@ -604,6 +604,65 @@ func TestGitlabClient_HideOldComments(t *testing.T) { Equals(t, summaryFooter, gotNotePutCalls[1].comment[2]) } +func TestGithubClient_GetPullLabels(t *testing.T) { + var mergeSuccessWithLabel = strings.ReplaceAll(mergeSuccess, `"labels":[]`, `"labels":["work in progress"]`) + testServer := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case "/api/v4/projects/runatlantis%2Fatlantis/merge_requests/1": + w.WriteHeader(http.StatusOK) + w.Write([]byte(mergeSuccessWithLabel)) // nolint: errcheck + default: + t.Errorf("got unexpected request at %q", r.RequestURI) + http.Error(w, "not found", http.StatusNotFound) + } + })) + + internalClient, err := gitlab.NewClient("token", gitlab.WithBaseURL(testServer.URL)) + Ok(t, err) + client := &GitlabClient{ + Client: internalClient, + Version: nil, + } + + labels, err := client.GetPullLabels(models.Repo{ + FullName: "runatlantis/atlantis", + }, models.PullRequest{ + Num: 1, + }) + Ok(t, err) + Equals(t, []string{"work in progress"}, labels) +} + +func TestGithubClient_GetPullLabels_EmptyResponse(t *testing.T) { + testServer := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case "/api/v4/projects/runatlantis%2Fatlantis/merge_requests/1": + w.WriteHeader(http.StatusOK) + w.Write([]byte(pipelineSuccess)) // nolint: errcheck + default: + t.Errorf("got unexpected request at %q", r.RequestURI) + http.Error(w, "not found", http.StatusNotFound) + } + })) + + internalClient, err := gitlab.NewClient("token", gitlab.WithBaseURL(testServer.URL)) + Ok(t, err) + client := &GitlabClient{ + Client: internalClient, + Version: nil, + } + + labels, err := client.GetPullLabels(models.Repo{ + FullName: "runatlantis/atlantis", + }, models.PullRequest{ + Num: 1, + }) + Ok(t, err) + Equals(t, 0, len(labels)) +} + var mergeSuccess = `{"id":22461274,"iid":13,"project_id":4580910,"title":"Update main.tf","description":"","state":"merged","created_at":"2019-01-15T18:27:29.375Z","updated_at":"2019-01-25T17:28:01.437Z","merged_by":{"id":1755902,"name":"Luke Kysow","username":"lkysow","state":"active","avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url":"https://gitlab.com/lkysow"},"merged_at":"2019-01-25T17:28:01.459Z","closed_by":null,"closed_at":null,"target_branch":"patch-1","source_branch":"patch-1-merger","upvotes":0,"downvotes":0,"author":{"id":1755902,"name":"Luke Kysow","username":"lkysow","state":"active","avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url":"https://gitlab.com/lkysow"},"assignee":null,"source_project_id":4580910,"target_project_id":4580910,"labels":[],"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","detailed_merge_status":"mergeable","sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","merge_commit_sha":"c9b336f1c71d3e64810b8cfa2abcfab232d6bff6","user_notes_count":0,"discussion_locked":null,"should_remove_source_branch":null,"force_remove_source_branch":false,"web_url":"https://gitlab.com/lkysow/atlantis-example/merge_requests/13","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"squash":false,"subscribed":true,"changes_count":"1","latest_build_started_at":null,"latest_build_finished_at":null,"first_deployed_to_production_at":null,"pipeline":null,"diff_refs":{"base_sha":"67cb91d3f6198189f433c045154a885784ba6977","head_sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","start_sha":"67cb91d3f6198189f433c045154a885784ba6977"},"merge_error":null,"approvals_before_merge":null}` var pipelineSuccess = `{"id": 22461274,"iid": 13,"project_id": 4580910,"title": "Update main.tf","description": "","state": "opened","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","merged_by": null,"merged_at": null,"closed_by": null,"closed_at": null,"target_branch": "patch-1","source_branch": "patch-1-merger","user_notes_count": 0,"upvotes": 0,"downvotes": 0,"author": {"id": 1755902,"name": "Luke Kysow","username": "lkysow","state": "active","avatar_url": "https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url": "https://gitlab.com/lkysow"},"assignee": null,"reviewers": [],"source_project_id": 4580910,"target_project_id": 4580910,"labels": [],"work_in_progress": false,"milestone": null,"merge_when_pipeline_succeeds": false,"merge_status": "can_be_merged","detailed_merge_status": "mergeable","sha": "cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","merge_commit_sha": null,"squash_commit_sha": null,"discussion_locked": null,"should_remove_source_branch": null,"force_remove_source_branch": true,"reference": "!13","references": {"short": "!13","relative": "!13","full": "lkysow/atlantis-example!13"},"web_url": "https://gitlab.com/lkysow/atlantis-example/merge_requests/13","time_stats": {"time_estimate": 0,"total_time_spent": 0,"human_time_estimate": null,"human_total_time_spent": null},"squash": true,"task_completion_status": {"count": 0,"completed_count": 0},"has_conflicts": false,"blocking_discussions_resolved": true,"approvals_before_merge": null,"subscribed": false,"changes_count": "1","latest_build_started_at": "2019-01-15T18:27:29.375Z","latest_build_finished_at": "2019-01-25T17:28:01.437Z","first_deployed_to_production_at": null,"pipeline": {"id": 488598,"sha": "67cb91d3f6198189f433c045154a885784ba6977","ref": "patch-1-merger","status": "success","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","web_url": "https://gitlab.com/lkysow/atlantis-example/-/pipelines/488598"},"head_pipeline": {"id": 488598,"sha": "67cb91d3f6198189f433c045154a885784ba6977","ref": "patch-1-merger","status": "success","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","web_url": "https://gitlab.com/lkysow/atlantis-example/-/pipelines/488598","before_sha": "0000000000000000000000000000000000000000","tag": false,"yaml_errors": null,"user": {"id": 1755902,"name": "Luke Kysow","username": "lkysow","state": "active","avatar_url": "https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url": "https://gitlab.com/lkysow"},"started_at": "2019-01-15T18:27:29.375Z","finished_at": "2019-01-25T17:28:01.437Z","committed_at": null,"duration": 31,"coverage": null,"detailed_status": {"icon": "status_success","text": "passed","label": "passed","group": "success","tooltip": "passed","has_details": true,"details_path": "/lkysow/atlantis-example/-/pipelines/488598","illustration": null,"favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"}},"diff_refs": {"base_sha": "67cb91d3f6198189f433c045154a885784ba6977","head_sha": "cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","start_sha": "67cb91d3f6198189f433c045154a885784ba6977"},"merge_error": null,"first_contribution": false,"user": {"can_merge": true}}` var projectSuccess = `{"id": 4580910,"description": "","name": "atlantis-example","name_with_namespace": "lkysow / atlantis-example","path": "atlantis-example","path_with_namespace": "lkysow/atlantis-example","created_at": "2018-04-30T13:44:28.367Z","default_branch": "patch-1","tag_list": [],"ssh_url_to_repo": "git@gitlab.com:lkysow/atlantis-example.git","http_url_to_repo": "https://gitlab.com/lkysow/atlantis-example.git","web_url": "https://gitlab.com/lkysow/atlantis-example","readme_url": "https://gitlab.com/lkysow/atlantis-example/-/blob/main/README.md","avatar_url": "https://gitlab.com/uploads/-/system/project/avatar/4580910/avatar.png","forks_count": 0,"star_count": 7,"last_activity_at": "2021-06-29T21:10:43.968Z","namespace": {"id": 1,"name": "lkysow","path": "lkysow","kind": "group","full_path": "lkysow","parent_id": 1,"avatar_url": "/uploads/-/system/group/avatar/1651/platform.png","web_url": "https://gitlab.com/groups/lkysow"},"_links": {"self": "https://gitlab.com/api/v4/projects/4580910","issues": "https://gitlab.com/api/v4/projects/4580910/issues","merge_requests": "https://gitlab.com/api/v4/projects/4580910/merge_requests","repo_branches": "https://gitlab.com/api/v4/projects/4580910/repository/branches","labels": "https://gitlab.com/api/v4/projects/4580910/labels","events": "https://gitlab.com/api/v4/projects/4580910/events","members": "https://gitlab.com/api/v4/projects/4580910/members"},"packages_enabled": false,"empty_repo": false,"archived": false,"visibility": "private","resolve_outdated_diff_discussions": false,"container_registry_enabled": false,"container_expiration_policy": {"cadence": "1d","enabled": false,"keep_n": 10,"older_than": "90d","name_regex": ".*","name_regex_keep": null,"next_run_at": "2021-05-01T13:44:28.397Z"},"issues_enabled": true,"merge_requests_enabled": true,"wiki_enabled": false,"jobs_enabled": true,"snippets_enabled": true,"service_desk_enabled": false,"service_desk_address": null,"can_create_merge_request_in": true,"issues_access_level": "private","repository_access_level": "enabled","merge_requests_access_level": "enabled","forking_access_level": "enabled","wiki_access_level": "disabled","builds_access_level": "enabled","snippets_access_level": "enabled","pages_access_level": "private","operations_access_level": "disabled","analytics_access_level": "enabled","emails_disabled": null,"shared_runners_enabled": true,"lfs_enabled": false,"creator_id": 818,"import_status": "none","import_error": null,"open_issues_count": 0,"runners_token": "1234456","ci_default_git_depth": 50,"ci_forward_deployment_enabled": true,"public_jobs": true,"build_git_strategy": "fetch","build_timeout": 3600,"auto_cancel_pending_pipelines": "enabled","build_coverage_regex": null,"ci_config_path": "","shared_with_groups": [],"only_allow_merge_if_pipeline_succeeds": true,"allow_merge_on_skipped_pipeline": false,"restrict_user_defined_variables": false,"request_access_enabled": true,"only_allow_merge_if_all_discussions_are_resolved": true,"remove_source_branch_after_merge": true,"printing_merge_request_link_enabled": true,"merge_method": "merge","suggestion_commit_message": "","auto_devops_enabled": false,"auto_devops_deploy_strategy": "continuous","autoclose_referenced_issues": true,"repository_storage": "default","approvals_before_merge": 0,"mirror": false,"external_authorization_classification_label": null,"marked_for_deletion_at": null,"marked_for_deletion_on": null,"requirements_enabled": false,"compliance_frameworks": [],"permissions": {"project_access": null,"group_access": {"access_level": 50,"notification_level": 3}}}` diff --git a/server/events/vcs/mocks/mock_client.go b/server/events/vcs/mocks/mock_client.go index 9f80f46def..7583e22fac 100644 --- a/server/events/vcs/mocks/mock_client.go +++ b/server/events/vcs/mocks/mock_client.go @@ -116,6 +116,25 @@ func (mock *MockClient) GetModifiedFiles(repo models.Repo, pull models.PullReque return ret0, ret1 } +func (mock *MockClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClient().") + } + params := []pegomock.Param{repo, pull} + result := pegomock.GetGenericMockFrom(mock).Invoke("GetPullLabels", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []string + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]string) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + func (mock *MockClient) GetTeamNamesForUser(repo models.Repo, user models.User) ([]string, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockClient().") @@ -467,6 +486,37 @@ func (c *MockClient_GetModifiedFiles_OngoingVerification) GetAllCapturedArgument return } +func (verifier *VerifierMockClient) GetPullLabels(repo models.Repo, pull models.PullRequest) *MockClient_GetPullLabels_OngoingVerification { + params := []pegomock.Param{repo, pull} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetPullLabels", params, verifier.timeout) + return &MockClient_GetPullLabels_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MockClient_GetPullLabels_OngoingVerification struct { + mock *MockClient + methodInvocations []pegomock.MethodInvocation +} + +func (c *MockClient_GetPullLabels_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest) { + repo, pull := c.GetAllCapturedArguments() + return repo[len(repo)-1], pull[len(pull)-1] +} + +func (c *MockClient_GetPullLabels_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]models.Repo, len(c.methodInvocations)) + for u, param := range params[0] { + _param0[u] = param.(models.Repo) + } + _param1 = make([]models.PullRequest, len(c.methodInvocations)) + for u, param := range params[1] { + _param1[u] = param.(models.PullRequest) + } + } + return +} + func (verifier *VerifierMockClient) GetTeamNamesForUser(repo models.Repo, user models.User) *MockClient_GetTeamNamesForUser_OngoingVerification { params := []pegomock.Param{repo, user} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetTeamNamesForUser", params, verifier.timeout) diff --git a/server/events/vcs/not_configured_vcs_client.go b/server/events/vcs/not_configured_vcs_client.go index 8ab7e03e89..6ce6f2da56 100644 --- a/server/events/vcs/not_configured_vcs_client.go +++ b/server/events/vcs/not_configured_vcs_client.go @@ -73,3 +73,7 @@ func (a *NotConfiguredVCSClient) GetFileContent(pull models.PullRequest, fileNam func (a *NotConfiguredVCSClient) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) { return "", a.err() } + +func (a *NotConfiguredVCSClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) { + return nil, a.err() +} diff --git a/server/events/vcs/proxy.go b/server/events/vcs/proxy.go index 64fb8fa8ef..25637bcd0f 100644 --- a/server/events/vcs/proxy.go +++ b/server/events/vcs/proxy.go @@ -107,3 +107,7 @@ func (d *ClientProxy) SupportsSingleFileDownload(repo models.Repo) bool { func (d *ClientProxy) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) { return d.clients[VCSHostType].GetCloneURL(VCSHostType, repo) } + +func (d *ClientProxy) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) { + return d.clients[repo.VCSHost.Type].GetPullLabels(repo, pull) +} diff --git a/server/server.go b/server/server.go index a2410a1315..1fbbce19f3 100644 --- a/server/server.go +++ b/server/server.go @@ -805,6 +805,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { SilenceForkPRErrors: userConfig.SilenceForkPRErrors, SilenceForkPRErrorsFlag: config.SilenceForkPRErrorsFlag, DisableAutoplan: userConfig.DisableAutoplan, + DisableAutoplanLabel: userConfig.DisableAutoplanLabel, Drainer: drainer, PreWorkflowHooksCommandRunner: preWorkflowHooksCommandRunner, PostWorkflowHooksCommandRunner: postWorkflowHooksCommandRunner, diff --git a/server/user_config.go b/server/user_config.go index edfc6dd1da..81fb7bef7a 100644 --- a/server/user_config.go +++ b/server/user_config.go @@ -34,6 +34,7 @@ type UserConfig struct { DisableApplyAll bool `mapstructure:"disable-apply-all"` DisableApply bool `mapstructure:"disable-apply"` DisableAutoplan bool `mapstructure:"disable-autoplan"` + DisableAutoplanLabel string `mapstructure:"disable-autoplan-label"` DisableMarkdownFolding bool `mapstructure:"disable-markdown-folding"` DisableRepoLocking bool `mapstructure:"disable-repo-locking"` DiscardApprovalOnPlanFlag bool `mapstructure:"discard-approval-on-plan"` From 078af7037652aaec8b4b348b3a4952d933706d98 Mon Sep 17 00:00:00 2001 From: Finn Arne Gangstad Date: Mon, 25 Sep 2023 21:17:27 +0200 Subject: [PATCH 23/35] fix: safer re-merging with updated upstream (#3499) * Safer handling of merging with an updated upstream. We used to call forceClone() to update with upstream, but this deletes the checked out directory. This is inefficient, can delete existing plan files, and is very surprising if you are working manually in the working directory. We now fetch an updated upstream, and re-do the merge operation. This leaves any working files intact. * Rename SafeToReClone -> CheckForUpstreamChanges It's never safe to clone again. But sometimes we need to check for upstream changes to avoid reverting changes. The flag is now used to know when we need to merge again non-destructively with new changes. * Update fixtures.go * Add test to make sure plans are not wiped out As long as the branch itself has not been updated, plans should be kept. Even if upstream has changed. * renamed HasDiverged to MergedAgain in PlanResult and from Clone() This flag was only set to true in case a call to Clone() ended up merging with an updated upstream, so the new name better represents what it means. * Test that Clone on branch update wipes old plans This complements the test that Clone with unmodified branch but modified upstream does _not_ wipe plans. * runGit now runs git instead of returning a function that runs git * Updated template to merged again instead of diverged This is no longer a warning, but expected behavior in merge chekout mode * Rename git wrapper to wrappedGit, add a type for static config Every call to wrappedGit for the same PR uses identical setup for directory, head repo and PR, so passing the --------- Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> Co-authored-by: PePe Amengual --- server/events/markdown_renderer_test.go | 8 +- server/events/mock_workingdir_test.go | 16 +- server/events/mocks/mock_working_dir.go | 16 +- server/events/models/models.go | 8 +- server/events/project_command_runner.go | 6 +- server/events/templates/diverged.tmpl | 5 - server/events/templates/merged_again.tmpl | 5 + .../templates/plan_success_unwrapped.tmpl | 2 +- .../templates/plan_success_wrapped.tmpl | 2 +- server/events/working_dir.go | 150 +++++++++++------- server/events/working_dir_test.go | 75 +++++---- 11 files changed, 173 insertions(+), 120 deletions(-) delete mode 100644 server/events/templates/diverged.tmpl create mode 100644 server/events/templates/merged_again.tmpl diff --git a/server/events/markdown_renderer_test.go b/server/events/markdown_renderer_test.go index 9cbc4e3df5..548ce03cdb 100644 --- a/server/events/markdown_renderer_test.go +++ b/server/events/markdown_renderer_test.go @@ -191,7 +191,7 @@ $$$ LockURL: "lock-url", RePlanCmd: "atlantis plan -d path -w workspace", ApplyCmd: "atlantis apply -d path -w workspace", - HasDiverged: true, + MergedAgain: true, }, Workspace: "workspace", RepoRelDir: "path", @@ -210,7 +210,7 @@ $$$ * :repeat: To **plan** this project again, comment: * $atlantis plan -d path -w workspace$ -:warning: The branch we're merging into is ahead, it is recommended to pull new commits first. +:twisted_rightwards_arrows: Upstream was modified, a new merge was performed. --- * :fast_forward: To **apply** all unapplied plans from this pull request, comment: @@ -1974,7 +1974,7 @@ $$$ LockURL: "lock-url", RePlanCmd: "atlantis plan -d path -w workspace", ApplyCmd: "atlantis apply -d path -w workspace", - HasDiverged: true, + MergedAgain: true, }, Workspace: "workspace", RepoRelDir: "path", @@ -1992,7 +1992,7 @@ $$$ * :repeat: To **plan** this project again, comment: * $atlantis plan -d path -w workspace$ -:warning: The branch we're merging into is ahead, it is recommended to pull new commits first. +:twisted_rightwards_arrows: Upstream was modified, a new merge was performed. --- * :fast_forward: To **apply** all unapplied plans from this pull request, comment: diff --git a/server/events/mock_workingdir_test.go b/server/events/mock_workingdir_test.go index 27a0695ea5..30b344ea3a 100644 --- a/server/events/mock_workingdir_test.go +++ b/server/events/mock_workingdir_test.go @@ -165,12 +165,12 @@ func (mock *MockWorkingDir) HasDiverged(cloneDir string) bool { return ret0 } -func (mock *MockWorkingDir) SetSafeToReClone() { +func (mock *MockWorkingDir) SetCheckForUpstreamChanges() { if mock == nil { panic("mock must not be nil. Use myMock := NewMockWorkingDir().") } params := []pegomock.Param{} - pegomock.GetGenericMockFrom(mock).Invoke("SetSafeToReClone", params, []reflect.Type{}) + pegomock.GetGenericMockFrom(mock).Invoke("SetCheckForUpstreamChanges", params, []reflect.Type{}) } func (mock *MockWorkingDir) VerifyWasCalledOnce() *VerifierMockWorkingDir { @@ -482,19 +482,19 @@ func (c *MockWorkingDir_HasDiverged_OngoingVerification) GetAllCapturedArguments return } -func (verifier *VerifierMockWorkingDir) SetSafeToReClone() *MockWorkingDir_SetSafeToReClone_OngoingVerification { +func (verifier *VerifierMockWorkingDir) SetCheckForUpstreamChanges() *MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification { params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetSafeToReClone", params, verifier.timeout) - return &MockWorkingDir_SetSafeToReClone_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetCheckForUpstreamChanges", params, verifier.timeout) + return &MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type MockWorkingDir_SetSafeToReClone_OngoingVerification struct { +type MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification struct { mock *MockWorkingDir methodInvocations []pegomock.MethodInvocation } -func (c *MockWorkingDir_SetSafeToReClone_OngoingVerification) GetCapturedArguments() { +func (c *MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification) GetCapturedArguments() { } -func (c *MockWorkingDir_SetSafeToReClone_OngoingVerification) GetAllCapturedArguments() { +func (c *MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification) GetAllCapturedArguments() { } diff --git a/server/events/mocks/mock_working_dir.go b/server/events/mocks/mock_working_dir.go index 8f051f1937..55ecc1ca4c 100644 --- a/server/events/mocks/mock_working_dir.go +++ b/server/events/mocks/mock_working_dir.go @@ -165,12 +165,12 @@ func (mock *MockWorkingDir) HasDiverged(cloneDir string) bool { return ret0 } -func (mock *MockWorkingDir) SetSafeToReClone() { +func (mock *MockWorkingDir) SetCheckForUpstreamChanges() { if mock == nil { panic("mock must not be nil. Use myMock := NewMockWorkingDir().") } params := []pegomock.Param{} - pegomock.GetGenericMockFrom(mock).Invoke("SetSafeToReClone", params, []reflect.Type{}) + pegomock.GetGenericMockFrom(mock).Invoke("SetCheckForUpstreamChanges", params, []reflect.Type{}) } func (mock *MockWorkingDir) VerifyWasCalledOnce() *VerifierMockWorkingDir { @@ -482,19 +482,19 @@ func (c *MockWorkingDir_HasDiverged_OngoingVerification) GetAllCapturedArguments return } -func (verifier *VerifierMockWorkingDir) SetSafeToReClone() *MockWorkingDir_SetSafeToReClone_OngoingVerification { +func (verifier *VerifierMockWorkingDir) SetCheckForUpstreamChanges() *MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification { params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetSafeToReClone", params, verifier.timeout) - return &MockWorkingDir_SetSafeToReClone_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SetCheckForUpstreamChanges", params, verifier.timeout) + return &MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type MockWorkingDir_SetSafeToReClone_OngoingVerification struct { +type MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification struct { mock *MockWorkingDir methodInvocations []pegomock.MethodInvocation } -func (c *MockWorkingDir_SetSafeToReClone_OngoingVerification) GetCapturedArguments() { +func (c *MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification) GetCapturedArguments() { } -func (c *MockWorkingDir_SetSafeToReClone_OngoingVerification) GetAllCapturedArguments() { +func (c *MockWorkingDir_SetCheckForUpstreamChanges_OngoingVerification) GetAllCapturedArguments() { } diff --git a/server/events/models/models.go b/server/events/models/models.go index 492fc36fea..bdc821c285 100644 --- a/server/events/models/models.go +++ b/server/events/models/models.go @@ -361,10 +361,10 @@ type PlanSuccess struct { RePlanCmd string // ApplyCmd is the command that users should run to apply this plan. ApplyCmd string - // HasDiverged is true if we're using the checkout merge strategy and the - // branch we're merging into has been updated since we cloned and merged - // it. - HasDiverged bool + // MergedAgain is true if we're using the checkout merge strategy and the + // branch we're merging into had been updated, and we had to merge again + // before planning + MergedAgain bool } type PolicySetResult struct { diff --git a/server/events/project_command_runner.go b/server/events/project_command_runner.go index 276a9ef81d..6b938b2b95 100644 --- a/server/events/project_command_runner.go +++ b/server/events/project_command_runner.go @@ -543,9 +543,9 @@ func (p *DefaultProjectCommandRunner) doPlan(ctx command.ProjectContext) (*model } defer unlockFn() - p.WorkingDir.SetSafeToReClone() + p.WorkingDir.SetCheckForUpstreamChanges() // Clone is idempotent so okay to run even if the repo was already cloned. - repoDir, hasDiverged, cloneErr := p.WorkingDir.Clone(ctx.HeadRepo, ctx.Pull, ctx.Workspace) + repoDir, mergedAgain, cloneErr := p.WorkingDir.Clone(ctx.HeadRepo, ctx.Pull, ctx.Workspace) if cloneErr != nil { if unlockErr := lockAttempt.UnlockFn(); unlockErr != nil { ctx.Log.Err("error unlocking state after plan error: %v", unlockErr) @@ -576,7 +576,7 @@ func (p *DefaultProjectCommandRunner) doPlan(ctx command.ProjectContext) (*model TerraformOutput: strings.Join(outputs, "\n"), RePlanCmd: ctx.RePlanCmd, ApplyCmd: ctx.ApplyCmd, - HasDiverged: hasDiverged, + MergedAgain: mergedAgain, }, "", nil } diff --git a/server/events/templates/diverged.tmpl b/server/events/templates/diverged.tmpl deleted file mode 100644 index f0f4be0ed2..0000000000 --- a/server/events/templates/diverged.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -{{ define "diverged" -}} -{{ if .HasDiverged }} -:warning: The branch we're merging into is ahead, it is recommended to pull new commits first. -{{ end -}} -{{ end -}} diff --git a/server/events/templates/merged_again.tmpl b/server/events/templates/merged_again.tmpl new file mode 100644 index 0000000000..796afe552a --- /dev/null +++ b/server/events/templates/merged_again.tmpl @@ -0,0 +1,5 @@ +{{ define "mergedAgain" -}} +{{ if .MergedAgain }} +:twisted_rightwards_arrows: Upstream was modified, a new merge was performed. +{{ end -}} +{{ end -}} diff --git a/server/events/templates/plan_success_unwrapped.tmpl b/server/events/templates/plan_success_unwrapped.tmpl index 1f34216a0d..6bd81de233 100644 --- a/server/events/templates/plan_success_unwrapped.tmpl +++ b/server/events/templates/plan_success_unwrapped.tmpl @@ -16,5 +16,5 @@ This plan was not saved because one or more projects failed and automerge requir * :repeat: To **plan** this project again, comment: * `{{ .RePlanCmd }}` {{ end -}} -{{ template "diverged" . }} +{{ template "mergedAgain" . }} {{ end -}} diff --git a/server/events/templates/plan_success_wrapped.tmpl b/server/events/templates/plan_success_wrapped.tmpl index 9630de1b2e..cef96d0609 100644 --- a/server/events/templates/plan_success_wrapped.tmpl +++ b/server/events/templates/plan_success_wrapped.tmpl @@ -20,5 +20,5 @@ This plan was not saved because one or more projects failed and automerge requir {{ end -}} {{ .PlanSummary -}} -{{ template "diverged" . -}} +{{ template "mergedAgain" . -}} {{ end -}} diff --git a/server/events/working_dir.go b/server/events/working_dir.go index 65fd28a304..a6b2c73656 100644 --- a/server/events/working_dir.go +++ b/server/events/working_dir.go @@ -53,7 +53,7 @@ type WorkingDir interface { // Set a flag in the workingdir so Clone() can know that it is safe to re-clone the workingdir if // the upstream branch has been modified. This is only safe after grabbing the project lock // and before running any plans - SetSafeToReClone() + SetCheckForUpstreamChanges() // DeletePlan deletes the plan for this repo, pull, workspace path and project name DeletePlan(r models.Repo, p models.PullRequest, workspace string, path string, projectName string) error // GetGitUntrackedFiles returns a list of Git untracked files in the working dir. @@ -84,9 +84,9 @@ type FileWorkspace struct { GithubAppEnabled bool // use the global setting without overriding GpgNoSigningEnabled bool - // flag indicating if a re-clone will be safe (project lock held, about to run plan) - SafeToReClone bool - Logger logging.SimpleLogging + // flag indicating if we have to merge with potential new changes upstream (directly after grabbing project lock) + CheckForUpstreamChanges bool + Logger logging.SimpleLogging } // Clone git clones headRepo, checks out the branch and then returns the absolute @@ -100,9 +100,9 @@ func (w *FileWorkspace) Clone( p models.PullRequest, workspace string) (string, bool, error) { cloneDir := w.cloneDir(p.BaseRepo, p, workspace) - hasDiverged := false - defer func() { w.SafeToReClone = false }() + defer func() { w.CheckForUpstreamChanges = false }() + c := wrappedGitContext{cloneDir, headRepo, p} // If the directory already exists, check if it's at the right commit. // If so, then we do nothing. if _, err := os.Stat(cloneDir); err == nil { @@ -122,16 +122,16 @@ func (w *FileWorkspace) Clone( outputRevParseCmd, err := revParseCmd.CombinedOutput() if err != nil { w.Logger.Warn("will re-clone repo, could not determine if was at correct commit: %s: %s: %s", strings.Join(revParseCmd.Args, " "), err, string(outputRevParseCmd)) - return cloneDir, false, w.forceClone(cloneDir, headRepo, p) + return cloneDir, false, w.forceClone(c) } currCommit := strings.Trim(string(outputRevParseCmd), "\n") // We're prefix matching here because BitBucket doesn't give us the full // commit, only a 12 character prefix. if strings.HasPrefix(currCommit, p.HeadCommit) { - if w.SafeToReClone && w.CheckoutMerge && w.recheckDiverged(p, headRepo, cloneDir) { + if w.CheckForUpstreamChanges && w.CheckoutMerge && w.recheckDiverged(p, headRepo, cloneDir) { w.Logger.Info("base branch has been updated, using merge strategy and will clone again") - hasDiverged = true + return cloneDir, true, w.mergeAgain(c) } else { w.Logger.Debug("repo is at correct commit %q so will not re-clone", p.HeadCommit) return cloneDir, false, nil @@ -143,7 +143,7 @@ func (w *FileWorkspace) Clone( } // Otherwise we clone the repo. - return cloneDir, hasDiverged, w.forceClone(cloneDir, headRepo, p) + return cloneDir, false, w.forceClone(c) } // recheckDiverged returns true if the branch we're merging into has diverged @@ -212,8 +212,8 @@ func (w *FileWorkspace) HasDiverged(cloneDir string) bool { return hasDiverged } -func (w *FileWorkspace) forceClone(cloneDir string, headRepo models.Repo, p models.PullRequest) error { - value, _ := cloneLocks.LoadOrStore(cloneDir, new(sync.Mutex)) +func (w *FileWorkspace) forceClone(c wrappedGitContext) error { + value, _ := cloneLocks.LoadOrStore(c.dir, new(sync.Mutex)) mutex := value.(*sync.Mutex) defer mutex.Unlock() @@ -222,97 +222,131 @@ func (w *FileWorkspace) forceClone(cloneDir string, headRepo models.Repo, p mode return nil } - err := os.RemoveAll(cloneDir) + err := os.RemoveAll(c.dir) if err != nil { - return errors.Wrapf(err, "deleting dir %q before cloning", cloneDir) + return errors.Wrapf(err, "deleting dir %q before cloning", c.dir) } // Create the directory and parents if necessary. - w.Logger.Info("creating dir %q", cloneDir) - if err := os.MkdirAll(cloneDir, 0700); err != nil { + w.Logger.Info("creating dir %q", c.dir) + if err := os.MkdirAll(c.dir, 0700); err != nil { return errors.Wrap(err, "creating new workspace") } // During testing, we mock some of this out. - headCloneURL := headRepo.CloneURL + headCloneURL := c.head.CloneURL if w.TestingOverrideHeadCloneURL != "" { headCloneURL = w.TestingOverrideHeadCloneURL } - baseCloneURL := p.BaseRepo.CloneURL + baseCloneURL := c.pr.BaseRepo.CloneURL if w.TestingOverrideBaseCloneURL != "" { baseCloneURL = w.TestingOverrideBaseCloneURL } - runGit := func(args ...string) error { - cmd := exec.Command("git", args...) // nolint: gosec - cmd.Dir = cloneDir - // The git merge command requires these env vars are set. - cmd.Env = append(os.Environ(), []string{ - "EMAIL=atlantis@runatlantis.io", - "GIT_AUTHOR_NAME=atlantis", - "GIT_COMMITTER_NAME=atlantis", - }...) - - cmdStr := w.sanitizeGitCredentials(strings.Join(cmd.Args, " "), p.BaseRepo, headRepo) - output, err := cmd.CombinedOutput() - sanitizedOutput := w.sanitizeGitCredentials(string(output), p.BaseRepo, headRepo) - if err != nil { - sanitizedErrMsg := w.sanitizeGitCredentials(err.Error(), p.BaseRepo, headRepo) - return fmt.Errorf("running %s: %s: %s", cmdStr, sanitizedOutput, sanitizedErrMsg) - } - w.Logger.Debug("ran: %s. Output: %s", cmdStr, strings.TrimSuffix(sanitizedOutput, "\n")) - return nil - } - // if branch strategy, use depth=1 if !w.CheckoutMerge { - return runGit("clone", "--depth=1", "--branch", p.HeadBranch, "--single-branch", headCloneURL, cloneDir) + return w.wrappedGit(c, "clone", "--depth=1", "--branch", c.pr.HeadBranch, "--single-branch", headCloneURL, c.dir) } // if merge strategy... // if no checkout depth, omit depth arg if w.CheckoutDepth == 0 { - if err := runGit("clone", "--branch", p.BaseBranch, "--single-branch", baseCloneURL, cloneDir); err != nil { + if err := w.wrappedGit(c, "clone", "--branch", c.pr.BaseBranch, "--single-branch", baseCloneURL, c.dir); err != nil { return err } } else { - if err := runGit("clone", "--depth", fmt.Sprint(w.CheckoutDepth), "--branch", p.BaseBranch, "--single-branch", baseCloneURL, cloneDir); err != nil { + if err := w.wrappedGit(c, "clone", "--depth", fmt.Sprint(w.CheckoutDepth), "--branch", c.pr.BaseBranch, "--single-branch", baseCloneURL, c.dir); err != nil { return err } } - if err := runGit("remote", "add", "head", headCloneURL); err != nil { + if err := w.wrappedGit(c, "remote", "add", "head", headCloneURL); err != nil { return err } + if w.GpgNoSigningEnabled { + if err := w.wrappedGit(c, "config", "--local", "commit.gpgsign", "false"); err != nil { + return err + } + } + + return w.mergeToBaseBranch(c) +} + +// There is a new upstream update that we need, and we want to update to it +// without deleting any existing plans +func (w *FileWorkspace) mergeAgain(c wrappedGitContext) error { + value, _ := cloneLocks.LoadOrStore(c.dir, new(sync.Mutex)) + mutex := value.(*sync.Mutex) - fetchRef := fmt.Sprintf("+refs/heads/%s:", p.HeadBranch) + defer mutex.Unlock() + if locked := mutex.TryLock(); !locked { + mutex.Lock() + return nil + } + + // Reset branch as if it was cloned again + if err := w.wrappedGit(c, "reset", "--hard", fmt.Sprintf("refs/remotes/head/%s", c.pr.BaseBranch)); err != nil { + return err + } + + return w.mergeToBaseBranch(c) +} + +// wrappedGitContext is the configuration for wrappedGit that is typically unchanged +// for a series of calls to wrappedGit +type wrappedGitContext struct { + dir string + head models.Repo + pr models.PullRequest +} + +// wrappedGit runs git with additional environment settings required for git merge, +// and with sanitized error logging to avoid leaking git credentials +func (w *FileWorkspace) wrappedGit(c wrappedGitContext, args ...string) error { + cmd := exec.Command("git", args...) // nolint: gosec + cmd.Dir = c.dir + // The git merge command requires these env vars are set. + cmd.Env = append(os.Environ(), []string{ + "EMAIL=atlantis@runatlantis.io", + "GIT_AUTHOR_NAME=atlantis", + "GIT_COMMITTER_NAME=atlantis", + }...) + cmdStr := w.sanitizeGitCredentials(strings.Join(cmd.Args, " "), c.pr.BaseRepo, c.head) + output, err := cmd.CombinedOutput() + sanitizedOutput := w.sanitizeGitCredentials(string(output), c.pr.BaseRepo, c.head) + if err != nil { + sanitizedErrMsg := w.sanitizeGitCredentials(err.Error(), c.pr.BaseRepo, c.head) + return fmt.Errorf("running %s: %s: %s", cmdStr, sanitizedOutput, sanitizedErrMsg) + } + w.Logger.Debug("ran: %s. Output: %s", cmdStr, strings.TrimSuffix(sanitizedOutput, "\n")) + return nil +} + +// Merge the PR into the base branch. +func (w *FileWorkspace) mergeToBaseBranch(c wrappedGitContext) error { + fetchRef := fmt.Sprintf("+refs/heads/%s:", c.pr.HeadBranch) fetchRemote := "head" if w.GithubAppEnabled { - fetchRef = fmt.Sprintf("pull/%d/head:", p.Num) + fetchRef = fmt.Sprintf("pull/%d/head:", c.pr.Num) fetchRemote = "origin" } // if no checkout depth, omit depth arg if w.CheckoutDepth == 0 { - if err := runGit("fetch", fetchRemote, fetchRef); err != nil { + if err := w.wrappedGit(c, "fetch", fetchRemote, fetchRef); err != nil { return err } } else { - if err := runGit("fetch", "--depth", fmt.Sprint(w.CheckoutDepth), fetchRemote, fetchRef); err != nil { + if err := w.wrappedGit(c, "fetch", "--depth", fmt.Sprint(w.CheckoutDepth), fetchRemote, fetchRef); err != nil { return err } } - if w.GpgNoSigningEnabled { - if err := runGit("config", "--local", "commit.gpgsign", "false"); err != nil { - return err - } - } - if err := runGit("merge-base", p.BaseBranch, "FETCH_HEAD"); err != nil { + if err := w.wrappedGit(c, "merge-base", c.pr.BaseBranch, "FETCH_HEAD"); err != nil { // git merge-base returning error means that we did not receive enough commits in shallow clone. // Fall back to retrieving full repo history. - if err := runGit("fetch", "--unshallow"); err != nil { + if err := w.wrappedGit(c, "fetch", "--unshallow"); err != nil { return err } } @@ -323,7 +357,7 @@ func (w *FileWorkspace) forceClone(cloneDir string, headRepo models.Repo, p mode // git rev-parse HEAD^2 to get the head commit because it will // always succeed whereas without --no-ff, if the merge was fast // forwarded then git rev-parse HEAD^2 would fail. - return runGit("merge", "-q", "--no-ff", "-m", "atlantis-merge", "FETCH_HEAD") + return w.wrappedGit(c, "merge", "-q", "--no-ff", "-m", "atlantis-merge", "FETCH_HEAD") } // GetWorkingDir returns the path to the workspace for this repo and pull. @@ -374,9 +408,9 @@ func (w *FileWorkspace) sanitizeGitCredentials(s string, base models.Repo, head return strings.Replace(baseReplaced, head.CloneURL, head.SanitizedCloneURL, -1) } -// Set the flag that indicates it is safe to re-clone if necessary -func (w *FileWorkspace) SetSafeToReClone() { - w.SafeToReClone = true +// Set the flag that indicates we need to check for upstream changes (if using merge checkout strategy) +func (w *FileWorkspace) SetCheckForUpstreamChanges() { + w.CheckForUpstreamChanges = true } func (w *FileWorkspace) DeletePlan(r models.Repo, p models.PullRequest, workspace string, projectPath string, projectName string) error { diff --git a/server/events/working_dir_test.go b/server/events/working_dir_test.go index 225f299291..8eae7c730d 100644 --- a/server/events/working_dir_test.go +++ b/server/events/working_dir_test.go @@ -3,6 +3,7 @@ package events_test import ( "crypto/tls" "fmt" + "github.com/stretchr/testify/assert" "net/http" "os" "path/filepath" @@ -97,13 +98,13 @@ func TestClone_CheckoutMergeNoneExisting(t *testing.T) { Logger: logger, } - cloneDir, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + cloneDir, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", BaseBranch: "main", }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) // Check the commits. actBaseCommit := runCmd(t, cloneDir, "git", "rev-parse", "HEAD~1") @@ -149,25 +150,25 @@ func TestClone_CheckoutMergeNoReclone(t *testing.T) { Logger: logger, } - _, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + _, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", BaseBranch: "main", }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) // Create a file that we can use to check if the repo was recloned. runCmd(t, dataDir, "touch", "repos/0/default/proof") // Now run the clone again. - cloneDir, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + cloneDir, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", BaseBranch: "main", }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) // Check that our proof file is still there, proving that we didn't reclone. _, err = os.Stat(filepath.Join(cloneDir, "proof")) @@ -202,25 +203,25 @@ func TestClone_CheckoutMergeNoRecloneFastForward(t *testing.T) { Logger: logger, } - _, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + _, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", BaseBranch: "main", }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) // Create a file that we can use to check if the repo was recloned. runCmd(t, dataDir, "touch", "repos/0/default/proof") // Now run the clone again. - cloneDir, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + cloneDir, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", BaseBranch: "main", }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) // Check that our proof file is still there, proving that we didn't reclone. _, err = os.Stat(filepath.Join(cloneDir, "proof")) @@ -320,13 +321,13 @@ func TestClone_CheckoutMergeShallow(t *testing.T) { Logger: logger, } - cloneDir, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + cloneDir, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", BaseBranch: "main", }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) gotBaseCommitType := runCmd(t, cloneDir, "git", "cat-file", "-t", baseCommit) Assert(t, gotBaseCommitType == "commit\n", "should have merge-base in shallow repo") @@ -351,13 +352,13 @@ func TestClone_CheckoutMergeShallow(t *testing.T) { Logger: logger, } - cloneDir, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + cloneDir, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", BaseBranch: "main", }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) gotBaseCommitType := runCmd(t, cloneDir, "git", "cat-file", "-t", baseCommit) Assert(t, gotBaseCommitType == "commit\n", "should have merge-base in full repo") @@ -387,12 +388,12 @@ func TestClone_NoReclone(t *testing.T) { GpgNoSigningEnabled: true, Logger: logger, } - cloneDir, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + cloneDir, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) // Check that our proof file is still there. _, err = os.Stat(filepath.Join(cloneDir, "proof")) @@ -416,6 +417,13 @@ func TestClone_RecloneWrongCommit(t *testing.T) { runCmd(t, repoDir, "git", "commit", "-m", "newfile") expCommit := runCmd(t, repoDir, "git", "rev-parse", "HEAD") + // Pretend that terraform has created a plan file, we'll check for it later + planFile := filepath.Join(dataDir, "repos/0/default/default.tfplan") + assert.NoFileExists(t, planFile) + _, err := os.Create(planFile) + Assert(t, err == nil, "creating plan file: %v", err) + assert.FileExists(t, planFile) + logger := logging.NewNoopLogger(t) wd := &events.FileWorkspace{ @@ -425,13 +433,14 @@ func TestClone_RecloneWrongCommit(t *testing.T) { GpgNoSigningEnabled: true, Logger: logger, } - cloneDir, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + cloneDir, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "branch", HeadCommit: expCommit, }, "default") Ok(t, err) - Equals(t, false, hasDiverged) + Equals(t, false, mergedAgain) + assert.NoFileExists(t, planFile, "Plan file should have been wiped out by Clone") // Use rev-parse to verify at correct commit. actCommit := runCmd(t, cloneDir, "git", "rev-parse", "HEAD") @@ -499,37 +508,47 @@ func TestClone_MasterHasDiverged(t *testing.T) { Logger: logger, } + // Pretend terraform has created a plan file, we'll check for it later + planFile := filepath.Join(repoDir, "repos/0/default/default.tfplan") + assert.NoFileExists(t, planFile) + _, err := os.Create(planFile) + Assert(t, err == nil, "creating plan file: %v", err) + assert.FileExists(t, planFile) + // Run the clone without the checkout merge strategy. It should return - // false for hasDiverged - _, hasDiverged, err := wd.Clone(models.Repo{}, models.PullRequest{ + // false for mergedAgain + _, mergedAgain, err := wd.Clone(models.Repo{}, models.PullRequest{ BaseRepo: models.Repo{}, HeadBranch: "second-pr", BaseBranch: "main", }, "default") Ok(t, err) - Assert(t, hasDiverged == false, "Clone with CheckoutMerge=false should not merge") + Assert(t, mergedAgain == false, "Clone with CheckoutMerge=false should not merge") + assert.FileExists(t, planFile, "Existing plan file should not be deleted by Clone with merge disabled") wd.CheckoutMerge = true - wd.SetSafeToReClone() + wd.SetCheckForUpstreamChanges() // Run the clone twice with the merge strategy, the first run should - // return true for hasDiverged, subsequent runs should + // return true for mergedAgain, subsequent runs should // return false since the first call is supposed to merge. - _, hasDiverged, err = wd.Clone(models.Repo{CloneURL: repoDir}, models.PullRequest{ + _, mergedAgain, err = wd.Clone(models.Repo{CloneURL: repoDir}, models.PullRequest{ BaseRepo: models.Repo{CloneURL: repoDir}, HeadBranch: "second-pr", BaseBranch: "main", }, "default") Ok(t, err) - Assert(t, hasDiverged == true, "First clone with CheckoutMerge=true with diverged base should have merged") + assert.FileExists(t, planFile, "Existing plan file should not be deleted by merging again") + Assert(t, mergedAgain == true, "First clone with CheckoutMerge=true with diverged base should have merged") - wd.SetSafeToReClone() - _, hasDiverged, err = wd.Clone(models.Repo{CloneURL: repoDir}, models.PullRequest{ + wd.SetCheckForUpstreamChanges() + _, mergedAgain, err = wd.Clone(models.Repo{CloneURL: repoDir}, models.PullRequest{ BaseRepo: models.Repo{CloneURL: repoDir}, HeadBranch: "second-pr", BaseBranch: "main", }, "default") Ok(t, err) - Assert(t, hasDiverged == false, "Second clone with CheckoutMerge=true and initially diverged base should not merge again") + Assert(t, mergedAgain == false, "Second clone with CheckoutMerge=true and initially diverged base should not merge again") + assert.FileExists(t, planFile, "Existing plan file should not have been deleted") } func TestHasDiverged_MasterHasDiverged(t *testing.T) { From 352bbedfbf385c69b22c13f0e53dcb14dabd1baa Mon Sep 17 00:00:00 2001 From: JSNortal <124635010+JSNortal@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:53:06 +0100 Subject: [PATCH 24/35] fix: issue with GH App credential not writing if lines already exist (#3679) * Fix issue with GH App credential not writing if lines already exist * Fix lint issue of unused variable. --------- Co-authored-by: PePe Amengual Co-authored-by: Dylan Page --- server/events/vcs/git_cred_writer.go | 34 ++++++++++++++++++++--- server/events/vcs/git_cred_writer_test.go | 19 +++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/server/events/vcs/git_cred_writer.go b/server/events/vcs/git_cred_writer.go index 6d6cf85317..eca5dc00d7 100644 --- a/server/events/vcs/git_cred_writer.go +++ b/server/events/vcs/git_cred_writer.go @@ -38,11 +38,23 @@ func WriteGitCreds(gitUser string, gitToken string, gitHostname string, home str } if ghAccessToken { - // Need to replace the line. - if err := fileLineReplace(config, gitUser, gitHostname, credsFile); err != nil { - return errors.Wrap(err, "replacing git credentials line for github app") + hasGHToken, err := fileHasGHToken(gitUser, gitHostname, credsFile) + if err != nil { + return err + } + if hasGHToken { + // Need to replace the line. + if err := fileLineReplace(config, gitUser, gitHostname, credsFile); err != nil { + return errors.Wrap(err, "replacing git credentials line for github app") + } + logger.Info("updated git app credentials in %s", credsFile) + } else { + if err := fileAppend(config, credsFile); err != nil { + return err + } + logger.Info("wrote git credentials to %s", credsFile) } - logger.Info("updated git app credentials in %s", credsFile) + } else { // Otherwise we need to append the line. if err := fileAppend(config, credsFile); err != nil { @@ -113,3 +125,17 @@ func fileLineReplace(line, user, host, filename string) error { return os.WriteFile(filename, []byte(toWrite), 0600) } + +func fileHasGHToken(user, host, filename string) (bool, error) { + currContents, err := os.ReadFile(filename) // nolint: gosec + if err != nil { + return false, err + } + prevLines := strings.Split(string(currContents), "\n") + for _, l := range prevLines { + if strings.HasPrefix(l, "https://"+user) && strings.HasSuffix(l, host) { + return true, nil + } + } + return false, nil +} diff --git a/server/events/vcs/git_cred_writer_test.go b/server/events/vcs/git_cred_writer_test.go index 082f732475..64e7588672 100644 --- a/server/events/vcs/git_cred_writer_test.go +++ b/server/events/vcs/git_cred_writer_test.go @@ -86,6 +86,25 @@ func TestWriteGitCreds_ReplaceApp(t *testing.T) { Equals(t, expContets, string(actContents)) } +// Test that the github app credential gets added even if there are other credentials. +func TestWriteGitCreds_AppendAppWhenFileNotEmpty(t *testing.T) { + logger := logging.NewNoopLogger(t) + tmp := t.TempDir() + t.Setenv("HOME", tmp) + + credsFile := filepath.Join(tmp, ".git-credentials") + contents := "line1\nhttps://user:token@host.com\nline2" + err := os.WriteFile(credsFile, []byte(contents), 0600) + Ok(t, err) + + err = vcs.WriteGitCreds("x-access-token", "token", "github.com", tmp, logger, true) + Ok(t, err) + expContets := "line1\nhttps://user:token@host.com\nline2\nhttps://x-access-token:token@github.com" + actContents, err := os.ReadFile(filepath.Join(tmp, ".git-credentials")) + Ok(t, err) + Equals(t, expContets, string(actContents)) +} + // Test that the github app credentials get updated when cred file is empty. func TestWriteGitCreds_AppendApp(t *testing.T) { logger := logging.NewNoopLogger(t) From dd9d0362b189c70093ceccd4f8871b926e9d5e4e Mon Sep 17 00:00:00 2001 From: Dylan Page Date: Mon, 25 Sep 2023 17:09:54 -0400 Subject: [PATCH 25/35] ci: auto generated release notes (#3790) --- .github/release.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/release.yml b/.github/release.yml index 96a4e9bf8b..df2619416f 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -2,7 +2,6 @@ changelog: exclude: labels: - ignore-for-release - - docs - github-actions authors: - octocat @@ -22,9 +21,3 @@ changelog: - title: Other Changes labels: - "*" - - title: Dependencies - labels: - - dependencies - - title: Build - labels: - - build From f83609a51b47f8ace314f2ea9a45d4c391c88554 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 01:46:33 +0000 Subject: [PATCH 26/35] fix(deps): update github.com/hashicorp/terraform-config-inspect digest to 5a6f8d1 in go.mod (#3792) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e503311d3c..0e46a373cf 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.6 - github.com/hashicorp/terraform-config-inspect v0.0.0-20230825013512-b800820f61b8 + github.com/hashicorp/terraform-config-inspect v0.0.0-20230925220900-5a6f8d18746d github.com/kr/pretty v0.3.1 github.com/mcdafydd/go-azuredevops v0.12.1 github.com/microcosm-cc/bluemonday v1.0.25 diff --git a/go.sum b/go.sum index d09bea7b4a..d7364b2013 100644 --- a/go.sum +++ b/go.sum @@ -267,8 +267,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.18.0 h1:wYnG7Lt31t2zYkcquwgKo6MWXzRUDIeIVU5naZwHLl8= github.com/hashicorp/hcl/v2 v2.18.0/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= -github.com/hashicorp/terraform-config-inspect v0.0.0-20230825013512-b800820f61b8 h1:SzE5lAYh9XXR3b1q3p3uBNqEY+syiiLZiFCIvr/JTsg= -github.com/hashicorp/terraform-config-inspect v0.0.0-20230825013512-b800820f61b8/go.mod h1:l8HcFPm9cQh6Q0KSWoYPiePqMvRFenybP1CH2MjKdlg= +github.com/hashicorp/terraform-config-inspect v0.0.0-20230925220900-5a6f8d18746d h1:g6kHlvZrFPFKeWRj5q/zyJA5gu7rlJGPf17h8hX7LHY= +github.com/hashicorp/terraform-config-inspect v0.0.0-20230925220900-5a6f8d18746d/go.mod h1:l8HcFPm9cQh6Q0KSWoYPiePqMvRFenybP1CH2MjKdlg= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= From 9f1175d59218d7b9bcc2e78ca0016362c5558748 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 01:02:08 +0000 Subject: [PATCH 27/35] fix(deps): update module github.com/xanzy/go-gitlab to v0.92.1 in go.mod (#3794) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0e46a373cf..96a7805cce 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/uber-go/tally/v4 v4.1.7 github.com/urfave/negroni/v3 v3.0.0 github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997 - github.com/xanzy/go-gitlab v0.91.1 + github.com/xanzy/go-gitlab v0.92.1 go.etcd.io/bbolt v1.3.7 go.uber.org/zap v1.26.0 golang.org/x/term v0.12.0 diff --git a/go.sum b/go.sum index d7364b2013..d66e918f22 100644 --- a/go.sum +++ b/go.sum @@ -453,8 +453,8 @@ github.com/urfave/negroni/v3 v3.0.0 h1:Vo8CeZfu1lFR9gW8GnAb6dOGCJyijfil9j/jKKc/J github.com/urfave/negroni/v3 v3.0.0/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs= github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997 h1:be5WC0FHdhimAhe2G3DPhduX117RM8qdTMYCMHDt4DM= github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997/go.mod h1:saryXNaL624mlulV138FP+HhVw7IpvETUXLS3nTvH1g= -github.com/xanzy/go-gitlab v0.91.1 h1:gnV57IPGYywWer32oXKBcdmc8dVxeKl3AauV8Bu17rw= -github.com/xanzy/go-gitlab v0.91.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= +github.com/xanzy/go-gitlab v0.92.1 h1:4HfRQtGtGd1M/Xn3G6hOikfWaysL7/G6y4EEzVKINPs= +github.com/xanzy/go-gitlab v0.92.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 68b10da2f27b0b206d579ad3a75dc1fdbea85b0b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Sep 2023 01:32:16 +0000 Subject: [PATCH 28/35] fix(deps): update module github.com/redis/go-redis/v9 to v9.2.1 in go.mod (#3798) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 96a7805cce..32c0812dfc 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/petergtz/pegomock/v4 v4.0.0 github.com/pkg/errors v0.9.1 - github.com/redis/go-redis/v9 v9.2.0 + github.com/redis/go-redis/v9 v9.2.1 github.com/remeh/sizedwaitgroup v1.0.0 github.com/shurcooL/githubv4 v0.0.0-20230704064427-599ae7bbf278 github.com/slack-go/slack v0.12.3 diff --git a/go.sum b/go.sum index d66e918f22..035b1c9e3f 100644 --- a/go.sum +++ b/go.sum @@ -388,8 +388,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= -github.com/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI= -github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= From d9d6ff6588a5b050c5be9d6a51d2dda0d166b152 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Sep 2023 01:53:05 +0000 Subject: [PATCH 29/35] chore(deps): update alpine docker tag to v3.18.4 in dockerfile (#3801) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b1c6406426..abfbbd86a2 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.18.3 +ARG ALPINE_TAG=3.18.4 ARG DEBIAN_TAG=12.1-slim # Stage 1: build artifact and download deps From 8c86e439274409f9cb706405f0c896bcf7cc33ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 30 Sep 2023 02:24:25 +0000 Subject: [PATCH 30/35] fix(deps): update module github.com/hashicorp/golang-lru/v2 to v2.0.7 in go.mod (#3803) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 32c0812dfc..1bbc50bb5e 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/hashicorp/go-getter/v2 v2.2.1 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/golang-lru/v2 v2.0.6 + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/terraform-config-inspect v0.0.0-20230925220900-5a6f8d18746d github.com/kr/pretty v0.3.1 github.com/mcdafydd/go-azuredevops v0.12.1 diff --git a/go.sum b/go.sum index 035b1c9e3f..1e217ecfee 100644 --- a/go.sum +++ b/go.sum @@ -261,8 +261,8 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= -github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.18.0 h1:wYnG7Lt31t2zYkcquwgKo6MWXzRUDIeIVU5naZwHLl8= From 010e1e6bc3b759b7d04c752c28b694209869a099 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 02:35:10 +0000 Subject: [PATCH 31/35] fix(deps): update module github.com/xanzy/go-gitlab to v0.92.3 in go.mod (#3804) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1bbc50bb5e..a419c80648 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/uber-go/tally/v4 v4.1.7 github.com/urfave/negroni/v3 v3.0.0 github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997 - github.com/xanzy/go-gitlab v0.92.1 + github.com/xanzy/go-gitlab v0.92.3 go.etcd.io/bbolt v1.3.7 go.uber.org/zap v1.26.0 golang.org/x/term v0.12.0 diff --git a/go.sum b/go.sum index 1e217ecfee..1216f65d19 100644 --- a/go.sum +++ b/go.sum @@ -453,8 +453,8 @@ github.com/urfave/negroni/v3 v3.0.0 h1:Vo8CeZfu1lFR9gW8GnAb6dOGCJyijfil9j/jKKc/J github.com/urfave/negroni/v3 v3.0.0/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs= github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997 h1:be5WC0FHdhimAhe2G3DPhduX117RM8qdTMYCMHDt4DM= github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997/go.mod h1:saryXNaL624mlulV138FP+HhVw7IpvETUXLS3nTvH1g= -github.com/xanzy/go-gitlab v0.92.1 h1:4HfRQtGtGd1M/Xn3G6hOikfWaysL7/G6y4EEzVKINPs= -github.com/xanzy/go-gitlab v0.92.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= +github.com/xanzy/go-gitlab v0.92.3 h1:bMtUHSV5BIhKeka6RyjLOOMZ31byVGDN5pGWmqBsIUs= +github.com/xanzy/go-gitlab v0.92.3/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From fe126560da48337b3d7817ae9e842fda2c57c139 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 00:42:49 +0000 Subject: [PATCH 32/35] chore(deps): update dependency open-policy-agent/conftest to v0.46.0 in dockerfile (#3805) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index abfbbd86a2..aa44f0e74c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,7 +60,7 @@ WORKDIR /tmp/build # install conftest # renovate: datasource=github-releases depName=open-policy-agent/conftest -ENV DEFAULT_CONFTEST_VERSION=0.45.0 +ENV DEFAULT_CONFTEST_VERSION=0.46.0 SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN AVAILABLE_CONFTEST_VERSIONS=${DEFAULT_CONFTEST_VERSION} && \ case ${TARGETPLATFORM} in \ From 4bf3a3b34dac17a81048645a907e2fd04410d6a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 00:24:39 +0000 Subject: [PATCH 33/35] chore(deps): update dependency open-policy-agent/conftest to v0.46.0 in testing/dockerfile (#3809) 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 22527bf5f8..762ee7fd9b 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.45.0 +ENV CONFTEST_VERSION=0.46.0 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 604c42a86340cd95a9539a3fa92ce80b7251a041 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 05:15:20 +0000 Subject: [PATCH 34/35] fix(deps): update module github.com/go-playground/validator/v10 to v10.15.5 in go.mod (#3808) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a419c80648..16295256da 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/briandowns/spinner v1.23.0 github.com/cactus/go-statsd-client/v5 v5.1.0 github.com/go-ozzo/ozzo-validation v3.6.0+incompatible - github.com/go-playground/validator/v10 v10.15.4 + github.com/go-playground/validator/v10 v10.15.5 github.com/go-test/deep v1.1.0 github.com/golang-jwt/jwt/v5 v5.0.0 github.com/google/go-github/v54 v54.0.0 diff --git a/go.sum b/go.sum index 1216f65d19..f919ca8de0 100644 --- a/go.sum +++ b/go.sum @@ -144,8 +144,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.4 h1:zMXza4EpOdooxPel5xDqXEdXG5r+WggpvnAKMsalBjs= -github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= +github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= From ee7ab5cc3bfb08fe4dbe8e8e592cb7d0f36dcd11 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 02:25:34 +0000 Subject: [PATCH 35/35] fix(deps): update module github.com/uber-go/tally/v4 to v4.1.9 in go.mod (#3813) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 16295256da..2ffdd2ba9e 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 - github.com/uber-go/tally/v4 v4.1.7 + github.com/uber-go/tally/v4 v4.1.9 github.com/urfave/negroni/v3 v3.0.0 github.com/warrensbox/terraform-switcher v0.1.1-0.20221027055942-201c8e92e997 github.com/xanzy/go-gitlab v0.92.3 diff --git a/go.sum b/go.sum index f919ca8de0..12efd33b32 100644 --- a/go.sum +++ b/go.sum @@ -445,8 +445,8 @@ github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= -github.com/uber-go/tally/v4 v4.1.7 h1:YiKvvMKCCXlCKXI0i1hVk+xda8YxdIpjeFXohpvn8Zo= -github.com/uber-go/tally/v4 v4.1.7/go.mod h1:pPR56rjthjtLB8xQlEx2I1VwAwRGCh/i4xMUcmG+6z4= +github.com/uber-go/tally/v4 v4.1.9 h1:AorgM0Ix5aCjSSE0SyKLC4JUQE439d64i3ayxEEYeTs= +github.com/uber-go/tally/v4 v4.1.9/go.mod h1:pPR56rjthjtLB8xQlEx2I1VwAwRGCh/i4xMUcmG+6z4= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/negroni/v3 v3.0.0 h1:Vo8CeZfu1lFR9gW8GnAb6dOGCJyijfil9j/jKKc/JhU=