From af77d1d22c696a64344cf2c513b94e595128085e Mon Sep 17 00:00:00 2001 From: Robert Tillery <1569876+beekus@users.noreply.github.com> Date: Wed, 13 Nov 2019 11:34:09 -0500 Subject: [PATCH] backend/remote: Filter environment variables when loading context (#23358) * backend/remote: Filter environment variables when loading context Following up on #23122, the remote system (Terraform Cloud or Enterprise) serves environment and Terraform variables using a single type of object. We only should load Terraform variables into the Terraform context. Fixes https://github.com/hashicorp/terraform/issues/23283. --- backend/remote/backend_context.go | 6 ++- backend/remote/backend_context_test.go | 71 ++++++++++++++++++++++++++ backend/remote/backend_mock.go | 59 +++++++++++++++++++++ backend/remote/testing.go | 3 +- 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/backend/remote/backend_context.go b/backend/remote/backend_context.go index 49286f977..9461cb54a 100644 --- a/backend/remote/backend_context.go +++ b/backend/remote/backend_context.go @@ -116,8 +116,10 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu op.Variables = make(map[string]backend.UnparsedVariableValue) } for _, v := range tfeVariables.Items { - op.Variables[v.Key] = &remoteStoredVariableValue{ - definition: v, + if v.Category == tfe.CategoryTerraform { + op.Variables[v.Key] = &remoteStoredVariableValue{ + definition: v, + } } } } diff --git a/backend/remote/backend_context_test.go b/backend/remote/backend_context_test.go index d178e8c1c..03de0f427 100644 --- a/backend/remote/backend_context_test.go +++ b/backend/remote/backend_context_test.go @@ -4,7 +4,9 @@ import ( "testing" tfe "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/configs" + "github.com/hashicorp/terraform/internal/initwd" "github.com/zclconf/go-cty/cty" ) @@ -141,3 +143,72 @@ func TestRemoteStoredVariableValue(t *testing.T) { }) } } + +func TestRemoteContextWithVars(t *testing.T) { + catTerraform := tfe.CategoryTerraform + catEnv := tfe.CategoryEnv + + tests := map[string]struct { + Opts *tfe.VariableCreateOptions + WantError string + }{ + "Terraform variable": { + &tfe.VariableCreateOptions{ + Category: &catTerraform, + }, + `Value for undeclared variable: A variable named "key" was assigned a value, but the root module does not declare a variable of that name. To use this value, add a "variable" block to the configuration.`, + }, + "environment variable": { + &tfe.VariableCreateOptions{ + Category: &catEnv, + }, + ``, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + configDir := "./testdata/empty" + + b, bCleanup := testBackendDefault(t) + defer bCleanup() + + _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) + defer configCleanup() + + op := &backend.Operation{ + ConfigDir: configDir, + ConfigLoader: configLoader, + Workspace: backend.DefaultStateName, + } + + v := test.Opts + if v.Key == nil { + key := "key" + v.Key = &key + } + if v.Workspace == nil { + v.Workspace = &tfe.Workspace{ + Name: b.workspace, + } + } + b.client.Variables.Create(nil, *v) + + _, _, diags := b.Context(op) + + if test.WantError != "" { + if !diags.HasErrors() { + t.Fatalf("missing expected error\ngot: \nwant: %s", test.WantError) + } + errStr := diags.Err().Error() + if errStr != test.WantError { + t.Fatalf("wrong error\ngot: %s\nwant: %s", errStr, test.WantError) + } + } else { + if diags.HasErrors() { + t.Fatalf("unexpected error\ngot: %s\nwant: ", diags.Err().Error()) + } + } + }) + } +} diff --git a/backend/remote/backend_mock.go b/backend/remote/backend_mock.go index d337d976e..42b51fd26 100644 --- a/backend/remote/backend_mock.go +++ b/backend/remote/backend_mock.go @@ -27,6 +27,7 @@ type mockClient struct { PolicyChecks *mockPolicyChecks Runs *mockRuns StateVersions *mockStateVersions + Variables *mockVariables Workspaces *mockWorkspaces } @@ -40,6 +41,7 @@ func newMockClient() *mockClient { c.PolicyChecks = newMockPolicyChecks(c) c.Runs = newMockRuns(c) c.StateVersions = newMockStateVersions(c) + c.Variables = newMockVariables(c) c.Workspaces = newMockWorkspaces(c) return c } @@ -945,6 +947,63 @@ func (m *mockStateVersions) Download(ctx context.Context, url string) ([]byte, e return state, nil } +type mockVariables struct { + client *mockClient + workspaces map[string]*tfe.VariableList +} + +func newMockVariables(client *mockClient) *mockVariables { + return &mockVariables{ + client: client, + workspaces: make(map[string]*tfe.VariableList), + } +} + +func (m *mockVariables) List(ctx context.Context, options tfe.VariableListOptions) (*tfe.VariableList, error) { + vl := m.workspaces[*options.Workspace] + return vl, nil +} + +func (m *mockVariables) Create(ctx context.Context, options tfe.VariableCreateOptions) (*tfe.Variable, error) { + v := &tfe.Variable{ + ID: generateID("var-"), + Key: *options.Key, + Category: *options.Category, + } + if options.Value != nil { + v.Value = *options.Value + } + if options.HCL != nil { + v.HCL = *options.HCL + } + if options.Sensitive != nil { + v.Sensitive = *options.Sensitive + } + + workspace := options.Workspace.Name + + if m.workspaces[workspace] == nil { + m.workspaces[workspace] = &tfe.VariableList{} + } + + vl := m.workspaces[workspace] + vl.Items = append(vl.Items, v) + + return v, nil +} + +func (m *mockVariables) Read(ctx context.Context, variableID string) (*tfe.Variable, error) { + panic("not implemented") +} + +func (m *mockVariables) Update(ctx context.Context, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) { + panic("not implemented") +} + +func (m *mockVariables) Delete(ctx context.Context, variableID string) error { + panic("not implemented") +} + type mockWorkspaces struct { client *mockClient workspaceIDs map[string]*tfe.Workspace diff --git a/backend/remote/testing.go b/backend/remote/testing.go index d7706fe19..e506f28f9 100644 --- a/backend/remote/testing.go +++ b/backend/remote/testing.go @@ -10,7 +10,7 @@ import ( "testing" tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform-svchost" + svchost "github.com/hashicorp/terraform-svchost" "github.com/hashicorp/terraform-svchost/auth" "github.com/hashicorp/terraform-svchost/disco" "github.com/hashicorp/terraform/backend" @@ -123,6 +123,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) { b.client.PolicyChecks = mc.PolicyChecks b.client.Runs = mc.Runs b.client.StateVersions = mc.StateVersions + b.client.Variables = mc.Variables b.client.Workspaces = mc.Workspaces b.ShowDiagnostics = func(vals ...interface{}) {