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/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_e2e_test.go b/server/controllers/events/events_controller_e2e_test.go
index 2727dd2bac..3b66a28225 100644
--- a/server/controllers/events/events_controller_e2e_test.go
+++ b/server/controllers/events/events_controller_e2e_test.go
@@ -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,
},
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/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/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/project_command_builder.go b/server/events/project_command_builder.go
index c52dee6360..275e8cbfbc 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 7560b5d6de..30dec015a5 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]
@@ -1677,7 +1677,7 @@ projects:
AllowAllRepoSettings: true,
}
scope, _, _ := metrics.NewLoggingScope(logger, "atlantis")
- terraformClient := terraform_mocks.NewMockClient()
+ terraformClient := tfclientmocks.NewMockClient()
builder := events.NewProjectCommandBuilder(
false,
@@ -1746,7 +1746,7 @@ func TestDefaultProjectCommandBuilder_WithPolicyCheckEnabled_BuildAutoplanComman
}
globalCfg := valid.NewGlobalCfgFromArgs(globalCfgArgs)
- terraformClient := terraform_mocks.NewMockClient()
+ terraformClient := tfclientmocks.NewMockClient()
builder := events.NewProjectCommandBuilder(
true,
@@ -1834,7 +1834,7 @@ func TestDefaultProjectCommandBuilder_BuildVersionCommand(t *testing.T) {
globalCfgArgs := valid.GlobalCfgArgs{
AllowAllRepoSettings: false,
}
- terraformClient := terraform_mocks.NewMockClient()
+ terraformClient := tfclientmocks.NewMockClient()
builder := events.NewProjectCommandBuilder(
false,
@@ -1964,7 +1964,7 @@ func TestDefaultProjectCommandBuilder_BuildPlanCommands_Single_With_RestrictFile
Ok(t, err)
}
- terraformClient := terraform_mocks.NewMockClient()
+ terraformClient := tfclientmocks.NewMockClient()
builder := events.NewProjectCommandBuilder(
false, // policyChecksSupported
@@ -2075,7 +2075,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/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"`