Skip to content

fix: atlantis import on workspaces #2937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions server/controllers/events/events_controller_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,21 @@ func TestGitHubWorkflow(t *testing.T) {
{"exp-output-merge.txt"},
},
},
{
Description: "import workspace",
RepoDir: "import-workspace",
Comments: []string{
"atlantis import -d dir1 -w ops 'random_id.dummy1[0]' AA",
"atlantis import -p dir1-ops 'random_id.dummy2[0]' BB",
"atlantis plan -p dir1-ops",
},
ExpReplies: [][]string{
{"exp-output-import-dir1-ops-dummy1.txt"},
{"exp-output-import-dir1-ops-dummy2.txt"},
{"exp-output-plan.txt"},
{"exp-output-merge.txt"},
},
},
{
Description: "import single project with -var",
RepoDir: "import-single-project-var",
Expand Down Expand Up @@ -1054,6 +1069,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
silenceNoProjects := false

commitStatusUpdater := mocks.NewMockCommitStatusUpdater()
asyncTfExec := runtimemocks.NewMockAsyncTFExec()

mockPreWorkflowHookRunner = runtimemocks.NewMockPreWorkflowHookRunner()
preWorkflowHookURLGenerator := mocks.NewMockPreWorkflowHookURLGenerator()
Expand Down Expand Up @@ -1124,19 +1140,18 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
},
PlanStepRunner: &runtime.PlanStepRunner{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
},
PlanStepRunner: runtime.NewPlanStepRunner(
terraformClient,
defaultTFVersion,
commitStatusUpdater,
asyncTfExec,
),
ShowStepRunner: showStepRunner,
PolicyCheckStepRunner: policyCheckRunner,
ApplyStepRunner: &runtime.ApplyStepRunner{
TerraformExecutor: terraformClient,
},
ImportStepRunner: &runtime.ImportStepRunner{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
},
ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTFVersion),
RunStepRunner: &runtime.RunStepRunner{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: 3
projects:
- name: dir1-ops
dir: dir1
workspace: ops
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
resource "random_id" "dummy1" {
count = terraform.workspace == "ops" ? 1 : 0

keepers = {}
byte_length = 1
}

resource "random_id" "dummy2" {
count = terraform.workspace == "ops" ? 1 : 0

keepers = {}
byte_length = 1
}

output "workspace" {
value = terraform.workspace
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Ran Import for project: `dir1-ops` dir: `dir1` workspace: `ops`

```diff
random_id.dummy1[0]: Importing from ID "AA"...
random_id.dummy1[0]: Import prepared!
Prepared random_id for import
random_id.dummy1[0]: Refreshing state... [id=AA]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.


```

* :repeat: To **plan** this project again, comment:
* `atlantis plan -p dir1-ops`


Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Ran Import for project: `dir1-ops` dir: `dir1` workspace: `ops`

```diff
random_id.dummy2[0]: Importing from ID "BB"...
random_id.dummy2[0]: Import prepared!
Prepared random_id for import
random_id.dummy2[0]: Refreshing state... [id=BB]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.


```

* :repeat: To **plan** this project again, comment:
* `atlantis plan -p dir1-ops`


Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Locks and plans deleted for the projects and workspaces modified in this pull request:

- dir: `dir1` workspace: `ops`
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Ran Plan for project: `dir1-ops` dir: `dir1` workspace: `ops`

```diff

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

```

* :arrow_forward: To **apply** this plan, comment:
* `atlantis apply -p dir1-ops`
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* `atlantis plan -p dir1-ops`

---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* `atlantis apply`
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
* `atlantis unlock`
20 changes: 14 additions & 6 deletions server/core/runtime/import_step_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,29 @@ import (
"github.com/runatlantis/atlantis/server/events/command"
)

type ImportStepRunner struct {
TerraformExecutor TerraformExec
DefaultTFVersion *version.Version
type importStepRunner struct {
terraformExecutor TerraformExec
defaultTFVersion *version.Version
}

func (p *ImportStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) {
tfVersion := p.DefaultTFVersion
func NewImportStepRunner(terraformExecutor TerraformExec, defaultTfVersion *version.Version) Runner {
runner := &importStepRunner{
terraformExecutor: terraformExecutor,
defaultTFVersion: defaultTfVersion,
}
return NewWorkspaceStepRunnerDelegate(terraformExecutor, defaultTfVersion, runner)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this delegation is the most important fix in this PR.

}

func (p *importStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) {
tfVersion := p.defaultTFVersion
if ctx.TerraformVersion != nil {
tfVersion = ctx.TerraformVersion
}

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, 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))
Expand Down
53 changes: 38 additions & 15 deletions server/core/runtime/import_step_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
matchers2 "github.com/runatlantis/atlantis/server/core/terraform/mocks/matchers"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/logging"
. "github.com/runatlantis/atlantis/testing"
)
Expand All @@ -29,33 +28,57 @@ func TestImportStepRunner_Run_Success(t *testing.T) {
Log: logger,
EscapedCommentArgs: []string{"-var", "foo=bar", "addr", "id"},
Workspace: workspace,
RepoRelDir: ".",
User: models.User{Username: "username"},
Pull: models.PullRequest{
Num: 2,
},
BaseRepo: models.Repo{
FullName: "owner/repo",
Owner: "owner",
Name: "repo",
},
}

RegisterMockTestingT(t)
terraform := mocks.NewMockClient()
tfVersion, _ := version.NewVersion("0.15.0")
s := &ImportStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
s := NewImportStepRunner(terraform, tfVersion)

When(terraform.RunCommandWithVersion(matchers.AnyCommandProjectContext(), AnyString(), AnyStringSlice(), matchers2.AnyMapOfStringToString(), matchers2.AnyPtrToGoVersionVersion(), AnyString())).
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)
_, err = os.Stat(planPath)
Assert(t, os.IsNotExist(err), "planfile should be deleted")
}

