From 58ae9ebafa21f7ac42b950493b3b4df6e146d79f Mon Sep 17 00:00:00 2001 From: sundowndev-snyk Date: Wed, 13 Jul 2022 11:55:31 +0400 Subject: [PATCH 1/2] feat: state discovery for tfc --- pkg/cmd/scan.go | 14 ++++- pkg/terraform/hcl/backend_test.go | 29 +--------- pkg/terraform/hcl/cloud.go | 33 +++++++++++ pkg/terraform/hcl/cloud_test.go | 55 +++++++++++++++++++ pkg/terraform/hcl/hcl.go | 5 +- pkg/terraform/hcl/hcl_test.go | 33 +++++++++++ pkg/terraform/hcl/testdata/cloud_block.tf | 8 +++ .../hcl/testdata/cloud_block_workspace.tf | 10 ++++ 8 files changed, 154 insertions(+), 33 deletions(-) create mode 100644 pkg/terraform/hcl/cloud.go create mode 100644 pkg/terraform/hcl/cloud_test.go create mode 100644 pkg/terraform/hcl/hcl_test.go create mode 100644 pkg/terraform/hcl/testdata/cloud_block.tf create mode 100644 pkg/terraform/hcl/testdata/cloud_block_workspace.tf diff --git a/pkg/cmd/scan.go b/pkg/cmd/scan.go index e0db4cf2e..482d9f81b 100644 --- a/pkg/cmd/scan.go +++ b/pkg/cmd/scan.go @@ -404,10 +404,18 @@ func retrieveBackendsFromHCL(workdir string) ([]config.SupplierConfig, error) { continue } + var cfg *config.SupplierConfig ws := hcl.GetCurrentWorkspaceName(path.Dir(match)) - if supplierConfig := body.Backend.SupplierConfig(ws); supplierConfig != nil { - globaloutput.Printf(color.WhiteString("Using Terraform state %s found in %s. Use the --from flag to specify another state file.\n"), supplierConfig, match) - supplierConfigs = append(supplierConfigs, *supplierConfig) + + if body.Cloud != nil { + cfg = body.Cloud.SupplierConfig(ws) + } + if body.Backend != nil { + cfg = body.Backend.SupplierConfig(ws) + } + if cfg != nil { + globaloutput.Printf(color.WhiteString("Using Terraform state %s found in %s. Use the --from flag to specify another state file.\n"), cfg, match) + supplierConfigs = append(supplierConfigs, *cfg) } } diff --git a/pkg/terraform/hcl/backend_test.go b/pkg/terraform/hcl/backend_test.go index 8667c28b7..31c0b0cbc 100644 --- a/pkg/terraform/hcl/backend_test.go +++ b/pkg/terraform/hcl/backend_test.go @@ -8,32 +8,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestHCL_getCurrentWorkspaceName(t *testing.T) { - cases := []struct { - name string - dir string - want string - }{ - { - name: "test with non-default workspace", - dir: "testdata/foo_workspace", - want: "foo", - }, - { - name: "test with non-existing directory", - dir: "testdata/noenvfile", - want: "default", - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - workspace := GetCurrentWorkspaceName(tt.dir) - assert.Equal(t, tt.want, workspace) - }) - } -} - func TestBackend_SupplierConfig(t *testing.T) { cases := []struct { name string @@ -45,7 +19,6 @@ func TestBackend_SupplierConfig(t *testing.T) { name: "test with no backend block", filename: "testdata/no_backend_block.tf", want: nil, - wantErr: "testdata/no_backend_block.tf:1,11-11: Missing backend block; A backend block is required.", }, { name: "test with local backend block", @@ -118,7 +91,7 @@ func TestBackend_SupplierConfig(t *testing.T) { } ws := GetCurrentWorkspaceName(path.Dir(tt.filename)) - if hcl.Backend.SupplierConfig(ws) == nil { + if hcl.Backend == nil || hcl.Backend.SupplierConfig(ws) == nil { assert.Nil(t, tt.want) return } diff --git a/pkg/terraform/hcl/cloud.go b/pkg/terraform/hcl/cloud.go new file mode 100644 index 000000000..b0702b884 --- /dev/null +++ b/pkg/terraform/hcl/cloud.go @@ -0,0 +1,33 @@ +package hcl + +import ( + "path" + + "github.com/hashicorp/hcl/v2" + "github.com/snyk/driftctl/pkg/iac/config" + "github.com/snyk/driftctl/pkg/iac/terraform/state" + "github.com/snyk/driftctl/pkg/iac/terraform/state/backend" +) + +type CloudWorkspacesBlock struct { + Name string `hcl:"name,optional"` + Tags []string `hcl:"tags,optional"` +} + +type CloudBlock struct { + Organization string `hcl:"organization"` + Workspaces CloudWorkspacesBlock `hcl:"workspaces,block"` + Remain hcl.Body `hcl:",remain"` +} + +func (c CloudBlock) SupplierConfig(workspace string) *config.SupplierConfig { + // If a workspace is specified in HCL, use it rather than the current environment + if c.Workspaces.Name != "" { + workspace = c.Workspaces.Name + } + return &config.SupplierConfig{ + Key: state.TerraformStateReaderSupplier, + Backend: backend.BackendKeyTFCloud, + Path: path.Join(c.Organization, workspace), + } +} diff --git a/pkg/terraform/hcl/cloud_test.go b/pkg/terraform/hcl/cloud_test.go new file mode 100644 index 000000000..847c5ddf6 --- /dev/null +++ b/pkg/terraform/hcl/cloud_test.go @@ -0,0 +1,55 @@ +package hcl + +import ( + "testing" + + "github.com/snyk/driftctl/pkg/iac/config" + "github.com/stretchr/testify/assert" +) + +func TestCloud_SupplierConfig(t *testing.T) { + cases := []struct { + name string + filename string + want *config.SupplierConfig + wantErr string + }{ + { + name: "test with cloud block and default workspace", + filename: "testdata/cloud_block.tf", + want: &config.SupplierConfig{ + Key: "tfstate", + Backend: "tfcloud", + Path: "example_corp/default", + }, + }, + { + name: "test with cloud block and non-default workspace", + filename: "testdata/cloud_block_workspace.tf", + want: &config.SupplierConfig{ + Key: "tfstate", + Backend: "tfcloud", + Path: "example_corp/my-workspace", + }, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + hcl, err := ParseTerraformFromHCL(tt.filename) + if tt.wantErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tt.wantErr) + return + } + + if hcl.Cloud == nil { + assert.Nil(t, tt.want) + return + } + + assert.Equal(t, *tt.want, *hcl.Cloud.SupplierConfig("default")) + }) + } +} diff --git a/pkg/terraform/hcl/hcl.go b/pkg/terraform/hcl/hcl.go index a26a39be3..d106fbd23 100644 --- a/pkg/terraform/hcl/hcl.go +++ b/pkg/terraform/hcl/hcl.go @@ -18,8 +18,9 @@ type MainBodyBlock struct { } type TerraformBlock struct { - Backend BackendBlock `hcl:"backend,block"` - Remain hcl.Body `hcl:",remain"` + Backend *BackendBlock `hcl:"backend,block"` + Cloud *CloudBlock `hcl:"cloud,block"` + Remain hcl.Body `hcl:",remain"` } func ParseTerraformFromHCL(filename string) (*TerraformBlock, error) { diff --git a/pkg/terraform/hcl/hcl_test.go b/pkg/terraform/hcl/hcl_test.go new file mode 100644 index 000000000..56f9e50af --- /dev/null +++ b/pkg/terraform/hcl/hcl_test.go @@ -0,0 +1,33 @@ +package hcl + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHCL_getCurrentWorkspaceName(t *testing.T) { + cases := []struct { + name string + dir string + want string + }{ + { + name: "test with non-default workspace", + dir: "testdata/foo_workspace", + want: "foo", + }, + { + name: "test with non-existing directory", + dir: "testdata/noenvfile", + want: "default", + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + workspace := GetCurrentWorkspaceName(tt.dir) + assert.Equal(t, tt.want, workspace) + }) + } +} diff --git a/pkg/terraform/hcl/testdata/cloud_block.tf b/pkg/terraform/hcl/testdata/cloud_block.tf new file mode 100644 index 000000000..0d3fdee05 --- /dev/null +++ b/pkg/terraform/hcl/testdata/cloud_block.tf @@ -0,0 +1,8 @@ +terraform { + cloud { + organization = "example_corp" + hostname = "app.terraform.io" # Optional; defaults to app.terraform.io + + workspaces {} + } +} diff --git a/pkg/terraform/hcl/testdata/cloud_block_workspace.tf b/pkg/terraform/hcl/testdata/cloud_block_workspace.tf new file mode 100644 index 000000000..85c807011 --- /dev/null +++ b/pkg/terraform/hcl/testdata/cloud_block_workspace.tf @@ -0,0 +1,10 @@ +terraform { + cloud { + organization = "example_corp" + hostname = "app.terraform.io" # Optional; defaults to app.terraform.io + + workspaces { + name = "my-workspace" + } + } +} From cfd8c53a9da2377df26f7aba2acb1977ef102c2d Mon Sep 17 00:00:00 2001 From: sundowndev-snyk Date: Wed, 13 Jul 2022 12:25:04 +0400 Subject: [PATCH 2/2] refactor: add remain field to cloud workspaces block schema --- pkg/terraform/hcl/cloud.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/terraform/hcl/cloud.go b/pkg/terraform/hcl/cloud.go index b0702b884..3683ef98b 100644 --- a/pkg/terraform/hcl/cloud.go +++ b/pkg/terraform/hcl/cloud.go @@ -10,8 +10,9 @@ import ( ) type CloudWorkspacesBlock struct { - Name string `hcl:"name,optional"` - Tags []string `hcl:"tags,optional"` + Name string `hcl:"name,optional"` + Tags []string `hcl:"tags,optional"` + Remain hcl.Body `hcl:",remain"` } type CloudBlock struct {