diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 8ca42d1d5d..c6cc7b846d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -9,7 +9,8 @@ automerge: true, baseBranches: [ 'main', - '/^release-.*/', + 'release-0.31', + 'release-0.32', ], platformAutomerge: true, labels: [ diff --git a/.github/workflows/atlantis-image.yml b/.github/workflows/atlantis-image.yml index 02f0f2dcec..0b8e8019df 100644 --- a/.github/workflows/atlantis-image.yml +++ b/.github/workflows/atlantis-image.yml @@ -53,6 +53,7 @@ jobs: strategy: matrix: image_type: [alpine, debian] + platform: [linux/arm64/v8, linux/amd64, linux/arm/v7] runs-on: ubuntu-24.04 env: # Set docker repo to either the fork or the main repo where the branch exists @@ -69,6 +70,11 @@ jobs: with: dockerfile: "Dockerfile" + - name: Set up Go + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version-file: "go.mod" + - name: Set up QEMU uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3 with: @@ -82,6 +88,10 @@ jobs: driver-opts: | image=moby/buildkit:v0.14.0 + - name: "Install cosign" + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 + if: env.PUSH == 'true' && github.event_name != 'pull_request' + # release version is the name of the tag i.e. v0.10.0 # release version also has the image type appended i.e. v0.10.0-alpine # release tag is either pre-release or latest i.e. latest @@ -146,21 +156,38 @@ jobs: ATLANTIS_VERSION=${{ env.RELEASE_VERSION }} ATLANTIS_COMMIT=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} ATLANTIS_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} - platforms: linux/arm64/v8,linux/amd64,linux/arm/v7 + platforms: ${{ matrix.platform }} push: ${{ env.PUSH }} tags: ${{ steps.meta.outputs.tags }} target: ${{ matrix.image_type }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.description'] }} - - name: "Sign and Attest Image" - if: env.PUSH == 'true' + - name: "Create Image Attestation" + if: env.PUSH == 'true' && github.event_name != 'pull_request' uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 with: subject-digest: ${{ steps.build.outputs.digest }} subject-name: ghcr.io/${{ github.repository }} push-to-registry: true + - name: "Sign images with environment annotations" + # no key needed, we're using the GitHub OIDC flow + # Only run on alpine/amd64 build to avoid signing multiple times + if: env.PUSH == 'true' && github.event_name != 'pull_request' && matrix.image_type == 'alpine' && matrix.platform == 'linux/amd64' + run: | + # Sign dev tags, version tags, and latest tags + echo "${TAGS}" | xargs -I {} cosign sign \ + --yes \ + --recursive=true \ + -a actor=${{ github.actor}} \ + -a ref_name=${{ github.ref_name}} \ + -a ref=${{ github.sha }} \ + {}@${DIGEST} + env: + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build.outputs.digest }} + test: needs: [changes] if: needs.changes.outputs.should-run-build == 'true' @@ -169,6 +196,7 @@ jobs: strategy: matrix: image_type: [alpine, debian] + platform: [linux/arm64/v8, linux/amd64, linux/arm/v7] env: # Set docker repo to either the fork or the main repo where the branch exists DOCKER_REPO: ghcr.io/${{ github.repository }} @@ -215,4 +243,5 @@ jobs: image_type: [alpine, debian] runs-on: ubuntu-24.04 steps: - - run: 'echo "No build required"' \ No newline at end of file + - run: 'echo "No build required"' + diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index afef23747f..15895346c6 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: markdown-lint - uses: DavidAnson/markdownlint-cli2-action@eb5ca3ab411449c66620fe7f1b3c9e10547144b0 # v18 + uses: DavidAnson/markdownlint-cli2-action@a23dae216ce3fee4db69da41fed90d2a4af801cf # v19 with: config: .markdownlint.yaml globs: 'runatlantis.io/**/*.md' diff --git a/Dockerfile b/Dockerfile index 186061acf4..ed8d0b5fe7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ARG GOLANG_TAG=1.23.4-alpine@sha256:6c5c9590f169f77c8046e45c611d3b28fe477789acd8 # renovate: datasource=github-releases depName=hashicorp/terraform versioning=hashicorp ARG DEFAULT_TERRAFORM_VERSION=1.10.3 # renovate: datasource=github-releases depName=opentofu/opentofu versioning=hashicorp -ARG DEFAULT_OPENTOFU_VERSION=1.8.7 +ARG DEFAULT_OPENTOFU_VERSION=1.8.8 # renovate: datasource=github-releases depName=open-policy-agent/conftest ARG DEFAULT_CONFTEST_VERSION=0.56.0 @@ -122,7 +122,7 @@ RUN ./download-release.sh \ "terraform" \ "${TARGETPLATFORM}" \ "${DEFAULT_TERRAFORM_VERSION}" \ - "1.6.6 1.7.5 1.8.5 ${DEFAULT_TERRAFORM_VERSION}" \ + "1.8.5 1.9.8 ${DEFAULT_TERRAFORM_VERSION}" \ && ./download-release.sh \ "tofu" \ "${TARGETPLATFORM}" \ diff --git a/cmd/server.go b/cmd/server.go index 5722b38cfa..aa8581e705 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -72,6 +72,7 @@ const ( CheckoutStrategyFlag = "checkout-strategy" ConfigFlag = "config" DataDirFlag = "data-dir" + DefaultTFDistributionFlag = "default-tf-distribution" DefaultTFVersionFlag = "default-tf-version" DisableApplyAllFlag = "disable-apply-all" DisableAutoplanFlag = "disable-autoplan" @@ -141,7 +142,7 @@ const ( SSLCertFileFlag = "ssl-cert-file" SSLKeyFileFlag = "ssl-key-file" RestrictFileList = "restrict-file-list" - TFDistributionFlag = "tf-distribution" + TFDistributionFlag = "tf-distribution" // deprecated for DefaultTFDistributionFlag TFDownloadFlag = "tf-download" TFDownloadURLFlag = "tf-download-url" UseTFPluginCache = "use-tf-plugin-cache" @@ -421,8 +422,8 @@ var stringFlags = map[string]stringFlag{ description: fmt.Sprintf("File containing x509 private key matching --%s.", SSLCertFileFlag), }, TFDistributionFlag: { - description: fmt.Sprintf("Which TF distribution to use. Can be set to %s or %s.", TFDistributionTerraform, TFDistributionOpenTofu), - defaultValue: DefaultTFDistribution, + description: "[Deprecated for --default-tf-distribution].", + hidden: true, }, TFDownloadURLFlag: { description: "Base URL to download Terraform versions from.", @@ -437,6 +438,10 @@ var stringFlags = map[string]stringFlag{ " Only set if using TFC/E as a remote backend." + " Should be specified via the ATLANTIS_TFE_TOKEN environment variable for security.", }, + DefaultTFDistributionFlag: { + description: fmt.Sprintf("Which TF distribution to use. Can be set to %s or %s.", TFDistributionTerraform, TFDistributionOpenTofu), + defaultValue: DefaultTFDistribution, + }, DefaultTFVersionFlag: { description: "Terraform version to default to (ex. v0.12.0). Will download if not yet on disk." + " If not set, Atlantis uses the terraform binary in its PATH.", @@ -840,12 +845,13 @@ func (s *ServerCmd) run() error { // Config looks good. Start the server. server, err := s.ServerCreator.NewServer(userConfig, server.Config{ - AllowForkPRsFlag: AllowForkPRsFlag, - AtlantisURLFlag: AtlantisURLFlag, - AtlantisVersion: s.AtlantisVersion, - DefaultTFVersionFlag: DefaultTFVersionFlag, - RepoConfigJSONFlag: RepoConfigJSONFlag, - SilenceForkPRErrorsFlag: SilenceForkPRErrorsFlag, + AllowForkPRsFlag: AllowForkPRsFlag, + AtlantisURLFlag: AtlantisURLFlag, + AtlantisVersion: s.AtlantisVersion, + DefaultTFDistributionFlag: DefaultTFDistributionFlag, + DefaultTFVersionFlag: DefaultTFVersionFlag, + RepoConfigJSONFlag: RepoConfigJSONFlag, + SilenceForkPRErrorsFlag: SilenceForkPRErrorsFlag, }) if err != nil { @@ -921,8 +927,11 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig, v *viper.Viper) { if c.RedisPort == 0 { c.RedisPort = DefaultRedisPort } - if c.TFDistribution == "" { - c.TFDistribution = DefaultTFDistribution + if c.TFDistribution != "" && c.DefaultTFDistribution == "" { + c.DefaultTFDistribution = c.TFDistribution + } + if c.DefaultTFDistribution == "" { + c.DefaultTFDistribution = DefaultTFDistribution } if c.TFDownloadURL == "" { c.TFDownloadURL = DefaultTFDownloadURL @@ -953,7 +962,7 @@ func (s *ServerCmd) validate(userConfig server.UserConfig) error { return fmt.Errorf("invalid log level: must be one of %v", ValidLogLevels) } - if userConfig.TFDistribution != TFDistributionTerraform && userConfig.TFDistribution != TFDistributionOpenTofu { + if userConfig.DefaultTFDistribution != TFDistributionTerraform && userConfig.DefaultTFDistribution != TFDistributionOpenTofu { return fmt.Errorf("invalid tf distribution: expected one of %s or %s", TFDistributionTerraform, TFDistributionOpenTofu) } @@ -1172,6 +1181,10 @@ func (s *ServerCmd) deprecationWarnings(userConfig *server.UserConfig) error { // } // + if userConfig.TFDistribution != "" { + deprecatedFlags = append(deprecatedFlags, TFDistributionFlag) + } + if len(deprecatedFlags) > 0 { warning := "WARNING: " if len(deprecatedFlags) == 1 { diff --git a/cmd/server_test.go b/cmd/server_test.go index c14e43cdd6..7d7c1b52d5 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -73,6 +73,7 @@ var testFlags = map[string]interface{}{ CheckoutStrategyFlag: CheckoutStrategyMerge, CheckoutDepthFlag: 0, DataDirFlag: "/path", + DefaultTFDistributionFlag: "terraform", DefaultTFVersionFlag: "v0.11.0", DisableApplyAllFlag: true, DisableMarkdownFoldingFlag: true, @@ -977,6 +978,46 @@ func TestExecute_AutoplanFileList(t *testing.T) { } } +func TestExecute_ValidateDefaultTFDistribution(t *testing.T) { + cases := []struct { + description string + flags map[string]interface{} + expectErr string + }{ + { + "terraform", + map[string]interface{}{ + DefaultTFDistributionFlag: "terraform", + }, + "", + }, + { + "opentofu", + map[string]interface{}{ + DefaultTFDistributionFlag: "opentofu", + }, + "", + }, + { + "errs on invalid distribution", + map[string]interface{}{ + DefaultTFDistributionFlag: "invalid_distribution", + }, + "invalid tf distribution: expected one of terraform or opentofu", + }, + } + for _, testCase := range cases { + t.Log("Should validate default tf distribution when " + testCase.description) + c := setupWithDefaults(testCase.flags, t) + err := c.Execute() + if testCase.expectErr != "" { + ErrEquals(t, testCase.expectErr, err) + } else { + Ok(t, err) + } + } +} + func setup(flags map[string]interface{}, t *testing.T) *cobra.Command { vipr := viper.New() for k, v := range flags { diff --git a/e2e/github.go b/e2e/github.go index 5037b52e87..b86145d846 100644 --- a/e2e/github.go +++ b/e2e/github.go @@ -21,7 +21,7 @@ import ( "os/exec" "strings" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" ) type GithubClient struct { @@ -90,7 +90,7 @@ func (g GithubClient) CreateAtlantisWebhook(ctx context.Context, hookURL string) atlantisHook := &github.Hook{ Events: []string{"issue_comment", "pull_request", "push"}, Config: hookConfig, - Active: github.Bool(true), + Active: github.Ptr(true), } hook, _, err := g.client.Repositories.CreateHook(ctx, g.ownerName, g.repoName, atlantisHook) @@ -146,7 +146,7 @@ func (g GithubClient) GetAtlantisStatus(ctx context.Context, branchName string) func (g GithubClient) ClosePullRequest(ctx context.Context, pullRequestNumber int) error { // clean up - _, _, err := g.client.PullRequests.Edit(ctx, g.ownerName, g.repoName, pullRequestNumber, &github.PullRequest{State: github.String("closed")}) + _, _, err := g.client.PullRequests.Edit(ctx, g.ownerName, g.repoName, pullRequestNumber, &github.PullRequest{State: github.Ptr("closed")}) if err != nil { return fmt.Errorf("error while closing new pull request: %v", err) } diff --git a/e2e/gitlab.go b/e2e/gitlab.go index 2226aa299d..a8f6449f49 100644 --- a/e2e/gitlab.go +++ b/e2e/gitlab.go @@ -20,7 +20,7 @@ import ( "os" "os/exec" - "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) type GitlabClient struct { diff --git a/e2e/go.mod b/e2e/go.mod index 1d706f3f8a..f600c3f52d 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -3,20 +3,20 @@ module github.com/runatlantis/atlantis/e2e go 1.23.4 require ( - github.com/google/go-github/v66 v66.0.0 + github.com/google/go-github/v68 v68.0.0 github.com/hashicorp/go-multierror v1.1.1 - github.com/xanzy/go-gitlab v0.114.0 + gitlab.com/gitlab-org/api/client-go v0.118.0 ) require ( - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/sys v0.28.0 // indirect golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.36.0 // indirect ) diff --git a/e2e/go.sum b/e2e/go.sum index 17d5056f99..825bce8d88 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -2,16 +2,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.2/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M= -github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4= +github.com/google/go-github/v68 v68.0.0 h1:ZW57zeNZiXTdQ16qrDiZ0k6XucrxZ2CGmoTvcCyQG6s= +github.com/google/go-github/v68 v68.0.0/go.mod h1:K9HAUBovM2sLwM408A18h+wd9vqdLOEqTUCbnRIcx68= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -31,30 +31,46 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/xanzy/go-gitlab v0.114.0 h1:0wQr/KBckwrZPfEMjRqpUz0HmsKKON9UhCYv9KDy19M= -github.com/xanzy/go-gitlab v0.114.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/gitlab-org/api/client-go v0.118.0 h1:qHIEw+XHt+2xuk4iZGW8fc6t+gTLAGEmTA5Bzp/brxs= +gitlab.com/gitlab-org/api/client-go v0.118.0/go.mod h1:E+X2dndIYDuUfKVP0C3jhkWvTSE00BkLbCsXTY3edDo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.mod b/go.mod index 0328202cbd..aff4490023 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,14 @@ require ( code.gitea.io/sdk/gitea v0.19.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/alicebob/miniredis/v2 v2.34.0 - github.com/bradleyfalzon/ghinstallation/v2 v2.12.0 + github.com/bradleyfalzon/ghinstallation/v2 v2.13.0 github.com/briandowns/spinner v1.23.1 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.23.0 github.com/go-test/deep v1.1.1 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/go-github/v66 v66.0.0 + github.com/google/go-github/v68 v68.0.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 @@ -45,7 +45,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/uber-go/tally/v4 v4.1.16 github.com/urfave/negroni/v3 v3.1.1 - github.com/xanzy/go-gitlab v0.114.0 + gitlab.com/gitlab-org/api/client-go v0.118.0 go.etcd.io/bbolt v1.3.11 go.uber.org/zap v1.27.0 golang.org/x/term v0.27.0 @@ -88,7 +88,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/css v1.0.1 // indirect @@ -140,6 +140,6 @@ require ( golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.36.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 5e21b61577..e67437d296 100644 --- a/go.sum +++ b/go.sum @@ -75,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.12.0 h1:k8oVjGhZel2qmCUsYwSE34jPNT9DL2wCBOtugsHv26g= -github.com/bradleyfalzon/ghinstallation/v2 v2.12.0/go.mod h1:V4gJcNyAftH0rXpRp1SUVUuh+ACxOH1xOk/ZzkRHltg= +github.com/bradleyfalzon/ghinstallation/v2 v2.13.0 h1:5FhjW93/YLQJDmPdeyMPw7IjAPzqsr+0jHPfrPz0sZI= +github.com/bradleyfalzon/ghinstallation/v2 v2.13.0/go.mod h1:EJ6fgedVEHa2kUyBTTvslJCXJafS/mhJNNKEOCspZXQ= github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650= github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -199,8 +199,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -216,8 +216,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M= -github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4= +github.com/google/go-github/v68 v68.0.0 h1:ZW57zeNZiXTdQ16qrDiZ0k6XucrxZ2CGmoTvcCyQG6s= +github.com/google/go-github/v68 v68.0.0/go.mod h1:K9HAUBovM2sLwM408A18h+wd9vqdLOEqTUCbnRIcx68= 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= @@ -461,8 +461,6 @@ 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.1.1 h1:6MS4nG9Jk/UuCACaUlNXCbiKa0ywF9LXz5dGu09v8hw= github.com/urfave/negroni/v3 v3.1.1/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs= -github.com/xanzy/go-gitlab v0.114.0 h1:0wQr/KBckwrZPfEMjRqpUz0HmsKKON9UhCYv9KDy19M= -github.com/xanzy/go-gitlab v0.114.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -476,6 +474,8 @@ github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8 github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +gitlab.com/gitlab-org/api/client-go v0.118.0 h1:qHIEw+XHt+2xuk4iZGW8fc6t+gTLAGEmTA5Bzp/brxs= +gitlab.com/gitlab-org/api/client-go v0.118.0/go.mod h1:E+X2dndIYDuUfKVP0C3jhkWvTSE00BkLbCsXTY3edDo= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -806,8 +806,8 @@ 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/runatlantis.io/docs/repo-level-atlantis-yaml.md b/runatlantis.io/docs/repo-level-atlantis-yaml.md index a5e89d20a4..25bf7ce160 100644 --- a/runatlantis.io/docs/repo-level-atlantis-yaml.md +++ b/runatlantis.io/docs/repo-level-atlantis-yaml.md @@ -66,6 +66,7 @@ projects: branch: /main/ dir: . workspace: default + terraform_distribution: terraform terraform_version: v0.11.0 delete_source_branch_on_merge: true repo_locking: true # deprecated: use repo_locks instead @@ -262,6 +263,20 @@ See [Custom Workflow Use Cases: Terragrunt](custom-workflows.md#terragrunt) See [Custom Workflow Use Cases: Running custom commands](custom-workflows.md#running-custom-commands) +### Terraform Distributions + +If you'd like to use a different distribution of Terraform than what is set +by the `--default-tf-version` flag, then set the `terraform_distribution` key: + +```yaml +version: 3 +projects: +- dir: project1 + terraform_distribution: opentofu +``` + +Atlantis will automatically download and use this distribution. Valid values are `terraform` and `opentofu`. + ### Terraform Versions If you'd like to use a different version of Terraform than what is in Atlantis' diff --git a/runatlantis.io/docs/server-configuration.md b/runatlantis.io/docs/server-configuration.md index 54d53f0d60..cf290dc5ca 100644 --- a/runatlantis.io/docs/server-configuration.md +++ b/runatlantis.io/docs/server-configuration.md @@ -386,6 +386,16 @@ and set `--autoplan-modules` to `false`. Note that the atlantis user is restricted to `~/.atlantis`. If you set the `--data-dir` flag to a path outside of Atlantis its home directory, ensure that you grant the atlantis user the correct permissions. +### `--default-tf-distribution` + + ```bash + atlantis server --default-tf-distribution="terraform" + # or + ATLANTIS_DEFAULT_TF_DISTRIBUTION="terraform" + ``` + + Which TF distribution to use. Can be set to `terraform` or `opentofu`. + ### `--default-tf-version` ```bash @@ -1259,13 +1269,8 @@ This is useful when you have many projects and want to keep the pull request cle ### `--tf-distribution` - ```bash - atlantis server --tf-distribution="terraform" - # or - ATLANTIS_TF_DISTRIBUTION="terraform" - ``` - - Which TF distribution to use. Can be set to `terraform` or `opentofu`. + + Deprecated for `--default-tf-distribution`. ### `--tf-download` diff --git a/server/controllers/events/events_controller.go b/server/controllers/events/events_controller.go index c838134132..ba9dd1d5c0 100644 --- a/server/controllers/events/events_controller.go +++ b/server/controllers/events/events_controller.go @@ -22,7 +22,7 @@ import ( "strconv" "strings" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/mcdafydd/go-azuredevops/azuredevops" "github.com/microcosm-cc/bluemonday" "github.com/pkg/errors" @@ -34,7 +34,7 @@ import ( "github.com/runatlantis/atlantis/server/events/vcs/gitea" "github.com/runatlantis/atlantis/server/logging" tally "github.com/uber-go/tally/v4" - gitlab "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) const githubHeader = "X-Github-Event" diff --git a/server/controllers/events/events_controller_e2e_test.go b/server/controllers/events/events_controller_e2e_test.go index a9d4fe70a1..3b66a28225 100644 --- a/server/controllers/events/events_controller_e2e_test.go +++ b/server/controllers/events/events_controller_e2e_test.go @@ -13,7 +13,7 @@ import ( "strings" "testing" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" @@ -29,6 +29,7 @@ import ( mock_policy "github.com/runatlantis/atlantis/server/core/runtime/policy/mocks" "github.com/runatlantis/atlantis/server/core/terraform" terraform_mocks "github.com/runatlantis/atlantis/server/core/terraform/mocks" + "github.com/runatlantis/atlantis/server/core/terraform/tfclient" "github.com/runatlantis/atlantis/server/events" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/mocks" @@ -1319,7 +1320,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers mockDownloader := terraform_mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - terraformClient, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "", "default-tf-version", "https://releases.hashicorp.com", true, false, projectCmdOutputHandler) + terraformClient, err := tfclient.NewClient(logger, distribution, binDir, cacheDir, "", "", "", "default-tf-version", "https://releases.hashicorp.com", true, false, projectCmdOutputHandler) Ok(t, err) boltdb, err := db.New(dataDir) Ok(t, err) @@ -1346,6 +1347,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers } } + defaultTFDistribution := terraformClient.DefaultDistribution() defaultTFVersion := terraformClient.DefaultVersion() locker := events.NewDefaultWorkingDirLocker() parser := &config.ParserValidator{} @@ -1429,7 +1431,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers terraformClient, ) - showStepRunner, err := runtime.NewShowStepRunner(terraformClient, defaultTFVersion) + showStepRunner, err := runtime.NewShowStepRunner(terraformClient, defaultTFDistribution, defaultTFVersion) Ok(t, err) @@ -1440,6 +1442,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers conftextExec.VersionCache = &LocalConftestCache{} policyCheckRunner, err := runtime.NewPolicyCheckStepRunner( + defaultTFDistribution, defaultTFVersion, conftextExec, ) @@ -1451,11 +1454,13 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers Locker: projectLocker, LockURLGenerator: &mockLockURLGenerator{}, InitStepRunner: &runtime.InitStepRunner{ - TerraformExecutor: terraformClient, - DefaultTFVersion: defaultTFVersion, + TerraformExecutor: terraformClient, + DefaultTFDistribution: defaultTFDistribution, + DefaultTFVersion: defaultTFVersion, }, PlanStepRunner: runtime.NewPlanStepRunner( terraformClient, + defaultTFDistribution, defaultTFVersion, statusUpdater, asyncTfExec, @@ -1465,10 +1470,11 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers ApplyStepRunner: &runtime.ApplyStepRunner{ TerraformExecutor: terraformClient, }, - ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTFVersion), - StateRmStepRunner: runtime.NewStateRmStepRunner(terraformClient, defaultTFVersion), + ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTFDistribution, defaultTFVersion), + StateRmStepRunner: runtime.NewStateRmStepRunner(terraformClient, defaultTFDistribution, defaultTFVersion), RunStepRunner: &runtime.RunStepRunner{ TerraformExecutor: terraformClient, + DefaultTFDistribution: defaultTFDistribution, DefaultTFVersion: defaultTFVersion, ProjectCmdOutputHandler: projectCmdOutputHandler, }, @@ -1695,26 +1701,26 @@ func GitHubPullRequestParsed(headSHA string) *github.PullRequest { headSHA = "13940d121be73f656e2132c6d7b4c8e87878ac8d" } return &github.PullRequest{ - Number: github.Int(2), - State: github.String("open"), - HTMLURL: github.String("htmlurl"), + Number: github.Ptr(2), + State: github.Ptr("open"), + HTMLURL: github.Ptr("htmlurl"), Head: &github.PullRequestBranch{ Repo: &github.Repository{ - FullName: github.String("runatlantis/atlantis-tests"), - CloneURL: github.String("https://github.com/runatlantis/atlantis-tests.git"), + FullName: github.Ptr("runatlantis/atlantis-tests"), + CloneURL: github.Ptr("https://github.com/runatlantis/atlantis-tests.git"), }, - SHA: github.String(headSHA), - Ref: github.String("branch"), + SHA: github.Ptr(headSHA), + Ref: github.Ptr("branch"), }, Base: &github.PullRequestBranch{ Repo: &github.Repository{ - FullName: github.String("runatlantis/atlantis-tests"), - CloneURL: github.String("https://github.com/runatlantis/atlantis-tests.git"), + FullName: github.Ptr("runatlantis/atlantis-tests"), + CloneURL: github.Ptr("https://github.com/runatlantis/atlantis-tests.git"), }, - Ref: github.String("main"), + Ref: github.Ptr("main"), }, User: &github.User{ - Login: github.String("atlantisbot"), + Login: github.Ptr("atlantisbot"), }, } } diff --git a/server/controllers/events/events_controller_test.go b/server/controllers/events/events_controller_test.go index 11bcec3445..f4c563552c 100644 --- a/server/controllers/events/events_controller_test.go +++ b/server/controllers/events/events_controller_test.go @@ -25,7 +25,7 @@ import ( "strings" "testing" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/mcdafydd/go-azuredevops/azuredevops" . "github.com/petergtz/pegomock/v4" events_controllers "github.com/runatlantis/atlantis/server/controllers/events" @@ -38,7 +38,7 @@ import ( "github.com/runatlantis/atlantis/server/logging" "github.com/runatlantis/atlantis/server/metrics" . "github.com/runatlantis/atlantis/testing" - gitlab "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) const githubHeader = "X-Github-Event" diff --git a/server/controllers/events/github_request_validator.go b/server/controllers/events/github_request_validator.go index 89ae67e6b2..dc8b89f560 100644 --- a/server/controllers/events/github_request_validator.go +++ b/server/controllers/events/github_request_validator.go @@ -19,7 +19,7 @@ import ( "io" "net/http" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" ) //go:generate pegomock generate --package mocks -o mocks/mock_github_request_validator.go GithubRequestValidator diff --git a/server/controllers/events/gitlab_request_parser_validator.go b/server/controllers/events/gitlab_request_parser_validator.go index 5d58dba831..22d2e08e0f 100644 --- a/server/controllers/events/gitlab_request_parser_validator.go +++ b/server/controllers/events/gitlab_request_parser_validator.go @@ -20,7 +20,7 @@ import ( "io" "net/http" - gitlab "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) const secretHeader = "X-Gitlab-Token" // #nosec diff --git a/server/controllers/events/gitlab_request_parser_validator_test.go b/server/controllers/events/gitlab_request_parser_validator_test.go index 184b9f00b7..fb61c4ff9d 100644 --- a/server/controllers/events/gitlab_request_parser_validator_test.go +++ b/server/controllers/events/gitlab_request_parser_validator_test.go @@ -22,7 +22,7 @@ import ( . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/controllers/events" . "github.com/runatlantis/atlantis/testing" - gitlab "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) var parser = events.DefaultGitlabRequestParserValidator{} diff --git a/server/core/config/parser_validator_test.go b/server/core/config/parser_validator_test.go index c21187bc47..05299aa725 100644 --- a/server/core/config/parser_validator_test.go +++ b/server/core/config/parser_validator_test.go @@ -610,6 +610,31 @@ workflows: }, }, }, + { + description: "project field with terraform_distribution set to opentofu", + input: ` +version: 3 +projects: +- dir: . + workspace: myworkspace + terraform_distribution: opentofu +`, + exp: valid.RepoCfg{ + Version: 3, + Projects: []valid.Project{ + { + Dir: ".", + Workspace: "myworkspace", + TerraformDistribution: String("opentofu"), + Autoplan: valid.Autoplan{ + WhenModified: raw.DefaultAutoPlanWhenModified, + Enabled: true, + }, + }, + }, + Workflows: make(map[string]valid.Workflow), + }, + }, { description: "project dir with ..", input: ` diff --git a/server/core/config/raw/project.go b/server/core/config/raw/project.go index fe0e656a8c..5b389c8605 100644 --- a/server/core/config/raw/project.go +++ b/server/core/config/raw/project.go @@ -26,6 +26,7 @@ type Project struct { Dir *string `yaml:"dir,omitempty"` Workspace *string `yaml:"workspace,omitempty"` Workflow *string `yaml:"workflow,omitempty"` + TerraformDistribution *string `yaml:"terraform_distribution,omitempty"` TerraformVersion *string `yaml:"terraform_version,omitempty"` Autoplan *Autoplan `yaml:"autoplan,omitempty"` PlanRequirements []string `yaml:"plan_requirements,omitempty"` @@ -86,6 +87,7 @@ func (p Project) Validate() error { validation.Field(&p.PlanRequirements, validation.By(validPlanReq)), validation.Field(&p.ApplyRequirements, validation.By(validApplyReq)), validation.Field(&p.ImportRequirements, validation.By(validImportReq)), + validation.Field(&p.TerraformDistribution, validation.By(validDistribution)), validation.Field(&p.TerraformVersion, validation.By(VersionValidator)), validation.Field(&p.DependsOn, validation.By(DependsOn)), validation.Field(&p.Name, validation.By(validName)), @@ -118,6 +120,9 @@ func (p Project) ToValid() valid.Project { if p.TerraformVersion != nil { v.TerraformVersion, _ = version.NewVersion(*p.TerraformVersion) } + if p.TerraformDistribution != nil { + v.TerraformDistribution = p.TerraformDistribution + } if p.Autoplan == nil { v.Autoplan = DefaultAutoPlan() } else { @@ -202,3 +207,11 @@ func validImportReq(value interface{}) error { } return nil } + +func validDistribution(value interface{}) error { + distribution := value.(*string) + if distribution != nil && *distribution != "terraform" && *distribution != "opentofu" { + return fmt.Errorf("'%s' is not a valid terraform_distribution, only '%s' and '%s' are supported", *distribution, "terraform", "opentofu") + } + return nil +} diff --git a/server/core/config/valid/global_cfg.go b/server/core/config/valid/global_cfg.go index b0bdc86822..48a78f7158 100644 --- a/server/core/config/valid/global_cfg.go +++ b/server/core/config/valid/global_cfg.go @@ -105,6 +105,7 @@ type MergedProjectCfg struct { AutoplanEnabled bool AutoMergeDisabled bool AutoMergeMethod string + TerraformDistribution *string TerraformVersion *version.Version RepoCfgVersion int PolicySets PolicySets @@ -412,6 +413,7 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro DependsOn: proj.DependsOn, Name: proj.GetName(), AutoplanEnabled: proj.Autoplan.Enabled, + TerraformDistribution: proj.TerraformDistribution, TerraformVersion: proj.TerraformVersion, RepoCfgVersion: rCfg.Version, PolicySets: g.PolicySets, @@ -438,6 +440,7 @@ func (g GlobalCfg) DefaultProjCfg(log logging.SimpleLogging, repoID string, repo Workspace: workspace, Name: "", AutoplanEnabled: DefaultAutoPlanEnabled, + TerraformDistribution: nil, TerraformVersion: nil, PolicySets: g.PolicySets, DeleteSourceBranchOnMerge: deleteSourceBranchOnMerge, diff --git a/server/core/config/valid/repo_cfg.go b/server/core/config/valid/repo_cfg.go index 4612f72cec..8478ce3dd0 100644 --- a/server/core/config/valid/repo_cfg.go +++ b/server/core/config/valid/repo_cfg.go @@ -147,6 +147,7 @@ type Project struct { Workspace string Name *string WorkflowName *string + TerraformDistribution *string TerraformVersion *version.Version Autoplan Autoplan PlanRequirements []string diff --git a/server/core/runtime/apply_step_runner.go b/server/core/runtime/apply_step_runner.go index 2e223f2996..35a864cfc8 100644 --- a/server/core/runtime/apply_step_runner.go +++ b/server/core/runtime/apply_step_runner.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" version "github.com/hashicorp/go-version" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/utils" @@ -17,10 +18,11 @@ import ( // ApplyStepRunner runs `terraform apply`. type ApplyStepRunner struct { - TerraformExecutor TerraformExec - DefaultTFVersion *version.Version - CommitStatusUpdater StatusUpdater - AsyncTFExec AsyncTFExec + TerraformExecutor TerraformExec + DefaultTFDistribution terraform.Distribution + DefaultTFVersion *version.Version + CommitStatusUpdater StatusUpdater + AsyncTFExec AsyncTFExec } func (a *ApplyStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) { @@ -39,11 +41,19 @@ func (a *ApplyStepRunner) Run(ctx command.ProjectContext, extraArgs []string, pa ctx.Log.Info("starting apply") var out string + tfDistribution := a.DefaultTFDistribution + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } + tfVersion := a.DefaultTFVersion + if ctx.TerraformVersion != nil { + tfVersion = ctx.TerraformVersion + } // TODO: Leverage PlanTypeStepRunnerDelegate here if IsRemotePlan(contents) { args := append(append([]string{"apply", "-input=false", "-no-color"}, extraArgs...), ctx.EscapedCommentArgs...) - out, err = a.runRemoteApply(ctx, args, path, planPath, ctx.TerraformVersion, envs) + out, err = a.runRemoteApply(ctx, args, path, planPath, tfDistribution, tfVersion, envs) if err == nil { out = a.cleanRemoteApplyOutput(out) } @@ -51,7 +61,7 @@ func (a *ApplyStepRunner) Run(ctx command.ProjectContext, extraArgs []string, pa // NOTE: we need to quote the plan path because Bitbucket Server can // have spaces in its repo owner names which is part of the path. args := append(append(append([]string{"apply", "-input=false"}, extraArgs...), ctx.EscapedCommentArgs...), fmt.Sprintf("%q", planPath)) - out, err = a.TerraformExecutor.RunCommandWithVersion(ctx, path, args, envs, ctx.TerraformVersion, ctx.Workspace) + out, err = a.TerraformExecutor.RunCommandWithVersion(ctx, path, args, envs, tfDistribution, tfVersion, ctx.Workspace) } // If the apply was successful, delete the plan. @@ -115,6 +125,7 @@ func (a *ApplyStepRunner) runRemoteApply( applyArgs []string, path string, absPlanPath string, + tfDistribution terraform.Distribution, tfVersion *version.Version, envs map[string]string) (string, error) { // The planfile contents are needed to ensure that the plan didn't change @@ -133,7 +144,7 @@ func (a *ApplyStepRunner) runRemoteApply( // Start the async command execution. ctx.Log.Debug("starting async tf remote operation") - inCh, outCh := a.AsyncTFExec.RunCommandAsync(ctx, filepath.Clean(path), applyArgs, envs, tfVersion, ctx.Workspace) + inCh, outCh := a.AsyncTFExec.RunCommandAsync(ctx, filepath.Clean(path), applyArgs, envs, tfDistribution, tfVersion, ctx.Workspace) var lines []string nextLineIsRunURL := false var runURL string diff --git a/server/core/runtime/apply_step_runner_test.go b/server/core/runtime/apply_step_runner_test.go index 2a31040c81..d9be33e1d6 100644 --- a/server/core/runtime/apply_step_runner_test.go +++ b/server/core/runtime/apply_step_runner_test.go @@ -14,7 +14,9 @@ import ( "github.com/runatlantis/atlantis/server/core/runtime" runtimemocks "github.com/runatlantis/atlantis/server/core/runtime/mocks" runtimemodels "github.com/runatlantis/atlantis/server/core/runtime/models" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/logging" @@ -59,17 +61,20 @@ func TestRun_Success(t *testing.T) { Ok(t, err) RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) o := runtime.ApplyStepRunner{ - TerraformExecutor: terraform, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, } - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := o.Run(ctx, []string{"extra", "args"}, tmpDir, map[string]string(nil)) Ok(t, err) Equals(t, "output", output) - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, []string{"apply", "-input=false", "extra", "args", "comment", "args", fmt.Sprintf("%q", planPath)}, map[string]string(nil), nil, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, []string{"apply", "-input=false", "extra", "args", "comment", "args", fmt.Sprintf("%q", planPath)}, map[string]string(nil), tfDistribution, nil, "workspace") _, err = os.Stat(planPath) Assert(t, os.IsNotExist(err), "planfile should be deleted") } @@ -91,22 +96,24 @@ func TestRun_AppliesCorrectProjectPlan(t *testing.T) { Ok(t, err) RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) o := runtime.ApplyStepRunner{ - TerraformExecutor: terraform, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, } - - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := o.Run(ctx, []string{"extra", "args"}, tmpDir, map[string]string(nil)) Ok(t, err) Equals(t, "output", output) - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, []string{"apply", "-input=false", "extra", "args", "comment", "args", fmt.Sprintf("%q", planPath)}, map[string]string(nil), nil, "default") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, []string{"apply", "-input=false", "extra", "args", "comment", "args", fmt.Sprintf("%q", planPath)}, map[string]string(nil), tfDistribution, nil, "default") _, err = os.Stat(planPath) Assert(t, os.IsNotExist(err), "planfile should be deleted") } -func TestRun_UsesConfiguredTFVersion(t *testing.T) { +func TestApplyStepRunner_TestRun_UsesConfiguredTFVersion(t *testing.T) { tmpDir := t.TempDir() planPath := filepath.Join(tmpDir, "workspace.tfplan") err := os.WriteFile(planPath, nil, 0600) @@ -123,17 +130,55 @@ func TestRun_UsesConfiguredTFVersion(t *testing.T) { } RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) o := runtime.ApplyStepRunner{ - TerraformExecutor: terraform, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, } + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). + ThenReturn("output", nil) + output, err := o.Run(ctx, []string{"extra", "args"}, tmpDir, map[string]string(nil)) + Ok(t, err) + Equals(t, "output", output) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, []string{"apply", "-input=false", "extra", "args", "comment", "args", fmt.Sprintf("%q", planPath)}, map[string]string(nil), tfDistribution, tfVersion, "workspace") + _, err = os.Stat(planPath) + Assert(t, os.IsNotExist(err), "planfile should be deleted") +} - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). +func TestApplyStepRunner_TestRun_UsesConfiguredDistribution(t *testing.T) { + tmpDir := t.TempDir() + planPath := filepath.Join(tmpDir, "workspace.tfplan") + err := os.WriteFile(planPath, nil, 0600) + Ok(t, err) + + logger := logging.NewNoopLogger(t) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + tfVersion, _ := version.NewVersion("0.11.0") + projTFDistribution := "opentofu" + ctx := command.ProjectContext{ + Workspace: "workspace", + RepoRelDir: ".", + EscapedCommentArgs: []string{"comment", "args"}, + TerraformDistribution: &projTFDistribution, + Log: logger, + } + + RegisterMockTestingT(t) + terraform := tfclientmocks.NewMockClient() + o := runtime.ApplyStepRunner{ + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, + } + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), NotEq[tf.Distribution](tfDistribution), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := o.Run(ctx, []string{"extra", "args"}, tmpDir, map[string]string(nil)) Ok(t, err) Equals(t, "output", output) - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, []string{"apply", "-input=false", "extra", "args", "comment", "args", fmt.Sprintf("%q", planPath)}, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(ctx), Eq(tmpDir), Eq([]string{"apply", "-input=false", "extra", "args", "comment", "args", fmt.Sprintf("%q", planPath)}), Eq(map[string]string(nil)), NotEq[tf.Distribution](tfDistribution), Eq(tfVersion), Eq("workspace")) _, err = os.Stat(planPath) Assert(t, os.IsNotExist(err), "planfile should be deleted") } @@ -197,7 +242,7 @@ func TestRun_UsingTarget(t *testing.T) { planPath := filepath.Join(tmpDir, "workspace.tfplan") err := os.WriteFile(planPath, nil, 0600) Ok(t, err) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() step := runtime.ApplyStepRunner{ TerraformExecutor: terraform, } @@ -361,7 +406,7 @@ type remoteApplyMock struct { } // RunCommandAsync fakes out running terraform async. -func (r *remoteApplyMock) RunCommandAsync(_ command.ProjectContext, _ string, args []string, _ map[string]string, _ *version.Version, _ string) (chan<- string, <-chan runtimemodels.Line) { +func (r *remoteApplyMock) RunCommandAsync(_ command.ProjectContext, _ string, args []string, _ map[string]string, _ tf.Distribution, _ *version.Version, _ string) (chan<- string, <-chan runtimemodels.Line) { r.CalledArgs = args in := make(chan string) diff --git a/server/core/runtime/env_step_runner_test.go b/server/core/runtime/env_step_runner_test.go index 0fe86f77f0..7772d56c5f 100644 --- a/server/core/runtime/env_step_runner_test.go +++ b/server/core/runtime/env_step_runner_test.go @@ -5,7 +5,9 @@ import ( "github.com/hashicorp/go-version" "github.com/runatlantis/atlantis/server/core/runtime" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks" @@ -38,12 +40,15 @@ func TestEnvStepRunner_Run(t *testing.T) { }, } RegisterMockTestingT(t) - tfClient := mocks.NewMockClient() + tfClient := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, err := version.NewVersion("0.12.0") Ok(t, err) projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler() runStepRunner := runtime.RunStepRunner{ TerraformExecutor: tfClient, + DefaultTFDistribution: tfDistribution, DefaultTFVersion: tfVersion, ProjectCmdOutputHandler: projectCmdOutputHandler, } diff --git a/server/core/runtime/import_step_runner.go b/server/core/runtime/import_step_runner.go index 0d5787a8ad..7f3a22b9b4 100644 --- a/server/core/runtime/import_step_runner.go +++ b/server/core/runtime/import_step_runner.go @@ -5,25 +5,32 @@ import ( "path/filepath" version "github.com/hashicorp/go-version" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/utils" ) type importStepRunner struct { - terraformExecutor TerraformExec - defaultTFVersion *version.Version + terraformExecutor TerraformExec + defaultTFDistribution terraform.Distribution + defaultTFVersion *version.Version } -func NewImportStepRunner(terraformExecutor TerraformExec, defaultTfVersion *version.Version) Runner { +func NewImportStepRunner(terraformExecutor TerraformExec, defaultTfDistribution terraform.Distribution, defaultTfVersion *version.Version) Runner { runner := &importStepRunner{ - terraformExecutor: terraformExecutor, - defaultTFVersion: defaultTfVersion, + terraformExecutor: terraformExecutor, + defaultTFDistribution: defaultTfDistribution, + defaultTFVersion: defaultTfVersion, } - return NewWorkspaceStepRunnerDelegate(terraformExecutor, defaultTfVersion, runner) + return NewWorkspaceStepRunnerDelegate(terraformExecutor, defaultTfDistribution, defaultTfVersion, runner) } func (p *importStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) { + tfDistribution := p.defaultTFDistribution tfVersion := p.defaultTFVersion + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } if ctx.TerraformVersion != nil { tfVersion = ctx.TerraformVersion } @@ -31,7 +38,7 @@ func (p *importStepRunner) Run(ctx command.ProjectContext, extraArgs []string, p importCmd := []string{"import"} importCmd = append(importCmd, extraArgs...) importCmd = append(importCmd, ctx.EscapedCommentArgs...) - out, err := p.terraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), importCmd, envs, tfVersion, ctx.Workspace) + out, err := p.terraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), importCmd, envs, tfDistribution, tfVersion, ctx.Workspace) // If the import was successful and a plan file exists, delete the plan. planPath := filepath.Join(path, GetPlanFilename(ctx.Workspace, ctx.ProjectName)) diff --git a/server/core/runtime/import_step_runner_test.go b/server/core/runtime/import_step_runner_test.go index b10f182de9..d7cacf9a5f 100644 --- a/server/core/runtime/import_step_runner_test.go +++ b/server/core/runtime/import_step_runner_test.go @@ -8,7 +8,9 @@ import ( "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/logging" . "github.com/runatlantis/atlantis/testing" @@ -29,17 +31,19 @@ func TestImportStepRunner_Run_Success(t *testing.T) { } RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.15.0") - s := NewImportStepRunner(terraform, tfVersion) + s := NewImportStepRunner(terraform, tfDistribution, tfVersion) - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil)) Ok(t, err) Equals(t, "output", output) commands := []string{"import", "-var", "foo=bar", "addr", "id"} - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfVersion, workspace) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfDistribution, tfVersion, workspace) _, err = os.Stat(planPath) Assert(t, os.IsNotExist(err), "planfile should be deleted") } @@ -59,23 +63,66 @@ func TestImportStepRunner_Run_Workspace(t *testing.T) { } RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() tfVersion, _ := version.NewVersion("0.15.0") - s := NewImportStepRunner(terraform, tfVersion) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + s := NewImportStepRunner(terraform, tfDistribution, tfVersion) - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil)) Ok(t, err) Equals(t, "output", output) // switch workspace - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "show"}, map[string]string(nil), tfVersion, workspace) - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "select", workspace}, map[string]string(nil), tfVersion, workspace) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "show"}, map[string]string(nil), tfDistribution, tfVersion, workspace) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "select", workspace}, map[string]string(nil), tfDistribution, tfVersion, workspace) // exec import commands := []string{"import", "-var", "foo=bar", "addr", "id"} - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfVersion, workspace) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfDistribution, tfVersion, workspace) + + _, err = os.Stat(planPath) + Assert(t, os.IsNotExist(err), "planfile should be deleted") +} + +func TestImportStepRunner_Run_UsesConfiguredDistribution(t *testing.T) { + logger := logging.NewNoopLogger(t) + workspace := "something" + tmpDir := t.TempDir() + planPath := filepath.Join(tmpDir, fmt.Sprintf("%s.tfplan", workspace)) + err := os.WriteFile(planPath, nil, 0600) + Ok(t, err) + + projTFDistribution := "opentofu" + context := command.ProjectContext{ + Log: logger, + EscapedCommentArgs: []string{"-var", "foo=bar", "addr", "id"}, + Workspace: workspace, + TerraformDistribution: &projTFDistribution, + } + + RegisterMockTestingT(t) + terraform := tfclientmocks.NewMockClient() + tfVersion, _ := version.NewVersion("0.15.0") + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + s := NewImportStepRunner(terraform, tfDistribution, tfVersion) + + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). + ThenReturn("output", nil) + output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil)) + Ok(t, err) + Equals(t, "output", output) + + // switch workspace + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(context), Eq(tmpDir), Eq([]string{"workspace", "show"}), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq(workspace)) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(context), Eq(tmpDir), Eq([]string{"workspace", "select", workspace}), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq(workspace)) + + // exec import + commands := []string{"import", "-var", "foo=bar", "addr", "id"} + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(context), Eq(tmpDir), Eq(commands), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq(workspace)) _, err = os.Stat(planPath) Assert(t, os.IsNotExist(err), "planfile should be deleted") diff --git a/server/core/runtime/init_step_runner.go b/server/core/runtime/init_step_runner.go index 0c6de1b013..c8da3ffa48 100644 --- a/server/core/runtime/init_step_runner.go +++ b/server/core/runtime/init_step_runner.go @@ -5,14 +5,16 @@ import ( version "github.com/hashicorp/go-version" "github.com/runatlantis/atlantis/server/core/runtime/common" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/utils" ) // InitStep runs `terraform init`. type InitStepRunner struct { - TerraformExecutor TerraformExec - DefaultTFVersion *version.Version + TerraformExecutor TerraformExec + DefaultTFDistribution terraform.Distribution + DefaultTFVersion *version.Version } func (i *InitStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) { @@ -33,6 +35,11 @@ func (i *InitStepRunner) Run(ctx command.ProjectContext, extraArgs []string, pat } } + tfDistribution := i.DefaultTFDistribution + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } + tfVersion := i.DefaultTFVersion if ctx.TerraformVersion != nil { tfVersion = ctx.TerraformVersion @@ -56,7 +63,7 @@ func (i *InitStepRunner) Run(ctx command.ProjectContext, extraArgs []string, pat terraformInitCmd := append(terraformInitVerb, finalArgs...) - out, err := i.TerraformExecutor.RunCommandWithVersion(ctx, path, terraformInitCmd, envs, tfVersion, ctx.Workspace) + out, err := i.TerraformExecutor.RunCommandWithVersion(ctx, path, terraformInitCmd, envs, tfDistribution, tfVersion, ctx.Workspace) // Only include the init output if there was an error. Otherwise it's // unnecessary and lengthens the comment. if err != nil { diff --git a/server/core/runtime/init_step_runner_test.go b/server/core/runtime/init_step_runner_test.go index 45927591a6..86d029c2d8 100644 --- a/server/core/runtime/init_step_runner_test.go +++ b/server/core/runtime/init_step_runner_test.go @@ -12,7 +12,9 @@ import ( "github.com/pkg/errors" "github.com/runatlantis/atlantis/server/core/runtime" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/logging" . "github.com/runatlantis/atlantis/testing" @@ -20,6 +22,8 @@ import ( func TestRun_UsesGetOrInitForRightVersion(t *testing.T) { RegisterMockTestingT(t) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) cases := []struct { version string expCmd string @@ -44,7 +48,7 @@ func TestRun_UsesGetOrInitForRightVersion(t *testing.T) { for _, c := range cases { t.Run(c.version, func(t *testing.T) { - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() logger := logging.NewNoopLogger(t) ctx := command.ProjectContext{ @@ -55,10 +59,11 @@ func TestRun_UsesGetOrInitForRightVersion(t *testing.T) { tfVersion, _ := version.NewVersion(c.version) iso := runtime.InitStepRunner{ - TerraformExecutor: terraform, - DefaultTFVersion: tfVersion, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, } - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := iso.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) @@ -71,7 +76,74 @@ func TestRun_UsesGetOrInitForRightVersion(t *testing.T) { if c.expCmd == "get" { expArgs = []string{c.expCmd, "-upgrade", "extra", "args"} } - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, "/path", expArgs, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, "/path", expArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace") + }) + } +} + +func TestInitStepRunner_TestRun_UsesConfiguredDistribution(t *testing.T) { + RegisterMockTestingT(t) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + cases := []struct { + version string + distribution string + expCmd string + }{ + { + "0.8.9", + "opentofu", + "get", + }, + { + "0.8.9", + "terraform", + "get", + }, + { + "0.9.0", + "opentofu", + "init", + }, + { + "0.9.1", + "terraform", + "init", + }, + } + + for _, c := range cases { + t.Run(c.version, func(t *testing.T) { + terraform := tfclientmocks.NewMockClient() + + logger := logging.NewNoopLogger(t) + ctx := command.ProjectContext{ + Workspace: "workspace", + RepoRelDir: ".", + Log: logger, + TerraformDistribution: &c.distribution, + } + + tfVersion, _ := version.NewVersion(c.version) + iso := runtime.InitStepRunner{ + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, + } + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). + ThenReturn("output", nil) + + output, err := iso.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) + Ok(t, err) + // When there is no error, should not return init output to PR. + Equals(t, "", output) + + // If using init then we specify -input=false but not for get. + expArgs := []string{c.expCmd, "-input=false", "-upgrade", "extra", "args"} + if c.expCmd == "get" { + expArgs = []string{c.expCmd, "-upgrade", "extra", "args"} + } + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(ctx), Eq("/path"), Eq(expArgs), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq("workspace")) }) } } @@ -79,15 +151,17 @@ func TestRun_UsesGetOrInitForRightVersion(t *testing.T) { func TestRun_ShowInitOutputOnError(t *testing.T) { // If there was an error during init then we want the output to be returned. RegisterMockTestingT(t) - tfClient := mocks.NewMockClient() + tfClient := tfclientmocks.NewMockClient() logger := logging.NewNoopLogger(t) - When(tfClient.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(tfClient.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", errors.New("error")) - + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.11.0") iso := runtime.InitStepRunner{ - TerraformExecutor: tfClient, - DefaultTFVersion: tfVersion, + TerraformExecutor: tfClient, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, } output, err := iso.Run(command.ProjectContext{ @@ -118,14 +192,16 @@ func TestRun_InitOmitsUpgradeFlagIfLockFileTracked(t *testing.T) { } RegisterMockTestingT(t) - terraform := mocks.NewMockClient() - + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.14.0") iso := runtime.InitStepRunner{ - TerraformExecutor: terraform, - DefaultTFVersion: tfVersion, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, } - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := iso.Run(ctx, []string{"extra", "args"}, repoDir, map[string]string(nil)) @@ -134,27 +210,29 @@ func TestRun_InitOmitsUpgradeFlagIfLockFileTracked(t *testing.T) { Equals(t, "", output) expectedArgs := []string{"init", "-input=false", "extra", "args"} - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, repoDir, expectedArgs, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, repoDir, expectedArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace") } func TestRun_InitKeepsUpgradeFlagIfLockFileNotPresent(t *testing.T) { tmpDir := t.TempDir() RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() logger := logging.NewNoopLogger(t) ctx := command.ProjectContext{ Workspace: "workspace", RepoRelDir: ".", Log: logger, } - + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.14.0") iso := runtime.InitStepRunner{ - TerraformExecutor: terraform, - DefaultTFVersion: tfVersion, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, } - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := iso.Run(ctx, []string{"extra", "args"}, tmpDir, map[string]string(nil)) @@ -163,7 +241,7 @@ func TestRun_InitKeepsUpgradeFlagIfLockFileNotPresent(t *testing.T) { Equals(t, "", output) expectedArgs := []string{"init", "-input=false", "-upgrade", "extra", "args"} - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, expectedArgs, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, expectedArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace") } func TestRun_InitKeepUpgradeFlagIfLockFilePresentAndTFLessThanPoint14(t *testing.T) { @@ -173,7 +251,7 @@ func TestRun_InitKeepUpgradeFlagIfLockFilePresentAndTFLessThanPoint14(t *testing Ok(t, err) RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() logger := logging.NewNoopLogger(t) ctx := command.ProjectContext{ @@ -181,13 +259,15 @@ func TestRun_InitKeepUpgradeFlagIfLockFilePresentAndTFLessThanPoint14(t *testing RepoRelDir: ".", Log: logger, } - + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.13.0") iso := runtime.InitStepRunner{ - TerraformExecutor: terraform, - DefaultTFVersion: tfVersion, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, } - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := iso.Run(ctx, []string{"extra", "args"}, tmpDir, map[string]string(nil)) @@ -196,7 +276,7 @@ func TestRun_InitKeepUpgradeFlagIfLockFilePresentAndTFLessThanPoint14(t *testing Equals(t, "", output) expectedArgs := []string{"init", "-input=false", "-upgrade", "extra", "args"} - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, expectedArgs, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, expectedArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace") } func TestRun_InitExtraArgsDeDupe(t *testing.T) { @@ -240,7 +320,7 @@ func TestRun_InitExtraArgsDeDupe(t *testing.T) { for _, c := range cases { t.Run(c.description, func(t *testing.T) { - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() logger := logging.NewNoopLogger(t) ctx := command.ProjectContext{ @@ -248,13 +328,15 @@ func TestRun_InitExtraArgsDeDupe(t *testing.T) { RepoRelDir: ".", Log: logger, } - + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.10.0") iso := runtime.InitStepRunner{ - TerraformExecutor: terraform, - DefaultTFVersion: tfVersion, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, } - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := iso.Run(ctx, c.extraArgs, "/path", map[string]string(nil)) @@ -262,7 +344,7 @@ func TestRun_InitExtraArgsDeDupe(t *testing.T) { // When there is no error, should not return init output to PR. Equals(t, "", output) - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, "/path", c.expectedArgs, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, "/path", c.expectedArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace") }) } } @@ -276,17 +358,19 @@ func TestRun_InitDeletesLockFileIfPresentAndNotTracked(t *testing.T) { Ok(t, err) RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() logger := logging.NewNoopLogger(t) - + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.14.0") iso := runtime.InitStepRunner{ - TerraformExecutor: terraform, - DefaultTFVersion: tfVersion, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, } - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) ctx := command.ProjectContext{ @@ -300,7 +384,7 @@ func TestRun_InitDeletesLockFileIfPresentAndNotTracked(t *testing.T) { Equals(t, "", output) expectedArgs := []string{"init", "-input=false", "-upgrade", "extra", "args"} - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, repoDir, expectedArgs, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, repoDir, expectedArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace") } func runCmd(t *testing.T, dir string, name string, args ...string) string { diff --git a/server/core/runtime/mocks/mock_async_tfexec.go b/server/core/runtime/mocks/mock_async_tfexec.go index 662571ed0b..453c80012d 100644 --- a/server/core/runtime/mocks/mock_async_tfexec.go +++ b/server/core/runtime/mocks/mock_async_tfexec.go @@ -7,6 +7,7 @@ import ( go_version "github.com/hashicorp/go-version" pegomock "github.com/petergtz/pegomock/v4" models "github.com/runatlantis/atlantis/server/core/runtime/models" + terraform "github.com/runatlantis/atlantis/server/core/terraform" command "github.com/runatlantis/atlantis/server/events/command" "reflect" "time" @@ -27,11 +28,11 @@ func NewMockAsyncTFExec(options ...pegomock.Option) *MockAsyncTFExec { func (mock *MockAsyncTFExec) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh } func (mock *MockAsyncTFExec) FailHandler() pegomock.FailHandler { return mock.fail } -func (mock *MockAsyncTFExec) RunCommandAsync(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *go_version.Version, workspace string) (chan<- string, <-chan models.Line) { +func (mock *MockAsyncTFExec) RunCommandAsync(ctx command.ProjectContext, path string, args []string, envs map[string]string, d terraform.Distribution, v *go_version.Version, workspace string) (chan<- string, <-chan models.Line) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockAsyncTFExec().") } - _params := []pegomock.Param{ctx, path, args, envs, v, workspace} + _params := []pegomock.Param{ctx, path, args, envs, d, v, workspace} _result := pegomock.GetGenericMockFrom(mock).Invoke("RunCommandAsync", _params, []reflect.Type{reflect.TypeOf((*chan<- string)(nil)).Elem(), reflect.TypeOf((*<-chan models.Line)(nil)).Elem()}) var _ret0 chan<- string var _ret1 <-chan models.Line @@ -91,8 +92,8 @@ type VerifierMockAsyncTFExec struct { timeout time.Duration } -func (verifier *VerifierMockAsyncTFExec) RunCommandAsync(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *go_version.Version, workspace string) *MockAsyncTFExec_RunCommandAsync_OngoingVerification { - _params := []pegomock.Param{ctx, path, args, envs, v, workspace} +func (verifier *VerifierMockAsyncTFExec) RunCommandAsync(ctx command.ProjectContext, path string, args []string, envs map[string]string, d terraform.Distribution, v *go_version.Version, workspace string) *MockAsyncTFExec_RunCommandAsync_OngoingVerification { + _params := []pegomock.Param{ctx, path, args, envs, d, v, workspace} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RunCommandAsync", _params, verifier.timeout) return &MockAsyncTFExec_RunCommandAsync_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -102,12 +103,12 @@ type MockAsyncTFExec_RunCommandAsync_OngoingVerification struct { methodInvocations []pegomock.MethodInvocation } -func (c *MockAsyncTFExec_RunCommandAsync_OngoingVerification) GetCapturedArguments() (command.ProjectContext, string, []string, map[string]string, *go_version.Version, string) { - ctx, path, args, envs, v, workspace := c.GetAllCapturedArguments() - return ctx[len(ctx)-1], path[len(path)-1], args[len(args)-1], envs[len(envs)-1], v[len(v)-1], workspace[len(workspace)-1] +func (c *MockAsyncTFExec_RunCommandAsync_OngoingVerification) GetCapturedArguments() (command.ProjectContext, string, []string, map[string]string, terraform.Distribution, *go_version.Version, string) { + ctx, path, args, envs, d, v, workspace := c.GetAllCapturedArguments() + return ctx[len(ctx)-1], path[len(path)-1], args[len(args)-1], envs[len(envs)-1], d[len(d)-1], v[len(v)-1], workspace[len(workspace)-1] } -func (c *MockAsyncTFExec_RunCommandAsync_OngoingVerification) GetAllCapturedArguments() (_param0 []command.ProjectContext, _param1 []string, _param2 [][]string, _param3 []map[string]string, _param4 []*go_version.Version, _param5 []string) { +func (c *MockAsyncTFExec_RunCommandAsync_OngoingVerification) GetAllCapturedArguments() (_param0 []command.ProjectContext, _param1 []string, _param2 [][]string, _param3 []map[string]string, _param4 []terraform.Distribution, _param5 []*go_version.Version, _param6 []string) { _params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(_params) > 0 { if len(_params) > 0 { @@ -135,15 +136,21 @@ func (c *MockAsyncTFExec_RunCommandAsync_OngoingVerification) GetAllCapturedArgu } } if len(_params) > 4 { - _param4 = make([]*go_version.Version, len(c.methodInvocations)) + _param4 = make([]terraform.Distribution, len(c.methodInvocations)) for u, param := range _params[4] { - _param4[u] = param.(*go_version.Version) + _param4[u] = param.(terraform.Distribution) } } if len(_params) > 5 { - _param5 = make([]string, len(c.methodInvocations)) + _param5 = make([]*go_version.Version, len(c.methodInvocations)) for u, param := range _params[5] { - _param5[u] = param.(string) + _param5[u] = param.(*go_version.Version) + } + } + if len(_params) > 6 { + _param6 = make([]string, len(c.methodInvocations)) + for u, param := range _params[6] { + _param6[u] = param.(string) } } } diff --git a/server/core/runtime/models/shell_command_runner.go b/server/core/runtime/models/shell_command_runner.go index 50b9f7760f..cd613bf450 100644 --- a/server/core/runtime/models/shell_command_runner.go +++ b/server/core/runtime/models/shell_command_runner.go @@ -10,8 +10,8 @@ import ( "github.com/pkg/errors" "github.com/runatlantis/atlantis/server/core/config/valid" + "github.com/runatlantis/atlantis/server/core/terraform/ansi" "github.com/runatlantis/atlantis/server/events/command" - "github.com/runatlantis/atlantis/server/events/terraform/ansi" "github.com/runatlantis/atlantis/server/jobs" ) diff --git a/server/core/runtime/multienv_step_runner_test.go b/server/core/runtime/multienv_step_runner_test.go index 360adce3f5..326307fdea 100644 --- a/server/core/runtime/multienv_step_runner_test.go +++ b/server/core/runtime/multienv_step_runner_test.go @@ -7,7 +7,9 @@ import ( . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/config/valid" "github.com/runatlantis/atlantis/server/core/runtime" - "github.com/runatlantis/atlantis/server/core/terraform/mocks" + "github.com/runatlantis/atlantis/server/core/terraform" + terraformmocks "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks" @@ -45,12 +47,15 @@ func TestMultiEnvStepRunner_Run(t *testing.T) { }, } RegisterMockTestingT(t) - tfClient := mocks.NewMockClient() + tfClient := tfclientmocks.NewMockClient() + mockDownloader := terraformmocks.NewMockDownloader() + tfDistribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, err := version.NewVersion("0.12.0") Ok(t, err) projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler() runStepRunner := runtime.RunStepRunner{ TerraformExecutor: tfClient, + DefaultTFDistribution: tfDistribution, DefaultTFVersion: tfVersion, ProjectCmdOutputHandler: projectCmdOutputHandler, } diff --git a/server/core/runtime/plan_step_runner.go b/server/core/runtime/plan_step_runner.go index 7d99dc26bf..b3fc491351 100644 --- a/server/core/runtime/plan_step_runner.go +++ b/server/core/runtime/plan_step_runner.go @@ -9,6 +9,7 @@ import ( version "github.com/hashicorp/go-version" "github.com/pkg/errors" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" ) @@ -26,34 +27,40 @@ var ( ) type planStepRunner struct { - TerraformExecutor TerraformExec - DefaultTFVersion *version.Version - CommitStatusUpdater StatusUpdater - AsyncTFExec AsyncTFExec + TerraformExecutor TerraformExec + DefaultTFDistribution terraform.Distribution + DefaultTFVersion *version.Version + CommitStatusUpdater StatusUpdater + AsyncTFExec AsyncTFExec } -func NewPlanStepRunner(terraformExecutor TerraformExec, defaultTfVersion *version.Version, commitStatusUpdater StatusUpdater, asyncTFExec AsyncTFExec) Runner { +func NewPlanStepRunner(terraformExecutor TerraformExec, defaultTfDistribution terraform.Distribution, defaultTfVersion *version.Version, commitStatusUpdater StatusUpdater, asyncTFExec AsyncTFExec) Runner { runner := &planStepRunner{ - TerraformExecutor: terraformExecutor, - DefaultTFVersion: defaultTfVersion, - CommitStatusUpdater: commitStatusUpdater, - AsyncTFExec: asyncTFExec, + TerraformExecutor: terraformExecutor, + DefaultTFDistribution: defaultTfDistribution, + DefaultTFVersion: defaultTfVersion, + CommitStatusUpdater: commitStatusUpdater, + AsyncTFExec: asyncTFExec, } - return NewWorkspaceStepRunnerDelegate(terraformExecutor, defaultTfVersion, runner) + return NewWorkspaceStepRunnerDelegate(terraformExecutor, defaultTfDistribution, defaultTfVersion, runner) } func (p *planStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) { + tfDistribution := p.DefaultTFDistribution tfVersion := p.DefaultTFVersion + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } if ctx.TerraformVersion != nil { tfVersion = ctx.TerraformVersion } planFile := filepath.Join(path, GetPlanFilename(ctx.Workspace, ctx.ProjectName)) planCmd := p.buildPlanCmd(ctx, extraArgs, path, tfVersion, planFile) - output, err := p.TerraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), planCmd, envs, tfVersion, ctx.Workspace) + output, err := p.TerraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), planCmd, envs, tfDistribution, tfVersion, ctx.Workspace) if p.isRemoteOpsErr(output, err) { ctx.Log.Debug("detected that this project is using TFE remote ops") - return p.remotePlan(ctx, extraArgs, path, tfVersion, planFile, envs) + return p.remotePlan(ctx, extraArgs, path, tfDistribution, tfVersion, planFile, envs) } if err != nil { return output, err @@ -72,14 +79,14 @@ func (p *planStepRunner) isRemoteOpsErr(output string, err error) bool { // remotePlan runs a terraform plan command compatible with TFE remote // operations. -func (p *planStepRunner) remotePlan(ctx command.ProjectContext, extraArgs []string, path string, tfVersion *version.Version, planFile string, envs map[string]string) (string, error) { +func (p *planStepRunner) remotePlan(ctx command.ProjectContext, extraArgs []string, path string, tfDistribution terraform.Distribution, tfVersion *version.Version, planFile string, envs map[string]string) (string, error) { argList := [][]string{ {"plan", "-input=false", "-refresh", "-no-color"}, extraArgs, ctx.EscapedCommentArgs, } args := p.flatten(argList) - output, err := p.runRemotePlan(ctx, args, path, tfVersion, envs) + output, err := p.runRemotePlan(ctx, args, path, tfDistribution, tfVersion, envs) if err != nil { return output, err } @@ -193,6 +200,7 @@ func (p *planStepRunner) runRemotePlan( ctx command.ProjectContext, cmdArgs []string, path string, + tfDistribution terraform.Distribution, tfVersion *version.Version, envs map[string]string) (string, error) { @@ -205,7 +213,7 @@ func (p *planStepRunner) runRemotePlan( // Start the async command execution. ctx.Log.Debug("starting async tf remote operation") - _, outCh := p.AsyncTFExec.RunCommandAsync(ctx, filepath.Clean(path), cmdArgs, envs, tfVersion, ctx.Workspace) + _, outCh := p.AsyncTFExec.RunCommandAsync(ctx, filepath.Clean(path), cmdArgs, envs, tfDistribution, tfVersion, ctx.Workspace) var lines []string nextLineIsRunURL := false var runURL string diff --git a/server/core/runtime/plan_step_runner_test.go b/server/core/runtime/plan_step_runner_test.go index f05336637c..6a16b03e3f 100644 --- a/server/core/runtime/plan_step_runner_test.go +++ b/server/core/runtime/plan_step_runner_test.go @@ -13,7 +13,9 @@ import ( "github.com/runatlantis/atlantis/server/core/runtime" runtimemocks "github.com/runatlantis/atlantis/server/core/runtime/mocks" runtimemodels "github.com/runatlantis/atlantis/server/core/runtime/models" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/logging" @@ -24,7 +26,7 @@ import ( func TestRun_AddsEnvVarFile(t *testing.T) { // Test that if env/workspace.tfvars file exists we use -var-file option. RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() commitStatusUpdater := runtimemocks.NewMockStatusUpdater() asyncTfExec := runtimemocks.NewMockAsyncTFExec() @@ -36,10 +38,12 @@ func TestRun_AddsEnvVarFile(t *testing.T) { err = os.WriteFile(envVarsFile, nil, 0600) Ok(t, err) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) // Using version >= 0.10 here so we don't expect any env commands. tfVersion, _ := version.NewVersion("0.10.0") logger := logging.NewNoopLogger(t) - s := runtime.NewPlanStepRunner(terraform, tfVersion, commitStatusUpdater, asyncTfExec) + s := runtime.NewPlanStepRunner(terraform, tfDistribution, tfVersion, commitStatusUpdater, asyncTfExec) expPlanArgs := []string{"plan", "-input=false", @@ -78,14 +82,14 @@ func TestRun_AddsEnvVarFile(t *testing.T) { Name: "repo", }, } - When(terraform.RunCommandWithVersion(ctx, tmpDir, expPlanArgs, map[string]string(nil), tfVersion, "workspace")).ThenReturn("output", nil) + When(terraform.RunCommandWithVersion(ctx, tmpDir, expPlanArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace")).ThenReturn("output", nil) output, err := s.Run(ctx, []string{"extra", "args"}, tmpDir, map[string]string(nil)) Ok(t, err) // Verify that env select was never called since we're in version >= 0.10 - terraform.VerifyWasCalled(Never()).RunCommandWithVersion(ctx, tmpDir, []string{"env", "select", "workspace"}, map[string]string(nil), tfVersion, "workspace") - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, expPlanArgs, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalled(Never()).RunCommandWithVersion(ctx, tmpDir, []string{"env", "select", "workspace"}, map[string]string(nil), tfDistribution, tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, tmpDir, expPlanArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace") Equals(t, "output", output) } @@ -93,12 +97,14 @@ func TestRun_UsesDiffPathForProject(t *testing.T) { // Test that if running for a project, uses a different path for the plan // file. RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() commitStatusUpdater := runtimemocks.NewMockStatusUpdater() asyncTfExec := runtimemocks.NewMockAsyncTFExec() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.10.0") logger := logging.NewNoopLogger(t) - s := runtime.NewPlanStepRunner(terraform, tfVersion, commitStatusUpdater, asyncTfExec) + s := runtime.NewPlanStepRunner(terraform, tfDistribution, tfVersion, commitStatusUpdater, asyncTfExec) ctx := command.ProjectContext{ Log: logger, Workspace: "default", @@ -115,7 +121,7 @@ func TestRun_UsesDiffPathForProject(t *testing.T) { Name: "repo", }, } - When(terraform.RunCommandWithVersion(ctx, "/path", []string{"workspace", "show"}, map[string]string(nil), tfVersion, "workspace")).ThenReturn("workspace\n", nil) + When(terraform.RunCommandWithVersion(ctx, "/path", []string{"workspace", "show"}, map[string]string(nil), tfDistribution, tfVersion, "workspace")).ThenReturn("workspace\n", nil) expPlanArgs := []string{"plan", "-input=false", @@ -137,7 +143,7 @@ func TestRun_UsesDiffPathForProject(t *testing.T) { "comment", "args", } - When(terraform.RunCommandWithVersion(ctx, "/path", expPlanArgs, map[string]string(nil), tfVersion, "default")).ThenReturn("output", nil) + When(terraform.RunCommandWithVersion(ctx, "/path", expPlanArgs, map[string]string(nil), tfDistribution, tfVersion, "default")).ThenReturn("output", nil) output, err := s.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) Ok(t, err) @@ -173,16 +179,19 @@ Terraform will perform the following actions: - aws_security_group_rule.allow_all ` RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() commitStatusUpdater := runtimemocks.NewMockStatusUpdater() asyncTfExec := runtimemocks.NewMockAsyncTFExec() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.10.0") - s := runtime.NewPlanStepRunner(terraform, tfVersion, commitStatusUpdater, asyncTfExec) + s := runtime.NewPlanStepRunner(terraform, tfDistribution, tfVersion, commitStatusUpdater, asyncTfExec) When(terraform.RunCommandWithVersion( Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), + Any[tf.Distribution](), Any[*version.Version](), Any[string]())). Then(func(params []Param) ReturnValues { @@ -223,11 +232,13 @@ Terraform will perform the following actions: // Test that even if there's an error, we get the returned output. func TestRun_OutputOnErr(t *testing.T) { RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() commitStatusUpdater := runtimemocks.NewMockStatusUpdater() asyncTfExec := runtimemocks.NewMockAsyncTFExec() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.10.0") - s := runtime.NewPlanStepRunner(terraform, tfVersion, commitStatusUpdater, asyncTfExec) + s := runtime.NewPlanStepRunner(terraform, tfDistribution, tfVersion, commitStatusUpdater, asyncTfExec) expOutput := "expected output" expErrMsg := "error!" When(terraform.RunCommandWithVersion( @@ -235,6 +246,7 @@ func TestRun_OutputOnErr(t *testing.T) { Any[string](), Any[[]string](), Any[map[string]string](), + Any[tf.Distribution](), Any[*version.Version](), Any[string]())). Then(func(params []Param) ReturnValues { @@ -287,7 +299,7 @@ func TestRun_NoOptionalVarsIn012(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() commitStatusUpdater := runtimemocks.NewMockStatusUpdater() asyncTfExec := runtimemocks.NewMockAsyncTFExec() When(terraform.RunCommandWithVersion( @@ -295,11 +307,14 @@ func TestRun_NoOptionalVarsIn012(t *testing.T) { Any[string](), Any[[]string](), Any[map[string]string](), + Any[tf.Distribution](), Any[*version.Version](), Any[string]())).ThenReturn("output", nil) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion(c.tfVersion) - s := runtime.NewPlanStepRunner(terraform, tfVersion, commitStatusUpdater, asyncTfExec) + s := runtime.NewPlanStepRunner(terraform, tfDistribution, tfVersion, commitStatusUpdater, asyncTfExec) ctx := command.ProjectContext{ Workspace: "default", RepoRelDir: ".", @@ -319,7 +334,7 @@ func TestRun_NoOptionalVarsIn012(t *testing.T) { Ok(t, err) Equals(t, "output", output) - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, "/path", expPlanArgs, map[string]string(nil), tfVersion, "default") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, "/path", expPlanArgs, map[string]string(nil), tfDistribution, tfVersion, "default") }) } @@ -385,11 +400,13 @@ locally at this time. }, } RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() commitStatusUpdater := runtimemocks.NewMockStatusUpdater() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion(c.tfVersion) asyncTf := &remotePlanMock{} - s := runtime.NewPlanStepRunner(terraform, tfVersion, commitStatusUpdater, asyncTf) + s := runtime.NewPlanStepRunner(terraform, tfDistribution, tfVersion, commitStatusUpdater, asyncTf) absProjectPath := t.TempDir() // First, terraform workspace gets run. @@ -398,6 +415,7 @@ locally at this time. absProjectPath, []string{"workspace", "show"}, map[string]string(nil), + tfDistribution, tfVersion, "default")).ThenReturn("default\n", nil) @@ -438,7 +456,7 @@ locally at this time. planErr := errors.New("exit status 1: err") planOutput := "\n" + c.remoteOpsErr asyncTf.LinesToSend = remotePlanOutput - When(terraform.RunCommandWithVersion(ctx, absProjectPath, expPlanArgs, map[string]string(nil), tfVersion, "default")). + When(terraform.RunCommandWithVersion(ctx, absProjectPath, expPlanArgs, map[string]string(nil), tfDistribution, tfVersion, "default")). ThenReturn(planOutput, planErr) output, err := s.Run(ctx, []string{"extra", "args"}, absProjectPath, map[string]string(nil)) @@ -536,6 +554,82 @@ Plan: 0 to add, 0 to change, 1 to destroy.`, output) } } +func TestPlanStepRunner_TestRun_UsesConfiguredDistribution(t *testing.T) { + RegisterMockTestingT(t) + + expPlanArgs := []string{ + "plan", + "-input=false", + "-refresh", + "-out", + fmt.Sprintf("%q", "/path/default.tfplan"), + "extra", + "args", + "comment", + "args", + } + + cases := []struct { + name string + tfVersion string + tfDistribution string + }{ + { + "stable version", + "0.12.0", + "terraform", + }, + { + "with prerelease", + "0.14.0-rc1", + "opentofu", + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + terraform := tfclientmocks.NewMockClient() + commitStatusUpdater := runtimemocks.NewMockStatusUpdater() + asyncTfExec := runtimemocks.NewMockAsyncTFExec() + When(terraform.RunCommandWithVersion( + Any[command.ProjectContext](), + Any[string](), + Any[[]string](), + Any[map[string]string](), + Any[tf.Distribution](), + Any[*version.Version](), + Any[string]())).ThenReturn("output", nil) + + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + tfVersion, _ := version.NewVersion(c.tfVersion) + s := runtime.NewPlanStepRunner(terraform, tfDistribution, tfVersion, commitStatusUpdater, asyncTfExec) + ctx := command.ProjectContext{ + Workspace: "default", + RepoRelDir: ".", + User: models.User{Username: "username"}, + EscapedCommentArgs: []string{"comment", "args"}, + Pull: models.PullRequest{ + Num: 2, + }, + BaseRepo: models.Repo{ + FullName: "owner/repo", + Owner: "owner", + Name: "repo", + }, + TerraformDistribution: &c.tfDistribution, + } + + output, err := s.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) + Ok(t, err) + Equals(t, "output", output) + + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(ctx), Eq("/path"), Eq(expPlanArgs), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq("default")) + }) + } + +} + type remotePlanMock struct { // LinesToSend will be sent on the channel. LinesToSend string @@ -543,7 +637,7 @@ type remotePlanMock struct { CalledArgs []string } -func (r *remotePlanMock) RunCommandAsync(_ command.ProjectContext, _ string, args []string, _ map[string]string, _ *version.Version, _ string) (chan<- string, <-chan runtimemodels.Line) { +func (r *remotePlanMock) RunCommandAsync(_ command.ProjectContext, _ string, args []string, _ map[string]string, _ tf.Distribution, _ *version.Version, _ string) (chan<- string, <-chan runtimemodels.Line) { r.CalledArgs = args in := make(chan string) out := make(chan runtimemodels.Line) diff --git a/server/core/runtime/plan_type_step_runner_delegate_test.go b/server/core/runtime/plan_type_step_runner_delegate_test.go index 286ae9ad40..db4be0ff03 100644 --- a/server/core/runtime/plan_type_step_runner_delegate_test.go +++ b/server/core/runtime/plan_type_step_runner_delegate_test.go @@ -153,3 +153,148 @@ func TestRunDelegate(t *testing.T) { }) } + +var openTofuPlanFileContents = ` +An execution plan has been generated and is shown below. +Resource actions are indicated with the following symbols: + - destroy + +OpenTofu will perform the following actions: + + - null_resource.hi[1] + + +Plan: 0 to add, 0 to change, 1 to destroy.` + +func TestRunDelegate_UsesConfiguredDistribution(t *testing.T) { + + RegisterMockTestingT(t) + + mockDefaultRunner := mocks.NewMockRunner() + mockRemoteRunner := mocks.NewMockRunner() + + subject := &planTypeStepRunnerDelegate{ + defaultRunner: mockDefaultRunner, + remotePlanRunner: mockRemoteRunner, + } + + tfDistribution := "opentofu" + tfVersion, _ := version.NewVersion("1.7.0") + + t.Run("Remote Runner Success", func(t *testing.T) { + tmpDir := t.TempDir() + planPath := filepath.Join(tmpDir, "workspace.tfplan") + err := os.WriteFile(planPath, []byte("Atlantis: this plan was created by remote ops\n"+openTofuPlanFileContents), 0600) + Ok(t, err) + + ctx := command.ProjectContext{ + Workspace: "workspace", + RepoRelDir: ".", + EscapedCommentArgs: []string{"comment", "args"}, + TerraformDistribution: &tfDistribution, + TerraformVersion: tfVersion, + } + extraArgs := []string{"extra", "args"} + envs := map[string]string{} + + expectedOut := "some random output" + + When(mockRemoteRunner.Run(ctx, extraArgs, tmpDir, envs)).ThenReturn(expectedOut, nil) + + output, err := subject.Run(ctx, extraArgs, tmpDir, envs) + + mockDefaultRunner.VerifyWasCalled(Never()) + + Equals(t, expectedOut, output) + Ok(t, err) + + }) + + t.Run("Remote Runner Failure", func(t *testing.T) { + tmpDir := t.TempDir() + planPath := filepath.Join(tmpDir, "workspace.tfplan") + err := os.WriteFile(planPath, []byte("Atlantis: this plan was created by remote ops\n"+openTofuPlanFileContents), 0600) + Ok(t, err) + + ctx := command.ProjectContext{ + Workspace: "workspace", + RepoRelDir: ".", + EscapedCommentArgs: []string{"comment", "args"}, + TerraformDistribution: &tfDistribution, + TerraformVersion: tfVersion, + } + extraArgs := []string{"extra", "args"} + envs := map[string]string{} + + expectedOut := "some random output" + + When(mockRemoteRunner.Run(ctx, extraArgs, tmpDir, envs)).ThenReturn(expectedOut, errors.New("err")) + + output, err := subject.Run(ctx, extraArgs, tmpDir, envs) + + mockDefaultRunner.VerifyWasCalled(Never()) + + Equals(t, expectedOut, output) + Assert(t, err != nil, "err should not be nil") + + }) + + t.Run("Local Runner Success", func(t *testing.T) { + tmpDir := t.TempDir() + planPath := filepath.Join(tmpDir, "workspace.tfplan") + err := os.WriteFile(planPath, []byte(openTofuPlanFileContents), 0600) + Ok(t, err) + + ctx := command.ProjectContext{ + Workspace: "workspace", + RepoRelDir: ".", + EscapedCommentArgs: []string{"comment", "args"}, + TerraformDistribution: &tfDistribution, + TerraformVersion: tfVersion, + } + extraArgs := []string{"extra", "args"} + envs := map[string]string{} + + expectedOut := "some random output" + + When(mockDefaultRunner.Run(ctx, extraArgs, tmpDir, envs)).ThenReturn(expectedOut, nil) + + output, err := subject.Run(ctx, extraArgs, tmpDir, envs) + + mockRemoteRunner.VerifyWasCalled(Never()) + + Equals(t, expectedOut, output) + Ok(t, err) + + }) + + t.Run("Local Runner Failure", func(t *testing.T) { + tmpDir := t.TempDir() + planPath := filepath.Join(tmpDir, "workspace.tfplan") + err := os.WriteFile(planPath, []byte(openTofuPlanFileContents), 0600) + Ok(t, err) + + ctx := command.ProjectContext{ + Workspace: "workspace", + RepoRelDir: ".", + EscapedCommentArgs: []string{"comment", "args"}, + TerraformDistribution: &tfDistribution, + TerraformVersion: tfVersion, + } + extraArgs := []string{"extra", "args"} + envs := map[string]string{} + + expectedOut := "some random output" + + When(mockDefaultRunner.Run(ctx, extraArgs, tmpDir, envs)).ThenReturn(expectedOut, errors.New("err")) + + output, err := subject.Run(ctx, extraArgs, tmpDir, envs) + + mockRemoteRunner.VerifyWasCalled(Never()) + + Equals(t, expectedOut, output) + Assert(t, err != nil, "err should not be nil") + + }) + +} diff --git a/server/core/runtime/policy_check_step_runner.go b/server/core/runtime/policy_check_step_runner.go index 98e4408bcb..2987875f18 100644 --- a/server/core/runtime/policy_check_step_runner.go +++ b/server/core/runtime/policy_check_step_runner.go @@ -3,6 +3,7 @@ package runtime import ( "github.com/hashicorp/go-version" "github.com/pkg/errors" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" ) @@ -13,7 +14,7 @@ type policyCheckStepRunner struct { } // NewPolicyCheckStepRunner creates a new step runner from an executor workflow -func NewPolicyCheckStepRunner(defaultTfVersion *version.Version, executorWorkflow VersionedExecutorWorkflow) (Runner, error) { +func NewPolicyCheckStepRunner(defaultTfDistribution terraform.Distribution, defaultTfVersion *version.Version, executorWorkflow VersionedExecutorWorkflow) (Runner, error) { policyCheckStepRunner := &policyCheckStepRunner{ versionEnsurer: executorWorkflow, executor: executorWorkflow, diff --git a/server/core/runtime/post_workflow_hook_runner_test.go b/server/core/runtime/post_workflow_hook_runner_test.go index 8bab373502..3a7d9499d0 100644 --- a/server/core/runtime/post_workflow_hook_runner_test.go +++ b/server/core/runtime/post_workflow_hook_runner_test.go @@ -8,7 +8,8 @@ import ( "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/runtime" - "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tf "github.com/runatlantis/atlantis/server/core/terraform" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/models" jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks" "github.com/runatlantis/atlantis/server/logging" @@ -142,8 +143,8 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) { Ok(t, err) RegisterMockTestingT(t) - terraform := mocks.NewMockClient() - When(terraform.EnsureVersion(Any[logging.SimpleLogging](), Any[*version.Version]())). + terraform := tfclientmocks.NewMockClient() + When(terraform.EnsureVersion(Any[logging.SimpleLogging](), Any[tf.Distribution](), Any[*version.Version]())). ThenReturn(nil) logger := logging.NewNoopLogger(t) diff --git a/server/core/runtime/pre_workflow_hook_runner_test.go b/server/core/runtime/pre_workflow_hook_runner_test.go index 40133c10a5..b621fa3e07 100644 --- a/server/core/runtime/pre_workflow_hook_runner_test.go +++ b/server/core/runtime/pre_workflow_hook_runner_test.go @@ -9,7 +9,8 @@ import ( "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/runtime" - "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tf "github.com/runatlantis/atlantis/server/core/terraform" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/models" jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks" "github.com/runatlantis/atlantis/server/logging" @@ -162,8 +163,8 @@ func TestPreWorkflowHookRunner_Run(t *testing.T) { Ok(t, err) RegisterMockTestingT(t) - terraform := mocks.NewMockClient() - When(terraform.EnsureVersion(Any[logging.SimpleLogging](), Any[*version.Version]())). + terraform := tfclientmocks.NewMockClient() + When(terraform.EnsureVersion(Any[logging.SimpleLogging](), Any[tf.Distribution](), Any[*version.Version]())). ThenReturn(nil) logger := logging.NewNoopLogger(t) diff --git a/server/core/runtime/run_step_runner.go b/server/core/runtime/run_step_runner.go index 76629ba460..20d55caee6 100644 --- a/server/core/runtime/run_step_runner.go +++ b/server/core/runtime/run_step_runner.go @@ -9,14 +9,16 @@ import ( "github.com/hashicorp/go-version" "github.com/runatlantis/atlantis/server/core/config/valid" "github.com/runatlantis/atlantis/server/core/runtime/models" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/jobs" ) // RunStepRunner runs custom commands. type RunStepRunner struct { - TerraformExecutor TerraformExec - DefaultTFVersion *version.Version + TerraformExecutor TerraformExec + DefaultTFDistribution terraform.Distribution + DefaultTFVersion *version.Version // TerraformBinDir is the directory where Atlantis downloads Terraform binaries. TerraformBinDir string ProjectCmdOutputHandler jobs.ProjectCommandOutputHandler @@ -31,12 +33,16 @@ func (r *RunStepRunner) Run( streamOutput bool, postProcessOutput valid.PostProcessRunOutputOption, ) (string, error) { + tfDistribution := r.DefaultTFDistribution tfVersion := r.DefaultTFVersion + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } if ctx.TerraformVersion != nil { tfVersion = ctx.TerraformVersion } - err := r.TerraformExecutor.EnsureVersion(ctx.Log, tfVersion) + err := r.TerraformExecutor.EnsureVersion(ctx.Log, tfDistribution, tfVersion) if err != nil { err = fmt.Errorf("%s: Downloading terraform Version %s", err, tfVersion.String()) ctx.Log.Debug("error: %s", err) @@ -45,27 +51,28 @@ func (r *RunStepRunner) Run( baseEnvVars := os.Environ() customEnvVars := map[string]string{ - "ATLANTIS_TERRAFORM_VERSION": tfVersion.String(), - "BASE_BRANCH_NAME": ctx.Pull.BaseBranch, - "BASE_REPO_NAME": ctx.BaseRepo.Name, - "BASE_REPO_OWNER": ctx.BaseRepo.Owner, - "COMMENT_ARGS": strings.Join(ctx.EscapedCommentArgs, ","), - "DIR": path, - "HEAD_BRANCH_NAME": ctx.Pull.HeadBranch, - "HEAD_COMMIT": ctx.Pull.HeadCommit, - "HEAD_REPO_NAME": ctx.HeadRepo.Name, - "HEAD_REPO_OWNER": ctx.HeadRepo.Owner, - "PATH": fmt.Sprintf("%s:%s", os.Getenv("PATH"), r.TerraformBinDir), - "PLANFILE": filepath.Join(path, GetPlanFilename(ctx.Workspace, ctx.ProjectName)), - "SHOWFILE": filepath.Join(path, ctx.GetShowResultFileName()), - "POLICYCHECKFILE": filepath.Join(path, ctx.GetPolicyCheckResultFileName()), - "PROJECT_NAME": ctx.ProjectName, - "PULL_AUTHOR": ctx.Pull.Author, - "PULL_NUM": fmt.Sprintf("%d", ctx.Pull.Num), - "PULL_URL": ctx.Pull.URL, - "REPO_REL_DIR": ctx.RepoRelDir, - "USER_NAME": ctx.User.Username, - "WORKSPACE": ctx.Workspace, + "ATLANTIS_TERRAFORM_DISTRIBUTION": tfDistribution.BinName(), + "ATLANTIS_TERRAFORM_VERSION": tfVersion.String(), + "BASE_BRANCH_NAME": ctx.Pull.BaseBranch, + "BASE_REPO_NAME": ctx.BaseRepo.Name, + "BASE_REPO_OWNER": ctx.BaseRepo.Owner, + "COMMENT_ARGS": strings.Join(ctx.EscapedCommentArgs, ","), + "DIR": path, + "HEAD_BRANCH_NAME": ctx.Pull.HeadBranch, + "HEAD_COMMIT": ctx.Pull.HeadCommit, + "HEAD_REPO_NAME": ctx.HeadRepo.Name, + "HEAD_REPO_OWNER": ctx.HeadRepo.Owner, + "PATH": fmt.Sprintf("%s:%s", os.Getenv("PATH"), r.TerraformBinDir), + "PLANFILE": filepath.Join(path, GetPlanFilename(ctx.Workspace, ctx.ProjectName)), + "SHOWFILE": filepath.Join(path, ctx.GetShowResultFileName()), + "POLICYCHECKFILE": filepath.Join(path, ctx.GetPolicyCheckResultFileName()), + "PROJECT_NAME": ctx.ProjectName, + "PULL_AUTHOR": ctx.Pull.Author, + "PULL_NUM": fmt.Sprintf("%d", ctx.Pull.Num), + "PULL_URL": ctx.Pull.URL, + "REPO_REL_DIR": ctx.RepoRelDir, + "USER_NAME": ctx.User.Username, + "WORKSPACE": ctx.Workspace, } finalEnvVars := baseEnvVars diff --git a/server/core/runtime/run_step_runner_test.go b/server/core/runtime/run_step_runner_test.go index 4672fa2bb0..2429d88fe8 100644 --- a/server/core/runtime/run_step_runner_test.go +++ b/server/core/runtime/run_step_runner_test.go @@ -10,7 +10,9 @@ import ( . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/config/valid" "github.com/runatlantis/atlantis/server/core/runtime" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks" @@ -20,11 +22,12 @@ import ( func TestRunStepRunner_Run(t *testing.T) { cases := []struct { - Command string - ProjectName string - ExpOut string - ExpErr string - Version string + Command string + ProjectName string + ExpOut string + ExpErr string + Version string + Distribution string }{ { Command: "", @@ -69,6 +72,18 @@ func TestRunStepRunner_Run(t *testing.T) { ProjectName: "my/project/name", ExpOut: "workspace=myworkspace version=0.11.0 dir=$DIR planfile=$DIR/my::project::name-myworkspace.tfplan showfile=$DIR/my::project::name-myworkspace.json project=my/project/name\n", }, + { + Command: "echo distribution=$ATLANTIS_TERRAFORM_DISTRIBUTION", + ProjectName: "my/project/name", + ExpOut: "distribution=terraform\n", + Distribution: "terraform", + }, + { + Command: "echo distribution=$ATLANTIS_TERRAFORM_DISTRIBUTION", + ProjectName: "my/project/name", + ExpOut: "distribution=tofu\n", + Distribution: "opentofu", + }, { Command: "echo base_repo_name=$BASE_REPO_NAME base_repo_owner=$BASE_REPO_OWNER head_repo_name=$HEAD_REPO_NAME head_repo_owner=$HEAD_REPO_OWNER head_branch_name=$HEAD_BRANCH_NAME head_commit=$HEAD_COMMIT base_branch_name=$BASE_BRANCH_NAME pull_num=$PULL_NUM pull_url=$PULL_URL pull_author=$PULL_AUTHOR repo_rel_dir=$REPO_REL_DIR", ExpOut: "base_repo_name=basename base_repo_owner=baseowner head_repo_name=headname head_repo_owner=headowner head_branch_name=add-feat head_commit=12345abcdef base_branch_name=main pull_num=2 pull_url=https://github.com/runatlantis/atlantis/pull/2 pull_author=acme repo_rel_dir=mydir\n", @@ -100,11 +115,17 @@ func TestRunStepRunner_Run(t *testing.T) { Ok(t, err) + projTFDistribution := "terraform" + if c.Distribution != "" { + projTFDistribution = c.Distribution + } + defaultVersion, _ := version.NewVersion("0.8") RegisterMockTestingT(t) - terraform := mocks.NewMockClient() - When(terraform.EnsureVersion(Any[logging.SimpleLogging](), Any[*version.Version]())). + terraform := tfclientmocks.NewMockClient() + defaultDistribution := tf.NewDistributionTerraformWithDownloader(mocks.NewMockDownloader()) + When(terraform.EnsureVersion(Any[logging.SimpleLogging](), Any[tf.Distribution](), Any[*version.Version]())). ThenReturn(nil) logger := logging.NewNoopLogger(t) @@ -113,6 +134,7 @@ func TestRunStepRunner_Run(t *testing.T) { r := runtime.RunStepRunner{ TerraformExecutor: terraform, + DefaultTFDistribution: defaultDistribution, DefaultTFVersion: defaultVersion, TerraformBinDir: "/bin/dir", ProjectCmdOutputHandler: projectCmdOutputHandler, @@ -138,12 +160,13 @@ func TestRunStepRunner_Run(t *testing.T) { User: models.User{ Username: "acme-user", }, - Log: logger, - Workspace: "myworkspace", - RepoRelDir: "mydir", - TerraformVersion: projVersion, - ProjectName: c.ProjectName, - EscapedCommentArgs: []string{"-target=resource1", "-target=resource2"}, + Log: logger, + Workspace: "myworkspace", + RepoRelDir: "mydir", + TerraformDistribution: &projTFDistribution, + TerraformVersion: projVersion, + ProjectName: c.ProjectName, + EscapedCommentArgs: []string{"-target=resource1", "-target=resource2"}, } out, err := r.Run(ctx, nil, c.Command, tmpDir, map[string]string{"test": "var"}, true, valid.PostProcessRunOutputShow) if c.ExpErr != "" { @@ -157,8 +180,8 @@ func TestRunStepRunner_Run(t *testing.T) { expOut := strings.Replace(c.ExpOut, "$DIR", tmpDir, -1) Equals(t, expOut, out) - terraform.VerifyWasCalledOnce().EnsureVersion(logger, projVersion) - terraform.VerifyWasCalled(Never()).EnsureVersion(logger, defaultVersion) + terraform.VerifyWasCalledOnce().EnsureVersion(Eq(logger), NotEq(defaultDistribution), Eq(projVersion)) + terraform.VerifyWasCalled(Never()).EnsureVersion(Eq(logger), Eq(defaultDistribution), Eq(defaultVersion)) }) } diff --git a/server/core/runtime/runtime.go b/server/core/runtime/runtime.go index 52fc5180eb..35e571262b 100644 --- a/server/core/runtime/runtime.go +++ b/server/core/runtime/runtime.go @@ -11,6 +11,7 @@ import ( version "github.com/hashicorp/go-version" "github.com/pkg/errors" runtimemodels "github.com/runatlantis/atlantis/server/core/runtime/models" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/logging" @@ -26,8 +27,8 @@ const ( // TerraformExec brings the interface from TerraformClient into this package // without causing circular imports. type TerraformExec interface { - RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *version.Version, workspace string) (string, error) - EnsureVersion(log logging.SimpleLogging, v *version.Version) error + RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, d terraform.Distribution, v *version.Version, workspace string) (string, error) + EnsureVersion(log logging.SimpleLogging, d terraform.Distribution, v *version.Version) error } // AsyncTFExec brings the interface from TerraformClient into this package @@ -43,7 +44,7 @@ type AsyncTFExec interface { // Callers can use the input channel to pass stdin input to the command. // If any error is passed on the out channel, there will be no // further output (so callers are free to exit). - RunCommandAsync(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *version.Version, workspace string) (chan<- string, <-chan runtimemodels.Line) + RunCommandAsync(ctx command.ProjectContext, path string, args []string, envs map[string]string, d terraform.Distribution, v *version.Version, workspace string) (chan<- string, <-chan runtimemodels.Line) } // StatusUpdater brings the interface from CommitStatusUpdater into this package diff --git a/server/core/runtime/show_step_runner.go b/server/core/runtime/show_step_runner.go index ba89479b56..ed346bc184 100644 --- a/server/core/runtime/show_step_runner.go +++ b/server/core/runtime/show_step_runner.go @@ -6,15 +6,17 @@ import ( "github.com/hashicorp/go-version" "github.com/pkg/errors" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" ) const minimumShowTfVersion string = "0.12.0" -func NewShowStepRunner(executor TerraformExec, defaultTFVersion *version.Version) (Runner, error) { +func NewShowStepRunner(executor TerraformExec, defaultTfDistribution terraform.Distribution, defaultTFVersion *version.Version) (Runner, error) { showStepRunner := &showStepRunner{ - terraformExecutor: executor, - defaultTFVersion: defaultTFVersion, + terraformExecutor: executor, + defaultTfDistribution: defaultTfDistribution, + defaultTFVersion: defaultTFVersion, } remotePlanRunner := NullRunner{} runner := NewPlanTypeStepRunnerDelegate(showStepRunner, remotePlanRunner) @@ -23,12 +25,17 @@ func NewShowStepRunner(executor TerraformExec, defaultTFVersion *version.Version // showStepRunner runs terraform show on an existing plan file and outputs it to a json file type showStepRunner struct { - terraformExecutor TerraformExec - defaultTFVersion *version.Version + terraformExecutor TerraformExec + defaultTfDistribution terraform.Distribution + defaultTFVersion *version.Version } func (p *showStepRunner) Run(ctx command.ProjectContext, _ []string, path string, envs map[string]string) (string, error) { + tfDistribution := p.defaultTfDistribution tfVersion := p.defaultTFVersion + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } if ctx.TerraformVersion != nil { tfVersion = ctx.TerraformVersion } @@ -41,6 +48,7 @@ func (p *showStepRunner) Run(ctx command.ProjectContext, _ []string, path string path, []string{"show", "-json", filepath.Clean(planFile)}, envs, + tfDistribution, tfVersion, ctx.Workspace, ) diff --git a/server/core/runtime/show_step_runner_test.go b/server/core/runtime/show_step_runner_test.go index 9803efb9ff..8c390014ad 100644 --- a/server/core/runtime/show_step_runner_test.go +++ b/server/core/runtime/show_step_runner_test.go @@ -9,7 +9,9 @@ import ( "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/logging" . "github.com/runatlantis/atlantis/testing" @@ -20,6 +22,8 @@ func TestShowStepRunnner(t *testing.T) { path := t.TempDir() resultPath := filepath.Join(path, "test-default.json") envs := map[string]string{"key": "val"} + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.12") context := command.ProjectContext{ Workspace: "default", @@ -29,17 +33,18 @@ func TestShowStepRunnner(t *testing.T) { RegisterMockTestingT(t) - mockExecutor := mocks.NewMockClient() + mockExecutor := tfclientmocks.NewMockClient() subject := showStepRunner{ - terraformExecutor: mockExecutor, - defaultTFVersion: tfVersion, + terraformExecutor: mockExecutor, + defaultTfDistribution: tfDistribution, + defaultTFVersion: tfVersion, } t.Run("success", func(t *testing.T) { When(mockExecutor.RunCommandWithVersion( - context, path, []string{"show", "-json", filepath.Join(path, "test-default.tfplan")}, envs, tfVersion, context.Workspace, + context, path, []string{"show", "-json", filepath.Join(path, "test-default.tfplan")}, envs, tfDistribution, tfVersion, context.Workspace, )).ThenReturn("success", nil) r, err := subject.Run(context, []string{}, path, envs) @@ -57,6 +62,8 @@ func TestShowStepRunnner(t *testing.T) { t.Run("success w/ version override", func(t *testing.T) { v, _ := version.NewVersion("0.13.0") + mockDownloader := mocks.NewMockDownloader() + d := tf.NewDistributionTerraformWithDownloader(mockDownloader) contextWithVersionOverride := command.ProjectContext{ Workspace: "default", @@ -66,7 +73,7 @@ func TestShowStepRunnner(t *testing.T) { } When(mockExecutor.RunCommandWithVersion( - contextWithVersionOverride, path, []string{"show", "-json", filepath.Join(path, "test-default.tfplan")}, envs, v, context.Workspace, + contextWithVersionOverride, path, []string{"show", "-json", filepath.Join(path, "test-default.tfplan")}, envs, d, v, context.Workspace, )).ThenReturn("success", nil) r, err := subject.Run(contextWithVersionOverride, []string{}, path, envs) @@ -81,9 +88,39 @@ func TestShowStepRunnner(t *testing.T) { }) + t.Run("success w/ distribution override", func(t *testing.T) { + + v, _ := version.NewVersion("0.13.0") + mockDownloader := mocks.NewMockDownloader() + d := tf.NewDistributionTerraformWithDownloader(mockDownloader) + projTFDistribution := "opentofu" + + contextWithDistributionOverride := command.ProjectContext{ + Workspace: "default", + ProjectName: "test", + Log: logger, + TerraformDistribution: &projTFDistribution, + } + + When(mockExecutor.RunCommandWithVersion( + Eq(contextWithDistributionOverride), Eq(path), Eq([]string{"show", "-json", filepath.Join(path, "test-default.tfplan")}), Eq(envs), NotEq(d), NotEq(v), Eq(context.Workspace), + )).ThenReturn("success", nil) + + r, err := subject.Run(contextWithDistributionOverride, []string{}, path, envs) + + Ok(t, err) + + actual, _ := os.ReadFile(resultPath) + + actualStr := string(actual) + Assert(t, actualStr == "success", "got expected result") + Assert(t, r == "success", "returned expected result") + + }) + t.Run("failure running command", func(t *testing.T) { When(mockExecutor.RunCommandWithVersion( - context, path, []string{"show", "-json", filepath.Join(path, "test-default.tfplan")}, envs, tfVersion, context.Workspace, + context, path, []string{"show", "-json", filepath.Join(path, "test-default.tfplan")}, envs, tfDistribution, tfVersion, context.Workspace, )).ThenReturn("success", errors.New("error")) _, err := subject.Run(context, []string{}, path, envs) diff --git a/server/core/runtime/state_rm_step_runner.go b/server/core/runtime/state_rm_step_runner.go index 3b4a08f102..42af97c006 100644 --- a/server/core/runtime/state_rm_step_runner.go +++ b/server/core/runtime/state_rm_step_runner.go @@ -5,25 +5,32 @@ import ( "path/filepath" version "github.com/hashicorp/go-version" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/utils" ) type stateRmStepRunner struct { - terraformExecutor TerraformExec - defaultTFVersion *version.Version + terraformExecutor TerraformExec + defaultTFDistribution terraform.Distribution + defaultTFVersion *version.Version } -func NewStateRmStepRunner(terraformExecutor TerraformExec, defaultTfVersion *version.Version) Runner { +func NewStateRmStepRunner(terraformExecutor TerraformExec, defaultTfDistribution terraform.Distribution, defaultTfVersion *version.Version) Runner { runner := &stateRmStepRunner{ - terraformExecutor: terraformExecutor, - defaultTFVersion: defaultTfVersion, + terraformExecutor: terraformExecutor, + defaultTFDistribution: defaultTfDistribution, + defaultTFVersion: defaultTfVersion, } - return NewWorkspaceStepRunnerDelegate(terraformExecutor, defaultTfVersion, runner) + return NewWorkspaceStepRunnerDelegate(terraformExecutor, defaultTfDistribution, defaultTfVersion, runner) } func (p *stateRmStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) { + tfDistribution := p.defaultTFDistribution tfVersion := p.defaultTFVersion + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } if ctx.TerraformVersion != nil { tfVersion = ctx.TerraformVersion } @@ -31,7 +38,7 @@ func (p *stateRmStepRunner) Run(ctx command.ProjectContext, extraArgs []string, stateRmCmd := []string{"state", "rm"} stateRmCmd = append(stateRmCmd, extraArgs...) stateRmCmd = append(stateRmCmd, ctx.EscapedCommentArgs...) - out, err := p.terraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), stateRmCmd, envs, tfVersion, ctx.Workspace) + out, err := p.terraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), stateRmCmd, envs, tfDistribution, tfVersion, ctx.Workspace) // If the state rm was successful and a plan file exists, delete the plan. planPath := filepath.Join(path, GetPlanFilename(ctx.Workspace, ctx.ProjectName)) diff --git a/server/core/runtime/state_rm_step_runner_test.go b/server/core/runtime/state_rm_step_runner_test.go index df5e1036e8..194879f2bd 100644 --- a/server/core/runtime/state_rm_step_runner_test.go +++ b/server/core/runtime/state_rm_step_runner_test.go @@ -8,7 +8,9 @@ import ( "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/logging" . "github.com/runatlantis/atlantis/testing" @@ -29,17 +31,19 @@ func TestStateRmStepRunner_Run_Success(t *testing.T) { } RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() tfVersion, _ := version.NewVersion("0.15.0") - s := NewStateRmStepRunner(terraform, tfVersion) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + s := NewStateRmStepRunner(terraform, tfDistribution, tfVersion) - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil)) Ok(t, err) Equals(t, "output", output) commands := []string{"state", "rm", "-lock=false", "addr1", "addr2", "addr3"} - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfVersion, "default") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfDistribution, tfVersion, "default") _, err = os.Stat(planPath) Assert(t, os.IsNotExist(err), "planfile should be deleted") } @@ -59,23 +63,67 @@ func TestStateRmStepRunner_Run_Workspace(t *testing.T) { } RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() tfVersion, _ := version.NewVersion("0.15.0") - s := NewStateRmStepRunner(terraform, tfVersion) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + s := NewStateRmStepRunner(terraform, tfDistribution, tfVersion) - When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[*version.Version](), Any[string]())). + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). ThenReturn("output", nil) output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil)) Ok(t, err) Equals(t, "output", output) // switch workspace - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "show"}, map[string]string(nil), tfVersion, workspace) - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "select", workspace}, map[string]string(nil), tfVersion, workspace) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "show"}, map[string]string(nil), tfDistribution, tfVersion, workspace) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "select", workspace}, map[string]string(nil), tfDistribution, tfVersion, workspace) // exec state rm commands := []string{"state", "rm", "-lock=false", "addr1", "addr2", "addr3"} - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfVersion, workspace) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfDistribution, tfVersion, workspace) + + _, err = os.Stat(planPath) + Assert(t, os.IsNotExist(err), "planfile should be deleted") +} + +func TestStateRmStepRunner_Run_UsesConfiguredDistribution(t *testing.T) { + logger := logging.NewNoopLogger(t) + workspace := "something" + tmpDir := t.TempDir() + planPath := filepath.Join(tmpDir, fmt.Sprintf("%s.tfplan", workspace)) + err := os.WriteFile(planPath, nil, 0600) + Ok(t, err) + + projTFDistribution := "opentofu" + + context := command.ProjectContext{ + Log: logger, + EscapedCommentArgs: []string{"-lock=false", "addr1", "addr2", "addr3"}, + Workspace: workspace, + TerraformDistribution: &projTFDistribution, + } + + RegisterMockTestingT(t) + terraform := tfclientmocks.NewMockClient() + tfVersion, _ := version.NewVersion("0.15.0") + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + s := NewStateRmStepRunner(terraform, tfDistribution, tfVersion) + + When(terraform.RunCommandWithVersion(Any[command.ProjectContext](), Any[string](), Any[[]string](), Any[map[string]string](), Any[tf.Distribution](), Any[*version.Version](), Any[string]())). + ThenReturn("output", nil) + output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil)) + Ok(t, err) + Equals(t, "output", output) + + // switch workspace + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(context), Eq(tmpDir), Eq([]string{"workspace", "show"}), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq(workspace)) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(context), Eq(tmpDir), Eq([]string{"workspace", "select", workspace}), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq(workspace)) + + // exec state rm + commands := []string{"state", "rm", "-lock=false", "addr1", "addr2", "addr3"} + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(context), Eq(tmpDir), Eq(commands), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq(workspace)) _, err = os.Stat(planPath) Assert(t, os.IsNotExist(err), "planfile should be deleted") diff --git a/server/core/runtime/version_step_runner.go b/server/core/runtime/version_step_runner.go index c75c5396fb..db1f525743 100644 --- a/server/core/runtime/version_step_runner.go +++ b/server/core/runtime/version_step_runner.go @@ -4,22 +4,28 @@ import ( "path/filepath" "github.com/hashicorp/go-version" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" ) // VersionStepRunner runs a version command given a ctx type VersionStepRunner struct { - TerraformExecutor TerraformExec - DefaultTFVersion *version.Version + TerraformExecutor TerraformExec + DefaultTFDistribution terraform.Distribution + DefaultTFVersion *version.Version } // Run ensures a given version for the executable, builds the args from the project context and then runs executable returning the result func (v *VersionStepRunner) Run(ctx command.ProjectContext, _ []string, path string, envs map[string]string) (string, error) { + tfDistribution := v.DefaultTFDistribution tfVersion := v.DefaultTFVersion + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } if ctx.TerraformVersion != nil { tfVersion = ctx.TerraformVersion } versionCmd := []string{"version"} - return v.TerraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), versionCmd, envs, tfVersion, ctx.Workspace) + return v.TerraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), versionCmd, envs, tfDistribution, tfVersion, ctx.Workspace) } diff --git a/server/core/runtime/version_step_runner_test.go b/server/core/runtime/version_step_runner_test.go index 55c4fc05f4..45bf890fab 100644 --- a/server/core/runtime/version_step_runner_test.go +++ b/server/core/runtime/version_step_runner_test.go @@ -5,7 +5,9 @@ import ( "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/logging" @@ -33,18 +35,62 @@ func TestRunVersionStep(t *testing.T) { }, } - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.15.0") tmpDir := t.TempDir() s := &VersionStepRunner{ - TerraformExecutor: terraform, - DefaultTFVersion: tfVersion, + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, } t.Run("ensure runs", func(t *testing.T) { _, err := s.Run(context, []string{}, tmpDir, map[string]string(nil)) - terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"version"}, map[string]string(nil), tfVersion, "default") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"version"}, map[string]string(nil), tfDistribution, tfVersion, "default") + Ok(t, err) + }) +} + +func TestVersionStepRunner_Run_UsesConfiguredDistribution(t *testing.T) { + RegisterMockTestingT(t) + logger := logging.NewNoopLogger(t) + workspace := "default" + projTFDistribution := "opentofu" + context := command.ProjectContext{ + Log: logger, + EscapedCommentArgs: []string{"comment", "args"}, + Workspace: workspace, + RepoRelDir: ".", + User: models.User{Username: "username"}, + Pull: models.PullRequest{ + Num: 2, + }, + BaseRepo: models.Repo{ + FullName: "owner/repo", + Owner: "owner", + Name: "repo", + }, + TerraformDistribution: &projTFDistribution, + } + + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + tfVersion, _ := version.NewVersion("0.15.0") + tmpDir := t.TempDir() + + s := &VersionStepRunner{ + TerraformExecutor: terraform, + DefaultTFDistribution: tfDistribution, + DefaultTFVersion: tfVersion, + } + + t.Run("ensure runs", func(t *testing.T) { + _, err := s.Run(context, []string{}, tmpDir, map[string]string(nil)) + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(context), Eq(tmpDir), Eq([]string{"version"}), Eq(map[string]string(nil)), NotEq(tfDistribution), Eq(tfVersion), Eq("default")) Ok(t, err) }) } diff --git a/server/core/runtime/workspace_step_runner_delegate.go b/server/core/runtime/workspace_step_runner_delegate.go index 9d77db44d0..5628a6a351 100644 --- a/server/core/runtime/workspace_step_runner_delegate.go +++ b/server/core/runtime/workspace_step_runner_delegate.go @@ -5,33 +5,40 @@ import ( "strings" "github.com/hashicorp/go-version" + "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/events/command" ) // workspaceStepRunnerDelegate ensures that a given step runner run on switched workspace type workspaceStepRunnerDelegate struct { - terraformExecutor TerraformExec - defaultTfVersion *version.Version - delegate Runner + terraformExecutor TerraformExec + defaultTfDistribution terraform.Distribution + defaultTfVersion *version.Version + delegate Runner } -func NewWorkspaceStepRunnerDelegate(terraformExecutor TerraformExec, defaultTfVersion *version.Version, delegate Runner) Runner { +func NewWorkspaceStepRunnerDelegate(terraformExecutor TerraformExec, defaultTfDistribution terraform.Distribution, defaultTfVersion *version.Version, delegate Runner) Runner { return &workspaceStepRunnerDelegate{ - terraformExecutor: terraformExecutor, - defaultTfVersion: defaultTfVersion, - delegate: delegate, + terraformExecutor: terraformExecutor, + defaultTfDistribution: defaultTfDistribution, + defaultTfVersion: defaultTfVersion, + delegate: delegate, } } func (r *workspaceStepRunnerDelegate) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) { + tfDistribution := r.defaultTfDistribution tfVersion := r.defaultTfVersion + if ctx.TerraformDistribution != nil { + tfDistribution = terraform.NewDistribution(*ctx.TerraformDistribution) + } if ctx.TerraformVersion != nil { tfVersion = ctx.TerraformVersion } // We only need to switch workspaces in version 0.9.*. In older versions, // there is no such thing as a workspace so we don't need to do anything. - if err := r.switchWorkspace(ctx, path, tfVersion, envs); err != nil { + if err := r.switchWorkspace(ctx, path, tfDistribution, tfVersion, envs); err != nil { return "", err } @@ -40,7 +47,7 @@ func (r *workspaceStepRunnerDelegate) Run(ctx command.ProjectContext, extraArgs // switchWorkspace changes the terraform workspace if necessary and will create // it if it doesn't exist. It handles differences between versions. -func (r *workspaceStepRunnerDelegate) switchWorkspace(ctx command.ProjectContext, path string, tfVersion *version.Version, envs map[string]string) error { +func (r *workspaceStepRunnerDelegate) switchWorkspace(ctx command.ProjectContext, path string, tfDistribution terraform.Distribution, tfVersion *version.Version, envs map[string]string) error { // In versions less than 0.9 there is no support for workspaces. noWorkspaceSupport := MustConstraint("<0.9").Check(tfVersion) // If the user tried to set a specific workspace in the comment but their @@ -63,7 +70,7 @@ func (r *workspaceStepRunnerDelegate) switchWorkspace(ctx command.ProjectContext // already in the right workspace then no need to switch. This will save us // about ten seconds. This command is only available in > 0.10. if !runningZeroPointNine { - workspaceShowOutput, err := r.terraformExecutor.RunCommandWithVersion(ctx, path, []string{workspaceCmd, "show"}, envs, tfVersion, ctx.Workspace) + workspaceShowOutput, err := r.terraformExecutor.RunCommandWithVersion(ctx, path, []string{workspaceCmd, "show"}, envs, tfDistribution, tfVersion, ctx.Workspace) if err != nil { return err } @@ -78,11 +85,11 @@ func (r *workspaceStepRunnerDelegate) switchWorkspace(ctx command.ProjectContext // To do this we can either select and catch the error or use list and then // look for the workspace. Both commands take the same amount of time so // that's why we're running select here. - _, err := r.terraformExecutor.RunCommandWithVersion(ctx, path, []string{workspaceCmd, "select", ctx.Workspace}, envs, tfVersion, ctx.Workspace) + _, err := r.terraformExecutor.RunCommandWithVersion(ctx, path, []string{workspaceCmd, "select", ctx.Workspace}, envs, tfDistribution, tfVersion, ctx.Workspace) if err != nil { // If terraform workspace select fails we run terraform workspace // new to create a new workspace automatically. - out, err := r.terraformExecutor.RunCommandWithVersion(ctx, path, []string{workspaceCmd, "new", ctx.Workspace}, envs, tfVersion, ctx.Workspace) + out, err := r.terraformExecutor.RunCommandWithVersion(ctx, path, []string{workspaceCmd, "new", ctx.Workspace}, envs, tfDistribution, tfVersion, ctx.Workspace) if err != nil { return fmt.Errorf("%s: %s", err, out) } diff --git a/server/core/runtime/workspace_step_runner_delegate_test.go b/server/core/runtime/workspace_step_runner_delegate_test.go index 2ef3032d50..e705e93b00 100644 --- a/server/core/runtime/workspace_step_runner_delegate_test.go +++ b/server/core/runtime/workspace_step_runner_delegate_test.go @@ -6,7 +6,9 @@ import ( "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" + tf "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/logging" @@ -16,7 +18,9 @@ import ( func TestRun_NoWorkspaceIn08(t *testing.T) { // We don't want any workspace commands to be run in 0.8. RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.8") workspace := "default" logger := logging.NewNoopLogger(t) @@ -24,7 +28,7 @@ func TestRun_NoWorkspaceIn08(t *testing.T) { Log: logger, Workspace: workspace, } - s := NewWorkspaceStepRunnerDelegate(terraform, tfVersion, &NullRunner{}) + s := NewWorkspaceStepRunnerDelegate(terraform, tfDistribution, tfVersion, &NullRunner{}) _, err := s.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) Ok(t, err) @@ -36,6 +40,7 @@ func TestRun_NoWorkspaceIn08(t *testing.T) { "select", "workspace"}, map[string]string(nil), + tfDistribution, tfVersion, workspace) terraform.VerifyWasCalled(Never()).RunCommandWithVersion(ctx, @@ -44,6 +49,7 @@ func TestRun_NoWorkspaceIn08(t *testing.T) { "select", "workspace"}, map[string]string(nil), + tfDistribution, tfVersion, workspace) } @@ -52,11 +58,13 @@ func TestRun_ErrWorkspaceIn08(t *testing.T) { // If they attempt to use a workspace other than default in 0.8 // we should error. RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.8") logger := logging.NewNoopLogger(t) workspace := "notdefault" - s := NewWorkspaceStepRunnerDelegate(terraform, tfVersion, &NullRunner{}) + s := NewWorkspaceStepRunnerDelegate(terraform, tfDistribution, tfVersion, &NullRunner{}) _, err := s.Run(command.ProjectContext{ Log: logger, @@ -67,6 +75,8 @@ func TestRun_ErrWorkspaceIn08(t *testing.T) { func TestRun_SwitchesWorkspace(t *testing.T) { RegisterMockTestingT(t) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) cases := []struct { tfVersion string @@ -92,14 +102,14 @@ func TestRun_SwitchesWorkspace(t *testing.T) { for _, c := range cases { t.Run(c.tfVersion, func(t *testing.T) { - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() tfVersion, _ := version.NewVersion(c.tfVersion) logger := logging.NewNoopLogger(t) ctx := command.ProjectContext{ Log: logger, Workspace: "workspace", } - s := NewWorkspaceStepRunnerDelegate(terraform, tfVersion, &NullRunner{}) + s := NewWorkspaceStepRunnerDelegate(terraform, tfDistribution, tfVersion, &NullRunner{}) _, err := s.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) Ok(t, err) @@ -111,12 +121,74 @@ func TestRun_SwitchesWorkspace(t *testing.T) { "select", "workspace"}, map[string]string(nil), + tfDistribution, tfVersion, "workspace") }) } } +func TestRun_SwitchesWorkspaceDistribution(t *testing.T) { + RegisterMockTestingT(t) + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) + + cases := []struct { + tfVersion string + tfDistribution string + expWorkspaceCmd string + }{ + { + "0.9.0", + "opentofu", + "env", + }, + { + "0.9.11", + "terraform", + "env", + }, + { + "0.10.0", + "terraform", + "workspace", + }, + { + "0.11.0", + "opentofu", + "workspace", + }, + } + + for _, c := range cases { + t.Run(c.tfVersion, func(t *testing.T) { + terraform := tfclientmocks.NewMockClient() + tfVersion, _ := version.NewVersion(c.tfVersion) + logger := logging.NewNoopLogger(t) + ctx := command.ProjectContext{ + Log: logger, + Workspace: "workspace", + TerraformDistribution: &c.tfDistribution, + } + s := NewWorkspaceStepRunnerDelegate(terraform, tfDistribution, tfVersion, &NullRunner{}) + + _, err := s.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) + Ok(t, err) + + // Verify that env select was called as well as plan. + terraform.VerifyWasCalledOnce().RunCommandWithVersion(Eq(ctx), + Eq("/path"), + Eq([]string{c.expWorkspaceCmd, + "select", + "workspace"}), + Eq(map[string]string(nil)), + NotEq(tfDistribution), + Eq(tfVersion), + Eq("workspace")) + }) + } +} + func TestRun_CreatesWorkspace(t *testing.T) { // Test that if `workspace select` fails, we call `workspace new`. RegisterMockTestingT(t) @@ -145,7 +217,9 @@ func TestRun_CreatesWorkspace(t *testing.T) { for _, c := range cases { t.Run(c.tfVersion, func(t *testing.T) { - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion(c.tfVersion) logger := logging.NewNoopLogger(t) ctx := command.ProjectContext{ @@ -163,20 +237,20 @@ func TestRun_CreatesWorkspace(t *testing.T) { Name: "repo", }, } - s := NewWorkspaceStepRunnerDelegate(terraform, tfVersion, &NullRunner{}) + s := NewWorkspaceStepRunnerDelegate(terraform, tfDistribution, tfVersion, &NullRunner{}) // Ensure that we actually try to switch workspaces by making the // output of `workspace show` to be a different name. - When(terraform.RunCommandWithVersion(ctx, "/path", []string{"workspace", "show"}, map[string]string(nil), tfVersion, "workspace")).ThenReturn("diffworkspace\n", nil) + When(terraform.RunCommandWithVersion(ctx, "/path", []string{"workspace", "show"}, map[string]string(nil), tfDistribution, tfVersion, "workspace")).ThenReturn("diffworkspace\n", nil) expWorkspaceArgs := []string{c.expWorkspaceCommand, "select", "workspace"} - When(terraform.RunCommandWithVersion(ctx, "/path", expWorkspaceArgs, map[string]string(nil), tfVersion, "workspace")).ThenReturn("", errors.New("workspace does not exist")) + When(terraform.RunCommandWithVersion(ctx, "/path", expWorkspaceArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace")).ThenReturn("", errors.New("workspace does not exist")) _, err := s.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) Ok(t, err) // Verify that env select was called as well as plan. - terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, "/path", expWorkspaceArgs, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalledOnce().RunCommandWithVersion(ctx, "/path", expWorkspaceArgs, map[string]string(nil), tfDistribution, tfVersion, "workspace") }) } } @@ -185,7 +259,9 @@ func TestRun_NoWorkspaceSwitchIfNotNecessary(t *testing.T) { // Tests that if workspace show says we're on the right workspace we don't // switch. RegisterMockTestingT(t) - terraform := mocks.NewMockClient() + terraform := tfclientmocks.NewMockClient() + mockDownloader := mocks.NewMockDownloader() + tfDistribution := tf.NewDistributionTerraformWithDownloader(mockDownloader) tfVersion, _ := version.NewVersion("0.10.0") logger := logging.NewNoopLogger(t) ctx := command.ProjectContext{ @@ -203,12 +279,12 @@ func TestRun_NoWorkspaceSwitchIfNotNecessary(t *testing.T) { Name: "repo", }, } - s := NewWorkspaceStepRunnerDelegate(terraform, tfVersion, &NullRunner{}) - When(terraform.RunCommandWithVersion(ctx, "/path", []string{"workspace", "show"}, map[string]string(nil), tfVersion, "workspace")).ThenReturn("workspace\n", nil) + s := NewWorkspaceStepRunnerDelegate(terraform, tfDistribution, tfVersion, &NullRunner{}) + When(terraform.RunCommandWithVersion(ctx, "/path", []string{"workspace", "show"}, map[string]string(nil), tfDistribution, tfVersion, "workspace")).ThenReturn("workspace\n", nil) _, err := s.Run(ctx, []string{"extra", "args"}, "/path", map[string]string(nil)) Ok(t, err) // Verify that workspace select was never called. - terraform.VerifyWasCalled(Never()).RunCommandWithVersion(ctx, "/path", []string{"workspace", "select", "workspace"}, map[string]string(nil), tfVersion, "workspace") + terraform.VerifyWasCalled(Never()).RunCommandWithVersion(ctx, "/path", []string{"workspace", "select", "workspace"}, map[string]string(nil), tfDistribution, tfVersion, "workspace") } diff --git a/server/events/terraform/ansi/strip.go b/server/core/terraform/ansi/strip.go similarity index 100% rename from server/events/terraform/ansi/strip.go rename to server/core/terraform/ansi/strip.go diff --git a/server/events/terraform/ansi/strip_test.go b/server/core/terraform/ansi/strip_test.go similarity index 100% rename from server/events/terraform/ansi/strip_test.go rename to server/core/terraform/ansi/strip_test.go diff --git a/server/core/terraform/distribution.go b/server/core/terraform/distribution.go index 0fd781765d..dbeaf6a46b 100644 --- a/server/core/terraform/distribution.go +++ b/server/core/terraform/distribution.go @@ -18,6 +18,14 @@ type Distribution interface { ResolveConstraint(context.Context, string) (*version.Version, error) } +func NewDistribution(distribution string) Distribution { + tfDistribution := NewDistributionTerraform() + if distribution == "opentofu" { + tfDistribution = NewDistributionOpenTofu() + } + return tfDistribution +} + type DistributionOpenTofu struct { downloader Downloader } diff --git a/server/core/terraform/mocks/mock_terraform_client.go b/server/core/terraform/tfclient/mocks/mock_terraform_client.go similarity index 79% rename from server/core/terraform/mocks/mock_terraform_client.go rename to server/core/terraform/tfclient/mocks/mock_terraform_client.go index 279de1a751..9dca6ffd4b 100644 --- a/server/core/terraform/mocks/mock_terraform_client.go +++ b/server/core/terraform/tfclient/mocks/mock_terraform_client.go @@ -1,11 +1,12 @@ // Code generated by pegomock. DO NOT EDIT. -// Source: github.com/runatlantis/atlantis/server/core/terraform (interfaces: Client) +// Source: github.com/runatlantis/atlantis/server/core/terraform/tfclient (interfaces: Client) package mocks import ( go_version "github.com/hashicorp/go-version" pegomock "github.com/petergtz/pegomock/v4" + terraform "github.com/runatlantis/atlantis/server/core/terraform" command "github.com/runatlantis/atlantis/server/events/command" logging "github.com/runatlantis/atlantis/server/logging" "reflect" @@ -42,11 +43,11 @@ func (mock *MockClient) DetectVersion(log logging.SimpleLogging, projectDirector return _ret0 } -func (mock *MockClient) EnsureVersion(log logging.SimpleLogging, v *go_version.Version) error { +func (mock *MockClient) EnsureVersion(log logging.SimpleLogging, d terraform.Distribution, v *go_version.Version) error { if mock == nil { panic("mock must not be nil. Use myMock := NewMockClient().") } - _params := []pegomock.Param{log, v} + _params := []pegomock.Param{log, d, v} _result := pegomock.GetGenericMockFrom(mock).Invoke("EnsureVersion", _params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) var _ret0 error if len(_result) != 0 { @@ -57,11 +58,11 @@ func (mock *MockClient) EnsureVersion(log logging.SimpleLogging, v *go_version.V return _ret0 } -func (mock *MockClient) RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *go_version.Version, workspace string) (string, error) { +func (mock *MockClient) RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, d terraform.Distribution, v *go_version.Version, workspace string) (string, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockClient().") } - _params := []pegomock.Param{ctx, path, args, envs, v, workspace} + _params := []pegomock.Param{ctx, path, args, envs, d, v, workspace} _result := pegomock.GetGenericMockFrom(mock).Invoke("RunCommandWithVersion", _params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) var _ret0 string var _ret1 error @@ -148,8 +149,8 @@ func (c *MockClient_DetectVersion_OngoingVerification) GetAllCapturedArguments() return } -func (verifier *VerifierMockClient) EnsureVersion(log logging.SimpleLogging, v *go_version.Version) *MockClient_EnsureVersion_OngoingVerification { - _params := []pegomock.Param{log, v} +func (verifier *VerifierMockClient) EnsureVersion(log logging.SimpleLogging, d terraform.Distribution, v *go_version.Version) *MockClient_EnsureVersion_OngoingVerification { + _params := []pegomock.Param{log, d, v} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "EnsureVersion", _params, verifier.timeout) return &MockClient_EnsureVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -159,12 +160,12 @@ type MockClient_EnsureVersion_OngoingVerification struct { methodInvocations []pegomock.MethodInvocation } -func (c *MockClient_EnsureVersion_OngoingVerification) GetCapturedArguments() (logging.SimpleLogging, *go_version.Version) { - log, v := c.GetAllCapturedArguments() - return log[len(log)-1], v[len(v)-1] +func (c *MockClient_EnsureVersion_OngoingVerification) GetCapturedArguments() (logging.SimpleLogging, terraform.Distribution, *go_version.Version) { + log, d, v := c.GetAllCapturedArguments() + return log[len(log)-1], d[len(d)-1], v[len(v)-1] } -func (c *MockClient_EnsureVersion_OngoingVerification) GetAllCapturedArguments() (_param0 []logging.SimpleLogging, _param1 []*go_version.Version) { +func (c *MockClient_EnsureVersion_OngoingVerification) GetAllCapturedArguments() (_param0 []logging.SimpleLogging, _param1 []terraform.Distribution, _param2 []*go_version.Version) { _params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(_params) > 0 { if len(_params) > 0 { @@ -174,17 +175,23 @@ func (c *MockClient_EnsureVersion_OngoingVerification) GetAllCapturedArguments() } } if len(_params) > 1 { - _param1 = make([]*go_version.Version, len(c.methodInvocations)) + _param1 = make([]terraform.Distribution, len(c.methodInvocations)) for u, param := range _params[1] { - _param1[u] = param.(*go_version.Version) + _param1[u] = param.(terraform.Distribution) + } + } + if len(_params) > 2 { + _param2 = make([]*go_version.Version, len(c.methodInvocations)) + for u, param := range _params[2] { + _param2[u] = param.(*go_version.Version) } } } return } -func (verifier *VerifierMockClient) RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *go_version.Version, workspace string) *MockClient_RunCommandWithVersion_OngoingVerification { - _params := []pegomock.Param{ctx, path, args, envs, v, workspace} +func (verifier *VerifierMockClient) RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, d terraform.Distribution, v *go_version.Version, workspace string) *MockClient_RunCommandWithVersion_OngoingVerification { + _params := []pegomock.Param{ctx, path, args, envs, d, v, workspace} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RunCommandWithVersion", _params, verifier.timeout) return &MockClient_RunCommandWithVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } @@ -194,12 +201,12 @@ type MockClient_RunCommandWithVersion_OngoingVerification struct { methodInvocations []pegomock.MethodInvocation } -func (c *MockClient_RunCommandWithVersion_OngoingVerification) GetCapturedArguments() (command.ProjectContext, string, []string, map[string]string, *go_version.Version, string) { - ctx, path, args, envs, v, workspace := c.GetAllCapturedArguments() - return ctx[len(ctx)-1], path[len(path)-1], args[len(args)-1], envs[len(envs)-1], v[len(v)-1], workspace[len(workspace)-1] +func (c *MockClient_RunCommandWithVersion_OngoingVerification) GetCapturedArguments() (command.ProjectContext, string, []string, map[string]string, terraform.Distribution, *go_version.Version, string) { + ctx, path, args, envs, d, v, workspace := c.GetAllCapturedArguments() + return ctx[len(ctx)-1], path[len(path)-1], args[len(args)-1], envs[len(envs)-1], d[len(d)-1], v[len(v)-1], workspace[len(workspace)-1] } -func (c *MockClient_RunCommandWithVersion_OngoingVerification) GetAllCapturedArguments() (_param0 []command.ProjectContext, _param1 []string, _param2 [][]string, _param3 []map[string]string, _param4 []*go_version.Version, _param5 []string) { +func (c *MockClient_RunCommandWithVersion_OngoingVerification) GetAllCapturedArguments() (_param0 []command.ProjectContext, _param1 []string, _param2 [][]string, _param3 []map[string]string, _param4 []terraform.Distribution, _param5 []*go_version.Version, _param6 []string) { _params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(_params) > 0 { if len(_params) > 0 { @@ -227,15 +234,21 @@ func (c *MockClient_RunCommandWithVersion_OngoingVerification) GetAllCapturedArg } } if len(_params) > 4 { - _param4 = make([]*go_version.Version, len(c.methodInvocations)) + _param4 = make([]terraform.Distribution, len(c.methodInvocations)) for u, param := range _params[4] { - _param4[u] = param.(*go_version.Version) + _param4[u] = param.(terraform.Distribution) } } if len(_params) > 5 { - _param5 = make([]string, len(c.methodInvocations)) + _param5 = make([]*go_version.Version, len(c.methodInvocations)) for u, param := range _params[5] { - _param5[u] = param.(string) + _param5[u] = param.(*go_version.Version) + } + } + if len(_params) > 6 { + _param6 = make([]string, len(c.methodInvocations)) + for u, param := range _params[6] { + _param6[u] = param.(string) } } } diff --git a/server/core/terraform/terraform_client.go b/server/core/terraform/tfclient/terraform_client.go similarity index 91% rename from server/core/terraform/terraform_client.go rename to server/core/terraform/tfclient/terraform_client.go index d01525704b..5ef864db79 100644 --- a/server/core/terraform/terraform_client.go +++ b/server/core/terraform/tfclient/terraform_client.go @@ -13,8 +13,8 @@ // limitations under the License. // Modified hereafter by contributors to runatlantis/atlantis. // -// Package terraform handles the actual running of terraform commands. -package terraform +// Package tfclient handles the actual running of terraform commands. +package tfclient import ( "context" @@ -33,8 +33,9 @@ import ( "github.com/pkg/errors" "github.com/runatlantis/atlantis/server/core/runtime/models" + "github.com/runatlantis/atlantis/server/core/terraform" + "github.com/runatlantis/atlantis/server/core/terraform/ansi" "github.com/runatlantis/atlantis/server/events/command" - "github.com/runatlantis/atlantis/server/events/terraform/ansi" "github.com/runatlantis/atlantis/server/jobs" "github.com/runatlantis/atlantis/server/logging" ) @@ -47,10 +48,10 @@ type Client interface { // RunCommandWithVersion executes terraform with args in path. If v is nil, // it will use the default Terraform version. workspace is the Terraform // workspace which should be set as an environment variable. - RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, v *version.Version, workspace string) (string, error) + RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, envs map[string]string, d terraform.Distribution, v *version.Version, workspace string) (string, error) // EnsureVersion makes sure that terraform version `v` is available to use - EnsureVersion(log logging.SimpleLogging, v *version.Version) error + EnsureVersion(log logging.SimpleLogging, d terraform.Distribution, v *version.Version) error // DetectVersion Extracts required_version from Terraform configuration in the specified project directory. Returns nil if unable to determine the version. DetectVersion(log logging.SimpleLogging, projectDirectory string) *version.Version @@ -58,7 +59,7 @@ type Client interface { type DefaultClient struct { // Distribution handles logic specific to the TF distribution being used by Atlantis - distribution Distribution + distribution terraform.Distribution // defaultVersion is the default version of terraform to use if another // version isn't specified. @@ -102,7 +103,7 @@ var versionRegex = regexp.MustCompile("(?:Terraform|OpenTofu) v(.*?)(\\s.*)?\n") // NewClientWithDefaultVersion creates a new terraform client and pre-fetches the default version func NewClientWithDefaultVersion( log logging.SimpleLogging, - distribution Distribution, + distribution terraform.Distribution, binDir string, cacheDir string, tfeToken string, @@ -189,7 +190,7 @@ func NewClientWithDefaultVersion( func NewTestClient( log logging.SimpleLogging, - distribution Distribution, + distribution terraform.Distribution, binDir string, cacheDir string, tfeToken string, @@ -227,7 +228,7 @@ func NewTestClient( // Will asynchronously download the required version if it doesn't exist already. func NewClient( log logging.SimpleLogging, - distribution Distribution, + distribution terraform.Distribution, binDir string, cacheDir string, tfeToken string, @@ -256,6 +257,10 @@ func NewClient( ) } +func (c *DefaultClient) DefaultDistribution() terraform.Distribution { + return c.distribution +} + // Version returns the default version of Terraform we use if no other version // is defined. func (c *DefaultClient) DefaultVersion() *version.Version { @@ -326,14 +331,14 @@ func (c *DefaultClient) DetectVersion(log logging.SimpleLogging, projectDirector } // See Client.EnsureVersion. -func (c *DefaultClient) EnsureVersion(log logging.SimpleLogging, v *version.Version) error { +func (c *DefaultClient) EnsureVersion(log logging.SimpleLogging, d terraform.Distribution, v *version.Version) error { if v == nil { v = c.defaultVersion } var err error c.versionsLock.Lock() - _, err = ensureVersion(log, c.distribution, c.versions, v, c.binDir, c.downloadBaseURL, c.downloadAllowed) + _, err = ensureVersion(log, d, c.versions, v, c.binDir, c.downloadBaseURL, c.downloadAllowed) c.versionsLock.Unlock() if err != nil { return err @@ -343,9 +348,9 @@ func (c *DefaultClient) EnsureVersion(log logging.SimpleLogging, v *version.Vers } // See Client.RunCommandWithVersion. -func (c *DefaultClient) RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, customEnvVars map[string]string, v *version.Version, workspace string) (string, error) { +func (c *DefaultClient) RunCommandWithVersion(ctx command.ProjectContext, path string, args []string, customEnvVars map[string]string, d terraform.Distribution, v *version.Version, workspace string) (string, error) { if isAsyncEligibleCommand(args[0]) { - _, outCh := c.RunCommandAsync(ctx, path, args, customEnvVars, v, workspace) + _, outCh := c.RunCommandAsync(ctx, path, args, customEnvVars, d, v, workspace) var lines []string var err error @@ -362,7 +367,7 @@ func (c *DefaultClient) RunCommandWithVersion(ctx command.ProjectContext, path s output = ansi.Strip(output) return fmt.Sprintf("%s\n", output), err } - tfCmd, cmd, err := c.prepExecCmd(ctx.Log, v, workspace, path, args) + tfCmd, cmd, err := c.prepExecCmd(ctx.Log, d, v, workspace, path, args) if err != nil { return "", err } @@ -388,8 +393,8 @@ func (c *DefaultClient) RunCommandWithVersion(ctx command.ProjectContext, path s // prepExecCmd builds a ready to execute command based on the version of terraform // v, and args. It returns a printable representation of the command that will // be run and the actual command. -func (c *DefaultClient) prepExecCmd(log logging.SimpleLogging, v *version.Version, workspace string, path string, args []string) (string, *exec.Cmd, error) { - tfCmd, envVars, err := c.prepCmd(log, v, workspace, path, args) +func (c *DefaultClient) prepExecCmd(log logging.SimpleLogging, d terraform.Distribution, v *version.Version, workspace string, path string, args []string) (string, *exec.Cmd, error) { + tfCmd, envVars, err := c.prepCmd(log, d, v, workspace, path, args) if err != nil { return "", nil, err } @@ -401,7 +406,8 @@ func (c *DefaultClient) prepExecCmd(log logging.SimpleLogging, v *version.Versio // prepCmd prepares a shell command (to be interpreted with `sh -c `) and set of environment // variables for running terraform. -func (c *DefaultClient) prepCmd(log logging.SimpleLogging, v *version.Version, workspace string, path string, args []string) (string, []string, error) { +func (c *DefaultClient) prepCmd(log logging.SimpleLogging, d terraform.Distribution, v *version.Version, workspace string, path string, args []string) (string, []string, error) { + if v == nil { v = c.defaultVersion } @@ -413,7 +419,7 @@ func (c *DefaultClient) prepCmd(log logging.SimpleLogging, v *version.Version, w } else { var err error c.versionsLock.Lock() - binPath, err = ensureVersion(log, c.distribution, c.versions, v, c.binDir, c.downloadBaseURL, c.downloadAllowed) + binPath, err = ensureVersion(log, d, c.versions, v, c.binDir, c.downloadBaseURL, c.downloadAllowed) c.versionsLock.Unlock() if err != nil { return "", nil, err @@ -446,8 +452,8 @@ func (c *DefaultClient) prepCmd(log logging.SimpleLogging, v *version.Version, w // Callers can use the input channel to pass stdin input to the command. // If any error is passed on the out channel, there will be no // further output (so callers are free to exit). -func (c *DefaultClient) RunCommandAsync(ctx command.ProjectContext, path string, args []string, customEnvVars map[string]string, v *version.Version, workspace string) (chan<- string, <-chan models.Line) { - cmd, envVars, err := c.prepCmd(ctx.Log, v, workspace, path, args) +func (c *DefaultClient) RunCommandAsync(ctx command.ProjectContext, path string, args []string, customEnvVars map[string]string, d terraform.Distribution, v *version.Version, workspace string) (chan<- string, <-chan models.Line) { + cmd, envVars, err := c.prepCmd(ctx.Log, d, v, workspace, path, args) if err != nil { // The signature of `RunCommandAsync` doesn't provide for returning an immediate error, only one // once reading the output. Since we won't be spawning a process, simulate that by sending the @@ -486,7 +492,7 @@ func MustConstraint(v string) version.Constraints { // It will download this version if we don't have it. func ensureVersion( log logging.SimpleLogging, - dist Distribution, + dist terraform.Distribution, versions map[string]string, v *version.Version, binDir string, diff --git a/server/core/terraform/terraform_client_internal_test.go b/server/core/terraform/tfclient/terraform_client_internal_test.go similarity index 88% rename from server/core/terraform/terraform_client_internal_test.go rename to server/core/terraform/tfclient/terraform_client_internal_test.go index f92a3fd2d2..9cde70e399 100644 --- a/server/core/terraform/terraform_client_internal_test.go +++ b/server/core/terraform/tfclient/terraform_client_internal_test.go @@ -1,4 +1,4 @@ -package terraform +package tfclient import ( "fmt" @@ -10,6 +10,8 @@ import ( version "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" runtimemodels "github.com/runatlantis/atlantis/server/core/runtime/models" + "github.com/runatlantis/atlantis/server/core/terraform" + terraform_mocks "github.com/runatlantis/atlantis/server/core/terraform/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks" @@ -120,7 +122,9 @@ func TestDefaultClient_RunCommandWithVersion_EnvVars(t *testing.T) { "DIR=$DIR", } customEnvVars := map[string]string{} - out, err := client.RunCommandWithVersion(ctx, tmp, args, customEnvVars, nil, "workspace") + mockDownloader := terraform_mocks.NewMockDownloader() + distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) + out, err := client.RunCommandWithVersion(ctx, tmp, args, customEnvVars, distribution, nil, "workspace") Ok(t, err) exp := fmt.Sprintf("TF_IN_AUTOMATION=true TF_PLUGIN_CACHE_DIR=%s WORKSPACE=workspace ATLANTIS_TERRAFORM_VERSION=0.11.11 DIR=%s\n", tmp, tmp) Equals(t, exp, out) @@ -163,7 +167,9 @@ func TestDefaultClient_RunCommandWithVersion_Error(t *testing.T) { "exit", "1", } - out, err := client.RunCommandWithVersion(ctx, tmp, args, map[string]string{}, nil, "workspace") + mockDownloader := terraform_mocks.NewMockDownloader() + distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) + out, err := client.RunCommandWithVersion(ctx, tmp, args, map[string]string{}, distribution, nil, "workspace") ErrEquals(t, fmt.Sprintf(`running 'echo dying && exit 1' in '%s': exit status 1`, tmp), err) // Test that we still get our output. Equals(t, "dying\n", out) @@ -209,7 +215,9 @@ func TestDefaultClient_RunCommandAsync_Success(t *testing.T) { "ATLANTIS_TERRAFORM_VERSION=$ATLANTIS_TERRAFORM_VERSION", "DIR=$DIR", } - _, outCh := client.RunCommandAsync(ctx, tmp, args, map[string]string{}, nil, "workspace") + mockDownloader := terraform_mocks.NewMockDownloader() + distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) + _, outCh := client.RunCommandAsync(ctx, tmp, args, map[string]string{}, distribution, nil, "workspace") out, err := waitCh(outCh) Ok(t, err) @@ -261,7 +269,9 @@ func TestDefaultClient_RunCommandAsync_BigOutput(t *testing.T) { _, err = f.WriteString(s) Ok(t, err) } - _, outCh := client.RunCommandAsync(ctx, tmp, []string{filename}, map[string]string{}, nil, "workspace") + mockDownloader := terraform_mocks.NewMockDownloader() + distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) + _, outCh := client.RunCommandAsync(ctx, tmp, []string{filename}, map[string]string{}, distribution, nil, "workspace") out, err := waitCh(outCh) Ok(t, err) @@ -301,7 +311,9 @@ func TestDefaultClient_RunCommandAsync_StderrOutput(t *testing.T) { overrideTF: "echo", projectCmdOutputHandler: projectCmdOutputHandler, } - _, outCh := client.RunCommandAsync(ctx, tmp, []string{"stderr", ">&2"}, map[string]string{}, nil, "workspace") + mockDownloader := terraform_mocks.NewMockDownloader() + distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) + _, outCh := client.RunCommandAsync(ctx, tmp, []string{"stderr", ">&2"}, map[string]string{}, distribution, nil, "workspace") out, err := waitCh(outCh) Ok(t, err) @@ -341,7 +353,9 @@ func TestDefaultClient_RunCommandAsync_ExitOne(t *testing.T) { overrideTF: "echo", projectCmdOutputHandler: projectCmdOutputHandler, } - _, outCh := client.RunCommandAsync(ctx, tmp, []string{"dying", "&&", "exit", "1"}, map[string]string{}, nil, "workspace") + mockDownloader := terraform_mocks.NewMockDownloader() + distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) + _, outCh := client.RunCommandAsync(ctx, tmp, []string{"dying", "&&", "exit", "1"}, map[string]string{}, distribution, nil, "workspace") out, err := waitCh(outCh) ErrEquals(t, fmt.Sprintf(`running 'sh -c' 'echo dying && exit 1' in '%s': exit status 1`, tmp), err) @@ -383,7 +397,9 @@ func TestDefaultClient_RunCommandAsync_Input(t *testing.T) { projectCmdOutputHandler: projectCmdOutputHandler, } - inCh, outCh := client.RunCommandAsync(ctx, tmp, []string{"a", "&&", "echo", "$a"}, map[string]string{}, nil, "workspace") + mockDownloader := terraform_mocks.NewMockDownloader() + distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) + inCh, outCh := client.RunCommandAsync(ctx, tmp, []string{"a", "&&", "echo", "$a"}, map[string]string{}, distribution, nil, "workspace") inCh <- "echo me\n" out, err := waitCh(outCh) diff --git a/server/core/terraform/terraform_client_test.go b/server/core/terraform/tfclient/terraform_client_test.go similarity index 86% rename from server/core/terraform/terraform_client_test.go rename to server/core/terraform/tfclient/terraform_client_test.go index 1c2c654495..50afd698a7 100644 --- a/server/core/terraform/terraform_client_test.go +++ b/server/core/terraform/tfclient/terraform_client_test.go @@ -11,7 +11,7 @@ // limitations under the License. // Modified hereafter by contributors to runatlantis/atlantis. -package terraform_test +package tfclient_test import ( "context" @@ -28,6 +28,7 @@ import ( "github.com/runatlantis/atlantis/cmd" "github.com/runatlantis/atlantis/server/core/terraform" "github.com/runatlantis/atlantis/server/core/terraform/mocks" + "github.com/runatlantis/atlantis/server/core/terraform/tfclient" "github.com/runatlantis/atlantis/server/events/command" jobmocks "github.com/runatlantis/atlantis/server/jobs/mocks" "github.com/runatlantis/atlantis/server/logging" @@ -42,12 +43,12 @@ func TestMustConstraint_PanicsOnBadConstraint(t *testing.T) { } }() - terraform.MustConstraint("invalid constraint") + tfclient.MustConstraint("invalid constraint") } func TestMustConstraint(t *testing.T) { t.Log("MustConstraint should return the constrain") - c := terraform.MustConstraint(">0.1") + c := tfclient.MustConstraint(">0.1") expectedConstraint, err := version.NewConstraint(">0.1") Ok(t, err) Equals(t, expectedConstraint.String(), c.String()) @@ -80,13 +81,13 @@ is 0.11.13. You can update by downloading from developer.hashicorp.com/terraform mockDownloader := mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) + c, err := tfclient.NewClient(logger, distribution, binDir, cacheDir, "", "", "", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) Ok(t, err) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) - output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{"test": "123"}, nil, "") + output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{"test": "123"}, distribution, nil, "") Ok(t, err) Equals(t, fakeBinOut+"\n", output) } @@ -117,13 +118,13 @@ is 0.11.13. You can update by downloading from developer.hashicorp.com/terraform mockDownloader := mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) + c, err := tfclient.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) Ok(t, err) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) - output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, nil, "") + output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, distribution, nil, "") Ok(t, err) Equals(t, fakeBinOut+"\n", output) } @@ -141,7 +142,7 @@ func TestNewClient_NoTF(t *testing.T) { mockDownloader := mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - _, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) + _, err := tfclient.NewClient(logger, distribution, binDir, cacheDir, "", "", "", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) ErrEquals(t, "terraform not found in $PATH. Set --default-tf-version or download terraform from https://developer.hashicorp.com/terraform/downloads", err) } @@ -167,13 +168,13 @@ func TestNewClient_DefaultTFFlagInPath(t *testing.T) { mockDownloader := mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, false, true, projectCmdOutputHandler) + c, err := tfclient.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, false, true, projectCmdOutputHandler) Ok(t, err) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) - output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, nil, "") + output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, distribution, nil, "") Ok(t, err) Equals(t, fakeBinOut+"\n", output) } @@ -198,13 +199,13 @@ func TestNewClient_DefaultTFFlagInBinDir(t *testing.T) { mockDownloader := mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - c, err := terraform.NewClient(logging.NewNoopLogger(t), distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) + c, err := tfclient.NewClient(logging.NewNoopLogger(t), distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) Ok(t, err) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) - output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, nil, "") + output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, distribution, nil, "") Ok(t, err) Equals(t, fakeBinOut+"\n", output) } @@ -232,7 +233,7 @@ func TestNewClient_DefaultTFFlagDownload(t *testing.T) { return []ReturnValue{binPath, err} }) distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) + c, err := tfclient.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) Ok(t, err) Ok(t, err) @@ -243,7 +244,7 @@ func TestNewClient_DefaultTFFlagDownload(t *testing.T) { // Reset PATH so that it has sh. Ok(t, os.Setenv("PATH", orig)) - output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, nil, "") + output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, distribution, nil, "") Ok(t, err) Equals(t, "\nTerraform v0.11.10\n\n", output) } @@ -255,7 +256,7 @@ func TestNewClient_BadVersion(t *testing.T) { projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler() mockDownloader := mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - _, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "malformed", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) + _, err := tfclient.NewClient(logger, distribution, binDir, cacheDir, "", "", "malformed", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) ErrEquals(t, "Malformed version: malformed", err) } @@ -283,11 +284,11 @@ func TestRunCommandWithVersion_DLsTF(t *testing.T) { return []ReturnValue{binPath, err} }) - c, err := terraform.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) + c, err := tfclient.NewClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) - output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, v, "") + output, err := c.RunCommandWithVersion(ctx, tmp, []string{"terraform", "init"}, map[string]string{}, distribution, v, "") Assert(t, err == nil, "err: %s: %s", err, output) Equals(t, "\nTerraform v99.99.99\n\n", output) @@ -304,7 +305,7 @@ func TestEnsureVersion_downloaded(t *testing.T) { distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) downloadsAllowed := true - c, err := terraform.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, downloadsAllowed, true, projectCmdOutputHandler) + c, err := tfclient.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, downloadsAllowed, true, projectCmdOutputHandler) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) @@ -318,7 +319,7 @@ func TestEnsureVersion_downloaded(t *testing.T) { return []ReturnValue{binPath, err} }) - err = c.EnsureVersion(logger, v) + err = c.EnsureVersion(logger, distribution, v) Ok(t, err) @@ -337,7 +338,7 @@ func TestEnsureVersion_downloaded_customURL(t *testing.T) { downloadsAllowed := true customURL := "http://releases.example.com" - c, err := terraform.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, customURL, downloadsAllowed, true, projectCmdOutputHandler) + c, err := tfclient.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, customURL, downloadsAllowed, true, projectCmdOutputHandler) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) @@ -351,7 +352,7 @@ func TestEnsureVersion_downloaded_customURL(t *testing.T) { return []ReturnValue{binPath, err} }) - err = c.EnsureVersion(logger, v) + err = c.EnsureVersion(logger, distribution, v) Ok(t, err) @@ -369,7 +370,7 @@ func TestEnsureVersion_downloaded_downloadingDisabled(t *testing.T) { distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) downloadsAllowed := false - c, err := terraform.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, downloadsAllowed, true, projectCmdOutputHandler) + c, err := tfclient.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, downloadsAllowed, true, projectCmdOutputHandler) Ok(t, err) Equals(t, "0.11.10", c.DefaultVersion().String()) @@ -377,7 +378,7 @@ func TestEnsureVersion_downloaded_downloadingDisabled(t *testing.T) { v, err := version.NewVersion("99.99.99") Ok(t, err) - err = c.EnsureVersion(logger, v) + err = c.EnsureVersion(logger, distribution, v) ErrContains(t, "could not find terraform version", err) ErrContains(t, "downloads are disabled", err) mockDownloader.VerifyWasCalled(Never()) @@ -501,7 +502,7 @@ terraform { mockDownloader := mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - c, err := terraform.NewTestClient( + c, err := tfclient.NewTestClient( logger, distribution, binDir, @@ -548,7 +549,7 @@ func TestExtractExactRegex(t *testing.T) { mockDownloader := mocks.NewMockDownloader() distribution := terraform.NewDistributionTerraformWithDownloader(mockDownloader) - c, err := terraform.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) + c, err := tfclient.NewTestClient(logger, distribution, binDir, cacheDir, "", "", "0.11.10", cmd.DefaultTFVersionFlag, cmd.DefaultTFDownloadURL, true, true, projectCmdOutputHandler) Ok(t, err) tests := []struct { diff --git a/server/events/apply_command_runner_test.go b/server/events/apply_command_runner_test.go index 6f713710f6..6ef5873c90 100644 --- a/server/events/apply_command_runner_test.go +++ b/server/events/apply_command_runner_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/db" "github.com/runatlantis/atlantis/server/core/locking" @@ -54,7 +54,7 @@ func TestApplyCommandRunner_IsLocked(t *testing.T) { scopeNull, _, _ := metrics.NewLoggingScope(logger, "atlantis") pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(logger, testdata.GithubRepo, testdata.Pull.Num)).ThenReturn(pull, nil) @@ -475,7 +475,7 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) { scopeNull, _, _ := metrics.NewLoggingScope(logger, "atlantis") pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} diff --git a/server/events/command/project_context.go b/server/events/command/project_context.go index 8fff2831d6..670aaa6c01 100644 --- a/server/events/command/project_context.go +++ b/server/events/command/project_context.go @@ -93,6 +93,10 @@ type ProjectContext struct { // Steps are the sequence of commands we need to run for this project and this // stage. Steps []valid.Step + // TerraformDistribution is the distribution of terraform we should use when + // executing commands for this project. This can be set to nil in which case + // we will use the default Atlantis terraform distribution. + TerraformDistribution *string // TerraformVersion is the version of terraform we should use when executing // commands for this project. This can be set to nil in which case we will // use the default Atlantis terraform version. diff --git a/server/events/command/scope_tags.go b/server/events/command/scope_tags.go index 2f51d86c83..8416927eab 100644 --- a/server/events/command/scope_tags.go +++ b/server/events/command/scope_tags.go @@ -7,12 +7,13 @@ import ( ) type ProjectScopeTags struct { - BaseRepo string - PrNumber string - Project string - ProjectPath string - TerraformVersion string - Workspace string + BaseRepo string + PrNumber string + Project string + ProjectPath string + TerraformDistribution string + TerraformVersion string + Workspace string } func (s ProjectScopeTags) Loadtags() map[string]string { diff --git a/server/events/command_runner.go b/server/events/command_runner.go index fdd4b39153..fd544baabf 100644 --- a/server/events/command_runner.go +++ b/server/events/command_runner.go @@ -17,7 +17,7 @@ import ( "fmt" "strconv" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/mcdafydd/go-azuredevops/azuredevops" "github.com/pkg/errors" "github.com/runatlantis/atlantis/server/core/config/valid" @@ -30,7 +30,7 @@ import ( "github.com/runatlantis/atlantis/server/recovery" "github.com/runatlantis/atlantis/server/utils" tally "github.com/uber-go/tally/v4" - gitlab "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) const ( diff --git a/server/events/command_runner_test.go b/server/events/command_runner_test.go index 8764c08bea..cd9cbc10e4 100644 --- a/server/events/command_runner_test.go +++ b/server/events/command_runner_test.go @@ -27,7 +27,7 @@ import ( "github.com/runatlantis/atlantis/server/logging" "github.com/runatlantis/atlantis/server/metrics" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" . "github.com/petergtz/pegomock/v4" lockingmocks "github.com/runatlantis/atlantis/server/core/locking/mocks" "github.com/runatlantis/atlantis/server/events" @@ -506,7 +506,7 @@ func TestRunCommentCommand_DisableApplyAllDisabled(t *testing.T) { vcsClient := setup(t) applyCommandRunner.DisableApplyAll = true pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), Eq(testdata.Pull.Num))).ThenReturn(pull, nil) @@ -594,7 +594,7 @@ func TestRunCommentCommand_ClosedPull(t *testing.T) { " comment saying that this is not allowed") vcsClient := setup(t) pull := &github.PullRequest{ - State: github.String("closed"), + State: github.Ptr("closed"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.ClosedPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), Eq(testdata.Pull.Num))).ThenReturn(pull, nil) @@ -647,12 +647,12 @@ func TestRunUnlockCommand_VCSComment(t *testing.T) { }{ { name: "PR open", - prState: github.String("open"), + prState: github.Ptr("open"), }, { name: "PR closed", - prState: github.String("closed"), + prState: github.Ptr("closed"), }, } @@ -689,7 +689,7 @@ func TestRunUnlockCommandFail_VCSComment(t *testing.T) { vcsClient := setup(t) pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), @@ -713,7 +713,7 @@ func TestRunUnlockCommandFail_DisableUnlockLabel(t *testing.T) { vcsClient := setup(t) pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), @@ -737,7 +737,7 @@ func TestRunUnlockCommandFail_GetLabelsFail(t *testing.T) { vcsClient := setup(t) pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), @@ -763,7 +763,7 @@ func TestRunUnlockCommandDoesntRetrieveLabelsIfDisableUnlockLabelNotSet(t *testi vcsClient := setup(t) pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), @@ -865,7 +865,7 @@ func TestRunCommentCommand_FailedPreWorkflowHook_FailOnPreWorkflowHookError_Fals When(projectCommandRunner.Plan(Any[command.ProjectContext]())).ThenReturn(command.ProjectResult{PlanSuccess: &models.PlanSuccess{}}) When(workingDir.GetPullDir(Any[models.Repo](), Any[models.PullRequest]())).ThenReturn(tmp, nil) - pull := &github.PullRequest{State: github.String("open")} + pull := &github.PullRequest{State: github.Ptr("open")} modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), Eq(testdata.Pull.Num))).ThenReturn(pull, nil) When(eventParsing.ParseGithubPull(Any[logging.SimpleLogging](), Eq(pull))).ThenReturn(modelPull, modelPull.BaseRepo, testdata.GithubRepo, nil) @@ -907,7 +907,7 @@ func TestRunGenericPlanCommand_DeletePlans(t *testing.T) { When(projectCommandRunner.Plan(Any[command.ProjectContext]())).ThenReturn(command.ProjectResult{PlanSuccess: &models.PlanSuccess{}}) When(workingDir.GetPullDir(Any[models.Repo](), Any[models.PullRequest]())).ThenReturn(tmp, nil) - pull := &github.PullRequest{State: github.String("open")} + pull := &github.PullRequest{State: github.Ptr("open")} modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), Eq(testdata.Pull.Num))).ThenReturn(pull, nil) When(eventParsing.ParseGithubPull(Any[logging.SimpleLogging](), Eq(pull))).ThenReturn(modelPull, modelPull.BaseRepo, testdata.GithubRepo, nil) @@ -1001,7 +1001,7 @@ func TestRunGenericPlanCommand_DiscardApprovals(t *testing.T) { When(projectCommandRunner.Plan(Any[command.ProjectContext]())).ThenReturn(command.ProjectResult{PlanSuccess: &models.PlanSuccess{}}) When(workingDir.GetPullDir(Any[models.Repo](), Any[models.PullRequest]())).ThenReturn(tmp, nil) - pull := &github.PullRequest{State: github.String("open")} + pull := &github.PullRequest{State: github.Ptr("open")} modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), Eq(testdata.Pull.Num))).ThenReturn(pull, nil) When(eventParsing.ParseGithubPull(Any[logging.SimpleLogging](), Eq(pull))).ThenReturn(modelPull, modelPull.BaseRepo, testdata.GithubRepo, nil) @@ -1025,7 +1025,7 @@ func TestFailedApprovalCreatesFailedStatusUpdate(t *testing.T) { defer func() { autoMerger.GlobalAutomerge = false }() pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{ @@ -1071,7 +1071,7 @@ func TestApprovedPoliciesUpdateFailedPolicyStatus(t *testing.T) { defer func() { autoMerger.GlobalAutomerge = false }() pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{ @@ -1127,7 +1127,7 @@ func TestApplyMergeablityWhenPolicyCheckFails(t *testing.T) { defer func() { autoMerger.GlobalAutomerge = false }() pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{ @@ -1174,7 +1174,7 @@ func TestApplyWithAutoMerge_VSCMerge(t *testing.T) { vcsClient := setup(t) pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState} When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), Eq(testdata.Pull.Num))).ThenReturn(pull, nil) @@ -1217,7 +1217,7 @@ func TestRunApply_DiscardedProjects(t *testing.T) { Ok(t, err) Ok(t, boltDB.UpdateProjectStatus(pull, "default", ".", models.DiscardedPlanStatus)) ghPull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } When(githubGetter.GetPullRequest(Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), Eq(testdata.Pull.Num))).ThenReturn(ghPull, nil) When(eventParsing.ParseGithubPull(Any[logging.SimpleLogging](), Eq(ghPull))).ThenReturn(pull, pull.BaseRepo, testdata.GithubRepo, nil) diff --git a/server/events/event_parser.go b/server/events/event_parser.go index 7ab18b07ca..c9cae1c828 100644 --- a/server/events/event_parser.go +++ b/server/events/event_parser.go @@ -24,7 +24,7 @@ import ( giteasdk "code.gitea.io/sdk/gitea" "github.com/go-playground/validator/v10" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" lru "github.com/hashicorp/golang-lru/v2" "github.com/mcdafydd/go-azuredevops/azuredevops" "github.com/pkg/errors" @@ -34,7 +34,7 @@ import ( "github.com/runatlantis/atlantis/server/events/vcs/bitbucketserver" "github.com/runatlantis/atlantis/server/events/vcs/gitea" "github.com/runatlantis/atlantis/server/logging" - "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) const gitlabPullOpened = "opened" diff --git a/server/events/event_parser_test.go b/server/events/event_parser_test.go index ef8f2de627..27515be71d 100644 --- a/server/events/event_parser_test.go +++ b/server/events/event_parser_test.go @@ -21,7 +21,7 @@ import ( "strings" "testing" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/mcdafydd/go-azuredevops/azuredevops" "github.com/mohae/deepcopy" "github.com/runatlantis/atlantis/server/events" @@ -30,7 +30,7 @@ import ( . "github.com/runatlantis/atlantis/server/events/vcs/testdata" "github.com/runatlantis/atlantis/server/logging" . "github.com/runatlantis/atlantis/testing" - gitlab "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) var parser = events.EventParser{ @@ -68,12 +68,12 @@ func TestParseGithubIssueCommentEvent(t *testing.T) { comment := github.IssueCommentEvent{ Repo: &Repo, Issue: &github.Issue{ - Number: github.Int(1), - User: &github.User{Login: github.String("issue_user")}, - HTMLURL: github.String("https://github.com/runatlantis/atlantis/issues/1"), + Number: github.Ptr(1), + User: &github.User{Login: github.Ptr("issue_user")}, + HTMLURL: github.Ptr("https://github.com/runatlantis/atlantis/issues/1"), }, Comment: &github.IssueComment{ - User: &github.User{Login: github.String("comment_user")}, + User: &github.User{Login: github.Ptr("comment_user")}, }, } @@ -170,8 +170,8 @@ func TestParseGithubPullEventFromDraft(t *testing.T) { logger := logging.NewNoopLogger(t) // verify that close event treated as 'close' events by default closeEvent := deepcopy.Copy(PullEvent).(github.PullRequestEvent) - closeEvent.Action = github.String("closed") - closeEvent.PullRequest.Draft = github.Bool(true) + closeEvent.Action = github.Ptr("closed") + closeEvent.PullRequest.Draft = github.Ptr(true) _, evType, _, _, _, err := parser.ParseGithubPullEvent(logger, &closeEvent) Ok(t, err) @@ -179,7 +179,7 @@ func TestParseGithubPullEventFromDraft(t *testing.T) { // verify that draft PRs are treated as 'other' events by default testEvent := deepcopy.Copy(PullEvent).(github.PullRequestEvent) - testEvent.PullRequest.Draft = github.Bool(true) + testEvent.PullRequest.Draft = github.Ptr(true) _, evType, _, _, _, err = parser.ParseGithubPullEvent(logger, &testEvent) Ok(t, err) Equals(t, models.OtherPullEvent, evType) diff --git a/server/events/mock_workingdir_test.go b/server/events/mock_workingdir_test.go index c11b9e28bf..65d5fc00a7 100644 --- a/server/events/mock_workingdir_test.go +++ b/server/events/mock_workingdir_test.go @@ -4,12 +4,11 @@ package events import ( - "reflect" - "time" - pegomock "github.com/petergtz/pegomock/v4" models "github.com/runatlantis/atlantis/server/events/models" logging "github.com/runatlantis/atlantis/server/logging" + "reflect" + "time" ) type MockWorkingDir struct { diff --git a/server/events/mocks/mock_event_parsing.go b/server/events/mocks/mock_event_parsing.go index 85403ea95c..e6b72acacd 100644 --- a/server/events/mocks/mock_event_parsing.go +++ b/server/events/mocks/mock_event_parsing.go @@ -5,13 +5,13 @@ package mocks import ( gitea "code.gitea.io/sdk/gitea" - github "github.com/google/go-github/v66/github" + github "github.com/google/go-github/v68/github" azuredevops "github.com/mcdafydd/go-azuredevops/azuredevops" pegomock "github.com/petergtz/pegomock/v4" models "github.com/runatlantis/atlantis/server/events/models" gitea0 "github.com/runatlantis/atlantis/server/events/vcs/gitea" logging "github.com/runatlantis/atlantis/server/logging" - go_gitlab "github.com/xanzy/go-gitlab" + go_gitlab "gitlab.com/gitlab-org/api/client-go" "reflect" "time" ) diff --git a/server/events/mocks/mock_github_pull_getter.go b/server/events/mocks/mock_github_pull_getter.go index c904e371e4..c2be8a5fb3 100644 --- a/server/events/mocks/mock_github_pull_getter.go +++ b/server/events/mocks/mock_github_pull_getter.go @@ -4,7 +4,7 @@ package mocks import ( - github "github.com/google/go-github/v66/github" + github "github.com/google/go-github/v68/github" pegomock "github.com/petergtz/pegomock/v4" models "github.com/runatlantis/atlantis/server/events/models" logging "github.com/runatlantis/atlantis/server/logging" diff --git a/server/events/mocks/mock_gitlab_merge_request_getter.go b/server/events/mocks/mock_gitlab_merge_request_getter.go index dfaa1396b9..2b28aae238 100644 --- a/server/events/mocks/mock_gitlab_merge_request_getter.go +++ b/server/events/mocks/mock_gitlab_merge_request_getter.go @@ -6,7 +6,7 @@ package mocks import ( pegomock "github.com/petergtz/pegomock/v4" logging "github.com/runatlantis/atlantis/server/logging" - go_gitlab "github.com/xanzy/go-gitlab" + go_gitlab "gitlab.com/gitlab-org/api/client-go" "reflect" "time" ) diff --git a/server/events/plan_command_runner_test.go b/server/events/plan_command_runner_test.go index c0085dc963..b1da0012e1 100644 --- a/server/events/plan_command_runner_test.go +++ b/server/events/plan_command_runner_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/db" "github.com/runatlantis/atlantis/server/events" @@ -472,7 +472,7 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) { scopeNull, _, _ := metrics.NewLoggingScope(logger, "atlantis") pull := &github.PullRequest{ - State: github.String("open"), + State: github.Ptr("open"), } modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num} diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index b27c6f67d8..db46ff433e 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -11,7 +11,7 @@ import ( tally "github.com/uber-go/tally/v4" "github.com/runatlantis/atlantis/server/core/config/valid" - "github.com/runatlantis/atlantis/server/core/terraform" + "github.com/runatlantis/atlantis/server/core/terraform/tfclient" "github.com/runatlantis/atlantis/server/logging" "github.com/runatlantis/atlantis/server/metrics" @@ -59,7 +59,7 @@ func NewInstrumentedProjectCommandBuilder( IncludeGitUntrackedFiles bool, AutoDiscoverMode string, scope tally.Scope, - terraformClient terraform.Client, + terraformClient tfclient.Client, ) *InstrumentedProjectCommandBuilder { scope = scope.SubScope("builder") @@ -119,7 +119,7 @@ func NewProjectCommandBuilder( IncludeGitUntrackedFiles bool, AutoDiscoverMode string, scope tally.Scope, - terraformClient terraform.Client, + terraformClient tfclient.Client, ) *DefaultProjectCommandBuilder { return &DefaultProjectCommandBuilder{ ParserValidator: parserValidator, @@ -249,7 +249,7 @@ type DefaultProjectCommandBuilder struct { // User config option: Controls auto-discovery of projects in a repository. AutoDiscoverMode string // Handles the actual running of Terraform commands. - TerraformExecutor terraform.Client + TerraformExecutor tfclient.Client } // See ProjectCommandBuilder.BuildAutoplanCommands. diff --git a/server/events/project_command_builder_internal_test.go b/server/events/project_command_builder_internal_test.go index d020871b31..115657e38e 100644 --- a/server/events/project_command_builder_internal_test.go +++ b/server/events/project_command_builder_internal_test.go @@ -9,7 +9,7 @@ import ( . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/config" "github.com/runatlantis/atlantis/server/core/config/valid" - "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks" @@ -648,7 +648,7 @@ projects: Ok(t, os.WriteFile(filepath.Join(tmp, "atlantis.yaml"), []byte(c.repoCfg), 0600)) } - terraformClient := mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := NewProjectCommandBuilder( false, @@ -865,7 +865,7 @@ projects: statsScope, _, _ := metrics.NewLoggingScope(logging.NewNoopLogger(t), "atlantis") - terraformClient := mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := NewProjectCommandBuilder( false, @@ -1112,7 +1112,7 @@ workflows: } statsScope, _, _ := metrics.NewLoggingScope(logging.NewNoopLogger(t), "atlantis") - terraformClient := mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := NewProjectCommandBuilder( true, @@ -1264,7 +1264,7 @@ projects: } statsScope, _, _ := metrics.NewLoggingScope(logging.NewNoopLogger(t), "atlantis") - terraformClient := mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := NewProjectCommandBuilder( false, @@ -1406,7 +1406,7 @@ projects: } statsScope, _, _ := metrics.NewLoggingScope(logging.NewNoopLogger(t), "atlantis") - terraformClient := mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := NewProjectCommandBuilder( false, diff --git a/server/events/project_command_builder_test.go b/server/events/project_command_builder_test.go index 2648d6ce5a..b158173eaa 100644 --- a/server/events/project_command_builder_test.go +++ b/server/events/project_command_builder_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/go-version" . "github.com/petergtz/pegomock/v4" - terraform_mocks "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/core/config" "github.com/runatlantis/atlantis/server/core/config/valid" @@ -233,7 +233,7 @@ terraform { scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") userConfig := defaultUserConfig - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() for _, c := range cases { t.Run(c.Description, func(t *testing.T) { @@ -616,7 +616,7 @@ projects: AllowAllRepoSettings: true, } - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, @@ -804,7 +804,7 @@ projects: AllowAllRepoSettings: true, } - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, @@ -1133,7 +1133,7 @@ projects: AllowAllRepoSettings: true, } - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, @@ -1231,7 +1231,7 @@ func TestDefaultProjectCommandBuilder_BuildMultiApply(t *testing.T) { globalCfgArgs := valid.GlobalCfgArgs{} scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, @@ -1317,7 +1317,7 @@ projects: scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") userConfig := defaultUserConfig - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, @@ -1405,7 +1405,7 @@ func TestDefaultProjectCommandBuilder_EscapeArgs(t *testing.T) { AllowAllRepoSettings: true, } - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, @@ -1558,7 +1558,7 @@ projects: AllowAllRepoSettings: true, } - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() When(terraformClient.DetectVersion(Any[logging.SimpleLogging](), Any[string]())).Then(func(params []Param) ReturnValues { projectName := filepath.Base(params[1].(string)) testVersion := testCase.Exp[projectName] @@ -1688,7 +1688,7 @@ projects: AllowAllRepoSettings: true, } scope, _, _ := metrics.NewLoggingScope(logger, "atlantis") - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, @@ -1769,7 +1769,7 @@ func TestDefaultProjectCommandBuilder_WithPolicyCheckEnabled_BuildAutoplanComman } globalCfg := valid.NewGlobalCfgFromArgs(globalCfgArgs) - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( true, @@ -1857,7 +1857,7 @@ func TestDefaultProjectCommandBuilder_BuildVersionCommand(t *testing.T) { globalCfgArgs := valid.GlobalCfgArgs{ AllowAllRepoSettings: false, } - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, @@ -1987,7 +1987,7 @@ func TestDefaultProjectCommandBuilder_BuildPlanCommands_Single_With_RestrictFile Ok(t, err) } - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, // policyChecksSupported @@ -2098,7 +2098,7 @@ func TestDefaultProjectCommandBuilder_BuildPlanCommands_with_IncludeGitUntracked Ok(t, err) } - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() builder := events.NewProjectCommandBuilder( false, // policyChecksSupported diff --git a/server/events/project_command_context_builder.go b/server/events/project_command_context_builder.go index 509fa728b8..8c1fe76516 100644 --- a/server/events/project_command_context_builder.go +++ b/server/events/project_command_context_builder.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" "github.com/runatlantis/atlantis/server/core/config/valid" - "github.com/runatlantis/atlantis/server/core/terraform" + "github.com/runatlantis/atlantis/server/core/terraform/tfclient" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" tally "github.com/uber-go/tally/v4" @@ -38,7 +38,7 @@ type ProjectCommandContextBuilder interface { prjCfg valid.MergedProjectCfg, commentFlags []string, repoDir string, - automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool, terraformClient terraform.Client, + automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool, terraformClient tfclient.Client, ) []command.ProjectContext } @@ -59,7 +59,7 @@ func (cb *CommandScopedStatsProjectCommandContextBuilder) BuildProjectContext( commentFlags []string, repoDir string, automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool, - terraformClient terraform.Client, + terraformClient tfclient.Client, ) (projectCmds []command.ProjectContext) { cb.ProjectCounter.Inc(1) @@ -93,7 +93,7 @@ func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext( commentFlags []string, repoDir string, automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool, - terraformClient terraform.Client, + terraformClient tfclient.Client, ) (projectCmds []command.ProjectContext) { ctx.Log.Debug("Building project command context for %s", cmdName) @@ -166,7 +166,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext( commentFlags []string, repoDir string, automerge, parallelApply, parallelPlan, verbose, abortOnExecutionOrderFail bool, - terraformClient terraform.Client, + terraformClient tfclient.Client, ) (projectCmds []command.ProjectContext) { if prjCfg.PolicyCheck { ctx.Log.Debug("PolicyChecks are enabled") @@ -297,6 +297,7 @@ func newProjectCommandContext(ctx *command.Context, RePlanCmd: planCmd, RepoRelDir: projCfg.RepoRelDir, RepoConfigVersion: projCfg.RepoCfgVersion, + TerraformDistribution: projCfg.TerraformDistribution, TerraformVersion: projCfg.TerraformVersion, User: ctx.User, Verbose: verbose, diff --git a/server/events/project_command_context_builder_test.go b/server/events/project_command_context_builder_test.go index ff40645e0a..5e66cdb4a2 100644 --- a/server/events/project_command_context_builder_test.go +++ b/server/events/project_command_context_builder_test.go @@ -5,7 +5,7 @@ import ( . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/config/valid" - terraform_mocks "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/mocks" @@ -47,7 +47,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) { expectedApplyCmt := "Apply Comment" expectedPlanCmt := "Plan Comment" - terraformClient := terraform_mocks.NewMockClient() + terraformClient := tfclientmocks.NewMockClient() t.Run("with project name defined", func(t *testing.T) { When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, projName, []string{})).ThenReturn(expectedPlanCmt) diff --git a/server/events/project_command_runner_test.go b/server/events/project_command_runner_test.go index 13a75a1658..b013741647 100644 --- a/server/events/project_command_runner_test.go +++ b/server/events/project_command_runner_test.go @@ -23,7 +23,9 @@ import ( . "github.com/petergtz/pegomock/v4" "github.com/runatlantis/atlantis/server/core/config/valid" "github.com/runatlantis/atlantis/server/core/runtime" + "github.com/runatlantis/atlantis/server/core/terraform" tmocks "github.com/runatlantis/atlantis/server/core/terraform/mocks" + tfclientmocks "github.com/runatlantis/atlantis/server/core/terraform/tfclient/mocks" "github.com/runatlantis/atlantis/server/events" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/mocks" @@ -542,12 +544,14 @@ func TestDefaultProjectCommandRunner_ApplyRunStepFailure(t *testing.T) { // not running any Terraform. func TestDefaultProjectCommandRunner_RunEnvSteps(t *testing.T) { RegisterMockTestingT(t) - tfClient := tmocks.NewMockClient() + tfClient := tfclientmocks.NewMockClient() + tfDistribution := terraform.NewDistributionTerraformWithDownloader(tmocks.NewMockDownloader()) tfVersion, err := version.NewVersion("0.12.0") Ok(t, err) projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler() run := runtime.RunStepRunner{ TerraformExecutor: tfClient, + DefaultTFDistribution: tfDistribution, DefaultTFVersion: tfVersion, ProjectCmdOutputHandler: projectCmdOutputHandler, } diff --git a/server/events/vcs/github_client.go b/server/events/vcs/github_client.go index c2f3f4ab53..fec2ebe24f 100644 --- a/server/events/vcs/github_client.go +++ b/server/events/vcs/github_client.go @@ -25,7 +25,7 @@ import ( "strings" "time" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/pkg/errors" "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" @@ -268,8 +268,8 @@ func (g *GithubClient) HidePrevCommandComments(logger logging.SimpleLogging, rep nextPage := 0 for { comments, resp, err := g.client.Issues.ListComments(g.ctx, repo.Owner, repo.Name, pullNum, &github.IssueListCommentsOptions{ - Sort: github.String("created"), - Direction: github.String("asc"), + Sort: github.Ptr("created"), + Direction: github.Ptr("asc"), ListOptions: github.ListOptions{Page: nextPage}, }) if resp != nil { @@ -913,9 +913,9 @@ func (g *GithubClient) UpdateStatus(logger logging.SimpleLogging, repo models.Re logger.Info("Updating GitHub Check status for '%s' to '%s'", src, ghState) status := &github.RepoStatus{ - State: github.String(ghState), - Description: github.String(description), - Context: github.String(src), + State: github.Ptr(ghState), + Description: github.Ptr(description), + Context: github.Ptr(src), TargetURL: &url, } _, resp, err := g.client.Repositories.CreateStatus(g.ctx, repo.Owner, repo.Name, pull.HeadCommit, status) diff --git a/server/events/vcs/github_credentials.go b/server/events/vcs/github_credentials.go index e46b0e3c2c..ad6fadda61 100644 --- a/server/events/vcs/github_credentials.go +++ b/server/events/vcs/github_credentials.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/bradleyfalzon/ghinstallation/v2" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/pkg/errors" ) diff --git a/server/events/vcs/gitlab_client.go b/server/events/vcs/gitlab_client.go index 07ebd0c1f8..e1b04710c5 100644 --- a/server/events/vcs/gitlab_client.go +++ b/server/events/vcs/gitlab_client.go @@ -29,7 +29,7 @@ import ( "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/events/vcs/common" "github.com/runatlantis/atlantis/server/logging" - "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" ) // gitlabMaxCommentLength is the maximum number of chars allowed by Gitlab in a diff --git a/server/events/vcs/gitlab_client_test.go b/server/events/vcs/gitlab_client_test.go index 8aee1e865a..d56bfa2f45 100644 --- a/server/events/vcs/gitlab_client_test.go +++ b/server/events/vcs/gitlab_client_test.go @@ -15,7 +15,7 @@ import ( "github.com/runatlantis/atlantis/server/events/command" "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/logging" - "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" . "github.com/runatlantis/atlantis/testing" ) diff --git a/server/events/vcs/instrumented_client.go b/server/events/vcs/instrumented_client.go index adc0ca0abc..d5d5809d9c 100644 --- a/server/events/vcs/instrumented_client.go +++ b/server/events/vcs/instrumented_client.go @@ -3,7 +3,7 @@ package vcs import ( "strconv" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/logging" "github.com/runatlantis/atlantis/server/metrics" diff --git a/server/events/vcs/mocks/mock_github_pull_request_getter.go b/server/events/vcs/mocks/mock_github_pull_request_getter.go index f8a809e44f..ad5670ca14 100644 --- a/server/events/vcs/mocks/mock_github_pull_request_getter.go +++ b/server/events/vcs/mocks/mock_github_pull_request_getter.go @@ -4,7 +4,7 @@ package mocks import ( - github "github.com/google/go-github/v66/github" + github "github.com/google/go-github/v68/github" pegomock "github.com/petergtz/pegomock/v4" models "github.com/runatlantis/atlantis/server/events/models" logging "github.com/runatlantis/atlantis/server/logging" diff --git a/server/events/vcs/testdata/fixtures.go b/server/events/vcs/testdata/fixtures.go index db17101876..aa18059bac 100644 --- a/server/events/vcs/testdata/fixtures.go +++ b/server/events/vcs/testdata/fixtures.go @@ -22,43 +22,43 @@ import ( "testing" "github.com/golang-jwt/jwt/v5" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/mcdafydd/go-azuredevops/azuredevops" ) var PullEvent = github.PullRequestEvent{ Sender: &github.User{ - Login: github.String("user"), + Login: github.Ptr("user"), }, Repo: &Repo, PullRequest: &Pull, - Action: github.String("opened"), + Action: github.Ptr("opened"), } var Pull = github.PullRequest{ Head: &github.PullRequestBranch{ - SHA: github.String("sha256"), - Ref: github.String("ref"), + SHA: github.Ptr("sha256"), + Ref: github.Ptr("ref"), Repo: &Repo, }, Base: &github.PullRequestBranch{ - SHA: github.String("sha256"), + SHA: github.Ptr("sha256"), Repo: &Repo, - Ref: github.String("basebranch"), + Ref: github.Ptr("basebranch"), }, - HTMLURL: github.String("html-url"), + HTMLURL: github.Ptr("html-url"), User: &github.User{ - Login: github.String("user"), + Login: github.Ptr("user"), }, - Number: github.Int(1), - State: github.String("open"), + Number: github.Ptr(1), + State: github.Ptr("open"), } var Repo = github.Repository{ - FullName: github.String("owner/repo"), - Owner: &github.User{Login: github.String("owner")}, - Name: github.String("repo"), - CloneURL: github.String("https://github.com/owner/repo.git"), + FullName: github.Ptr("owner/repo"), + Owner: &github.User{Login: github.Ptr("owner")}, + Name: github.Ptr("repo"), + CloneURL: github.Ptr("https://github.com/owner/repo.git"), } var ADPullEvent = azuredevops.Event{ diff --git a/server/server.go b/server/server.go index 22f6db5498..a77eeddaf8 100644 --- a/server/server.go +++ b/server/server.go @@ -42,6 +42,7 @@ import ( "github.com/runatlantis/atlantis/server/core/config/valid" "github.com/runatlantis/atlantis/server/core/db" "github.com/runatlantis/atlantis/server/core/redis" + "github.com/runatlantis/atlantis/server/core/terraform/tfclient" "github.com/runatlantis/atlantis/server/jobs" "github.com/runatlantis/atlantis/server/metrics" "github.com/runatlantis/atlantis/server/scheduled" @@ -127,12 +128,13 @@ type Server struct { // Config holds config for server that isn't passed in by the user. type Config struct { - AllowForkPRsFlag string - AtlantisURLFlag string - AtlantisVersion string - DefaultTFVersionFlag string - RepoConfigJSONFlag string - SilenceForkPRErrorsFlag string + AllowForkPRsFlag string + AtlantisURLFlag string + AtlantisVersion string + DefaultTFDistributionFlag string + DefaultTFVersionFlag string + RepoConfigJSONFlag string + SilenceForkPRErrorsFlag string } // WebhookConfig is nested within UserConfig. It's used to configure webhooks. @@ -427,12 +429,9 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { ) } - distribution := terraform.NewDistributionTerraform() - if userConfig.TFDistribution == "opentofu" { - distribution = terraform.NewDistributionOpenTofu() - } + distribution := terraform.NewDistribution(userConfig.DefaultTFDistribution) - terraformClient, err := terraform.NewClient( + terraformClient, err := tfclient.NewClient( logger, distribution, binDir, @@ -449,7 +448,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { // are, then we don't error out because we don't have/want terraform // installed on our CI system where the unit tests run. if err != nil && flag.Lookup("test.v") == nil { - return nil, errors.Wrap(err, fmt.Sprintf("initializing %s", userConfig.TFDistribution)) + return nil, errors.Wrap(err, fmt.Sprintf("initializing %s", userConfig.DefaultTFDistribution)) } markdownRenderer := events.NewMarkdownRenderer( gitlabClient.SupportsCommonMark(), @@ -586,10 +585,12 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { userConfig.ExecutableName, allowCommands, ) + defaultTfDistribution := terraformClient.DefaultDistribution() defaultTfVersion := terraformClient.DefaultVersion() pendingPlanFinder := &events.DefaultPendingPlanFinder{} runStepRunner := &runtime.RunStepRunner{ TerraformExecutor: terraformClient, + DefaultTFDistribution: defaultTfDistribution, DefaultTFVersion: defaultTfVersion, TerraformBinDir: terraformClient.TerraformBinDir(), ProjectCmdOutputHandler: projectCmdOutputHandler, @@ -648,13 +649,14 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { terraformClient, ) - showStepRunner, err := runtime.NewShowStepRunner(terraformClient, defaultTfVersion) + showStepRunner, err := runtime.NewShowStepRunner(terraformClient, defaultTfDistribution, defaultTfVersion) if err != nil { return nil, errors.Wrap(err, "initializing show step runner") } policyCheckStepRunner, err := runtime.NewPolicyCheckStepRunner( + defaultTfDistribution, defaultTfVersion, policy.NewConfTestExecutorWorkflow(logger, binDir, &policy.ConfTestGoGetterVersionDownloader{}), ) @@ -672,17 +674,19 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { Locker: projectLocker, LockURLGenerator: router, InitStepRunner: &runtime.InitStepRunner{ - TerraformExecutor: terraformClient, - DefaultTFVersion: defaultTfVersion, + TerraformExecutor: terraformClient, + DefaultTFDistribution: defaultTfDistribution, + DefaultTFVersion: defaultTfVersion, }, - PlanStepRunner: runtime.NewPlanStepRunner(terraformClient, defaultTfVersion, commitStatusUpdater, terraformClient), + PlanStepRunner: runtime.NewPlanStepRunner(terraformClient, defaultTfDistribution, defaultTfVersion, commitStatusUpdater, terraformClient), ShowStepRunner: showStepRunner, PolicyCheckStepRunner: policyCheckStepRunner, ApplyStepRunner: &runtime.ApplyStepRunner{ - TerraformExecutor: terraformClient, - DefaultTFVersion: defaultTfVersion, - CommitStatusUpdater: commitStatusUpdater, - AsyncTFExec: terraformClient, + TerraformExecutor: terraformClient, + DefaultTFDistribution: defaultTfDistribution, + DefaultTFVersion: defaultTfVersion, + CommitStatusUpdater: commitStatusUpdater, + AsyncTFExec: terraformClient, }, RunStepRunner: runStepRunner, EnvStepRunner: &runtime.EnvStepRunner{ @@ -695,8 +699,8 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { TerraformExecutor: terraformClient, DefaultTFVersion: defaultTfVersion, }, - ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTfVersion), - StateRmStepRunner: runtime.NewStateRmStepRunner(terraformClient, defaultTfVersion), + ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTfDistribution, defaultTfVersion), + StateRmStepRunner: runtime.NewStateRmStepRunner(terraformClient, defaultTfDistribution, defaultTfVersion), WorkingDir: workingDir, Webhooks: webhooksManager, WorkingDirLocker: workingDirLocker, diff --git a/server/user_config.go b/server/user_config.go index 10e6e6b9fc..9cd4f54675 100644 --- a/server/user_config.go +++ b/server/user_config.go @@ -109,7 +109,7 @@ type UserConfig struct { SSLCertFile string `mapstructure:"ssl-cert-file"` SSLKeyFile string `mapstructure:"ssl-key-file"` RestrictFileList bool `mapstructure:"restrict-file-list"` - TFDistribution string `mapstructure:"tf-distribution"` + TFDistribution string `mapstructure:"tf-distribution"` // deprecated in favor of DefaultTFDistribution TFDownload bool `mapstructure:"tf-download"` TFDownloadURL string `mapstructure:"tf-download-url"` TFEHostname string `mapstructure:"tfe-hostname"` @@ -117,6 +117,7 @@ type UserConfig struct { TFEToken string `mapstructure:"tfe-token"` VarFileAllowlist string `mapstructure:"var-file-allowlist"` VCSStatusName string `mapstructure:"vcs-status-name"` + DefaultTFDistribution string `mapstructure:"default-tf-distribution"` DefaultTFVersion string `mapstructure:"default-tf-version"` Webhooks []WebhookConfig `mapstructure:"webhooks" flag:"false"` WebBasicAuth bool `mapstructure:"web-basic-auth"` diff --git a/testdrive/github.go b/testdrive/github.go index a56d3eee35..4fc8279678 100644 --- a/testdrive/github.go +++ b/testdrive/github.go @@ -18,7 +18,7 @@ import ( "strings" "time" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" ) var githubUsername string @@ -57,13 +57,13 @@ func (g *Client) CheckForkSuccess(ownerName string, forkRepoName string) bool { func (g *Client) CreateWebhook(ownerName string, repoName string, hookURL string) error { contentType := "json" hookConfig := &github.HookConfig{ - ContentType: &contentType, - URL: &hookURL, + ContentType: github.Ptr(contentType), + URL: github.Ptr(hookURL), } atlantisHook := &github.Hook{ Events: []string{"issue_comment", "pull_request", "pull_request_review", "push"}, Config: hookConfig, - Active: github.Bool(true), + Active: github.Ptr(true), } _, _, err := g.client.Repositories.CreateHook(g.ctx, ownerName, repoName, atlantisHook) return err @@ -87,10 +87,10 @@ func (g *Client) CreatePullRequest(ownerName string, repoName string, head strin // If not, create it. newPullRequest := &github.NewPullRequest{ - Title: github.String("Welcome to Atlantis!"), - Head: github.String(head), - Body: github.String(pullRequestBody), - Base: github.String(base), + Title: github.Ptr("Welcome to Atlantis!"), + Head: github.Ptr(head), + Body: github.Ptr(pullRequestBody), + Base: github.Ptr(base), } pull, _, err := g.client.PullRequests.Create(g.ctx, ownerName, repoName, newPullRequest) if err != nil { diff --git a/testdrive/testdrive.go b/testdrive/testdrive.go index 6847540fc0..9f2b61c6c7 100644 --- a/testdrive/testdrive.go +++ b/testdrive/testdrive.go @@ -31,7 +31,7 @@ import ( "time" "github.com/briandowns/spinner" - "github.com/google/go-github/v66/github" + "github.com/google/go-github/v68/github" "github.com/mitchellh/colorstring" "github.com/pkg/errors" )