func TestImportStepRunner_Run_Workspace(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)

context := command.ProjectContext{
Log: logger,
EscapedCommentArgs: []string{"-var", "foo=bar", "addr", "id"},
Workspace: workspace,
}

RegisterMockTestingT(t)
terraform := mocks.NewMockClient()
tfVersion, _ := version.NewVersion("0.15.0")
s := NewImportStepRunner(terraform, tfVersion)

When(terraform.RunCommandWithVersion(matchers.AnyCommandProjectContext(), AnyString(), AnyStringSlice(), matchers2.AnyMapOfStringToString(), matchers2.AnyPtrToGoVersionVersion(), AnyString())).
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)

// exec import
commands := []string{"import", "-var", "foo=bar", "addr", "id"}
terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfVersion, "default")
terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfVersion, workspace)

_, err = os.Stat(planPath)
Assert(t, os.IsNotExist(err), "planfile should be deleted")
}
10 changes: 5 additions & 5 deletions server/core/runtime/minimum_version_step_runner_delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
"github.com/runatlantis/atlantis/server/events/command"
)

// MinimumVersionStepRunnerDelegate ensures that a given step runner can't run unless the command version being used
// minimumVersionStepRunnerDelegate ensures that a given step runner can't run unless the command version being used
// is greater than a provided minimum
type MinimumVersionStepRunnerDelegate struct {
type minimumVersionStepRunnerDelegate struct {
minimumVersion *version.Version
defaultTfVersion *version.Version
delegate Runner
Expand All @@ -20,17 +20,17 @@ func NewMinimumVersionStepRunnerDelegate(minimumVersionStr string, defaultVersio
minimumVersion, err := version.NewVersion(minimumVersionStr)

if err != nil {
return &MinimumVersionStepRunnerDelegate{}, errors.Wrap(err, "initializing minimum version")
return &minimumVersionStepRunnerDelegate{}, errors.Wrap(err, "initializing minimum version")
}

return &MinimumVersionStepRunnerDelegate{
return &minimumVersionStepRunnerDelegate{
minimumVersion: minimumVersion,
defaultTfVersion: defaultVersion,
delegate: delegate,
}, nil
}

func (r *MinimumVersionStepRunnerDelegate) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) {
func (r *minimumVersionStepRunnerDelegate) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) {
tfVersion := r.defaultTfVersion
if ctx.TerraformVersion != nil {
tfVersion = ctx.TerraformVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestRunMinimumVersionDelegate(t *testing.T) {
expectedOut := "some valid output from delegate"

t.Run("default version success", func(t *testing.T) {
subject := &MinimumVersionStepRunnerDelegate{
subject := &minimumVersionStepRunnerDelegate{
defaultTfVersion: tfVersion12,
minimumVersion: tfVersion12,
delegate: mockDelegate,
Expand All @@ -48,7 +48,7 @@ func TestRunMinimumVersionDelegate(t *testing.T) {
})

t.Run("ctx version success", func(t *testing.T) {
subject := &MinimumVersionStepRunnerDelegate{
subject := &minimumVersionStepRunnerDelegate{
defaultTfVersion: tfVersion11,
minimumVersion: tfVersion12,
delegate: mockDelegate,
Expand All @@ -72,7 +72,7 @@ func TestRunMinimumVersionDelegate(t *testing.T) {
})

t.Run("default version failure", func(t *testing.T) {
subject := &MinimumVersionStepRunnerDelegate{
subject := &minimumVersionStepRunnerDelegate{
defaultTfVersion: tfVersion11,
minimumVersion: tfVersion12,
delegate: mockDelegate,
Expand All @@ -94,7 +94,7 @@ func TestRunMinimumVersionDelegate(t *testing.T) {
})

t.Run("ctx version failure", func(t *testing.T) {
subject := &MinimumVersionStepRunnerDelegate{
subject := &minimumVersionStepRunnerDelegate{
defaultTfVersion: tfVersion12,
minimumVersion: tfVersion12,
delegate: mockDelegate,
Expand Down
33 changes: 33 additions & 0 deletions server/core/runtime/mocks/matchers/recv_chan_of_models_line.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading