Skip to content

Commit 351e216

Browse files
Merge pull request #1563 from snyk/feat/tfstate-discovery-tfc
State discovery for Terraform cloud
2 parents 90a15f9 + cfd8c53 commit 351e216

File tree

8 files changed

+155
-33
lines changed

8 files changed

+155
-33
lines changed

pkg/cmd/scan.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,18 @@ func retrieveBackendsFromHCL(workdir string) ([]config.SupplierConfig, error) {
404404
continue
405405
}
406406

407+
var cfg *config.SupplierConfig
407408
ws := hcl.GetCurrentWorkspaceName(path.Dir(match))
408-
if supplierConfig := body.Backend.SupplierConfig(ws); supplierConfig != nil {
409-
globaloutput.Printf(color.WhiteString("Using Terraform state %s found in %s. Use the --from flag to specify another state file.\n"), supplierConfig, match)
410-
supplierConfigs = append(supplierConfigs, *supplierConfig)
409+
410+
if body.Cloud != nil {
411+
cfg = body.Cloud.SupplierConfig(ws)
412+
}
413+
if body.Backend != nil {
414+
cfg = body.Backend.SupplierConfig(ws)
415+
}
416+
if cfg != nil {
417+
globaloutput.Printf(color.WhiteString("Using Terraform state %s found in %s. Use the --from flag to specify another state file.\n"), cfg, match)
418+
supplierConfigs = append(supplierConfigs, *cfg)
411419
}
412420
}
413421

pkg/terraform/hcl/backend_test.go

+1-28
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,6 @@ import (
88
"github.com/stretchr/testify/assert"
99
)
1010

11-
func TestHCL_getCurrentWorkspaceName(t *testing.T) {
12-
cases := []struct {
13-
name string
14-
dir string
15-
want string
16-
}{
17-
{
18-
name: "test with non-default workspace",
19-
dir: "testdata/foo_workspace",
20-
want: "foo",
21-
},
22-
{
23-
name: "test with non-existing directory",
24-
dir: "testdata/noenvfile",
25-
want: "default",
26-
},
27-
}
28-
29-
for _, tt := range cases {
30-
t.Run(tt.name, func(t *testing.T) {
31-
workspace := GetCurrentWorkspaceName(tt.dir)
32-
assert.Equal(t, tt.want, workspace)
33-
})
34-
}
35-
}
36-
3711
func TestBackend_SupplierConfig(t *testing.T) {
3812
cases := []struct {
3913
name string
@@ -45,7 +19,6 @@ func TestBackend_SupplierConfig(t *testing.T) {
4519
name: "test with no backend block",
4620
filename: "testdata/no_backend_block.tf",
4721
want: nil,
48-
wantErr: "testdata/no_backend_block.tf:1,11-11: Missing backend block; A backend block is required.",
4922
},
5023
{
5124
name: "test with local backend block",
@@ -118,7 +91,7 @@ func TestBackend_SupplierConfig(t *testing.T) {
11891
}
11992

12093
ws := GetCurrentWorkspaceName(path.Dir(tt.filename))
121-
if hcl.Backend.SupplierConfig(ws) == nil {
94+
if hcl.Backend == nil || hcl.Backend.SupplierConfig(ws) == nil {
12295
assert.Nil(t, tt.want)
12396
return
12497
}

pkg/terraform/hcl/cloud.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package hcl
2+
3+
import (
4+
"path"
5+
6+
"github.com/hashicorp/hcl/v2"
7+
"github.com/snyk/driftctl/pkg/iac/config"
8+
"github.com/snyk/driftctl/pkg/iac/terraform/state"
9+
"github.com/snyk/driftctl/pkg/iac/terraform/state/backend"
10+
)
11+
12+
type CloudWorkspacesBlock struct {
13+
Name string `hcl:"name,optional"`
14+
Tags []string `hcl:"tags,optional"`
15+
Remain hcl.Body `hcl:",remain"`
16+
}
17+
18+
type CloudBlock struct {
19+
Organization string `hcl:"organization"`
20+
Workspaces CloudWorkspacesBlock `hcl:"workspaces,block"`
21+
Remain hcl.Body `hcl:",remain"`
22+
}
23+
24+
func (c CloudBlock) SupplierConfig(workspace string) *config.SupplierConfig {
25+
// If a workspace is specified in HCL, use it rather than the current environment
26+
if c.Workspaces.Name != "" {
27+
workspace = c.Workspaces.Name
28+
}
29+
return &config.SupplierConfig{
30+
Key: state.TerraformStateReaderSupplier,
31+
Backend: backend.BackendKeyTFCloud,
32+
Path: path.Join(c.Organization, workspace),
33+
}
34+
}

pkg/terraform/hcl/cloud_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package hcl
2+
3+
import (
4+
"testing"
5+
6+
"github.com/snyk/driftctl/pkg/iac/config"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestCloud_SupplierConfig(t *testing.T) {
11+
cases := []struct {
12+
name string
13+
filename string
14+
want *config.SupplierConfig
15+
wantErr string
16+
}{
17+
{
18+
name: "test with cloud block and default workspace",
19+
filename: "testdata/cloud_block.tf",
20+
want: &config.SupplierConfig{
21+
Key: "tfstate",
22+
Backend: "tfcloud",
23+
Path: "example_corp/default",
24+
},
25+
},
26+
{
27+
name: "test with cloud block and non-default workspace",
28+
filename: "testdata/cloud_block_workspace.tf",
29+
want: &config.SupplierConfig{
30+
Key: "tfstate",
31+
Backend: "tfcloud",
32+
Path: "example_corp/my-workspace",
33+
},
34+
},
35+
}
36+
37+
for _, tt := range cases {
38+
t.Run(tt.name, func(t *testing.T) {
39+
hcl, err := ParseTerraformFromHCL(tt.filename)
40+
if tt.wantErr == "" {
41+
assert.NoError(t, err)
42+
} else {
43+
assert.EqualError(t, err, tt.wantErr)
44+
return
45+
}
46+
47+
if hcl.Cloud == nil {
48+
assert.Nil(t, tt.want)
49+
return
50+
}
51+
52+
assert.Equal(t, *tt.want, *hcl.Cloud.SupplierConfig("default"))
53+
})
54+
}
55+
}

pkg/terraform/hcl/hcl.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ type MainBodyBlock struct {
1818
}
1919

2020
type TerraformBlock struct {
21-
Backend BackendBlock `hcl:"backend,block"`
22-
Remain hcl.Body `hcl:",remain"`
21+
Backend *BackendBlock `hcl:"backend,block"`
22+
Cloud *CloudBlock `hcl:"cloud,block"`
23+
Remain hcl.Body `hcl:",remain"`
2324
}
2425

2526
func ParseTerraformFromHCL(filename string) (*TerraformBlock, error) {

pkg/terraform/hcl/hcl_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package hcl
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestHCL_getCurrentWorkspaceName(t *testing.T) {
10+
cases := []struct {
11+
name string
12+
dir string
13+
want string
14+
}{
15+
{
16+
name: "test with non-default workspace",
17+
dir: "testdata/foo_workspace",
18+
want: "foo",
19+
},
20+
{
21+
name: "test with non-existing directory",
22+
dir: "testdata/noenvfile",
23+
want: "default",
24+
},
25+
}
26+
27+
for _, tt := range cases {
28+
t.Run(tt.name, func(t *testing.T) {
29+
workspace := GetCurrentWorkspaceName(tt.dir)
30+
assert.Equal(t, tt.want, workspace)
31+
})
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
terraform {
2+
cloud {
3+
organization = "example_corp"
4+
hostname = "app.terraform.io" # Optional; defaults to app.terraform.io
5+
6+
workspaces {}
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
terraform {
2+
cloud {
3+
organization = "example_corp"
4+
hostname = "app.terraform.io" # Optional; defaults to app.terraform.io
5+
6+
workspaces {
7+
name = "my-workspace"
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)