Skip to content

Commit 6149422

Browse files
committed
fix: local variables should not be overridden by remote variables during terraform import
1 parent 7aeaec9 commit 6149422

File tree

3 files changed

+246
-2
lines changed

3 files changed

+246
-2
lines changed

internal/backend/remote/backend_context.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,10 @@ func (b *Remote) LocalRun(op *backend.Operation) (*backend.LocalRun, statemgr.Fu
111111
}
112112
for _, v := range tfeVariables.Items {
113113
if v.Category == tfe.CategoryTerraform {
114-
op.Variables[v.Key] = &remoteStoredVariableValue{
115-
definition: v,
114+
if _, ok := op.Variables[v.Key]; !ok {
115+
op.Variables[v.Key] = &remoteStoredVariableValue{
116+
definition: v,
117+
}
116118
}
117119
}
118120
}

internal/backend/remote/backend_context_test.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package remote
22

33
import (
44
"context"
5+
"github.com/hashicorp/terraform/internal/terraform"
6+
"github.com/hashicorp/terraform/internal/tfdiags"
7+
"reflect"
58
"testing"
69

710
tfe "github.com/hashicorp/go-tfe"
@@ -233,3 +236,234 @@ func TestRemoteContextWithVars(t *testing.T) {
233236
})
234237
}
235238
}
239+
240+
func TestRemoteVariablesDoNotOverride(t *testing.T) {
241+
catTerraform := tfe.CategoryTerraform
242+
243+
varName1 := "key1"
244+
varName2 := "key2"
245+
varName3 := "key3"
246+
247+
varValue1 := "value1"
248+
varValue2 := "value2"
249+
varValue3 := "value3"
250+
251+
tests := map[string]struct {
252+
localVariables map[string]backend.UnparsedVariableValue
253+
remoteVariables []*tfe.VariableCreateOptions
254+
expectedVariables terraform.InputValues
255+
}{
256+
"no local variables": {
257+
map[string]backend.UnparsedVariableValue{},
258+
[]*tfe.VariableCreateOptions{
259+
{
260+
Key: &varName1,
261+
Value: &varValue1,
262+
Category: &catTerraform,
263+
},
264+
{
265+
Key: &varName2,
266+
Value: &varValue2,
267+
Category: &catTerraform,
268+
},
269+
{
270+
Key: &varName3,
271+
Value: &varValue3,
272+
Category: &catTerraform,
273+
},
274+
},
275+
terraform.InputValues{
276+
varName1: &terraform.InputValue{
277+
Value: cty.StringVal(varValue1),
278+
SourceType: terraform.ValueFromInput,
279+
SourceRange: tfdiags.SourceRange{
280+
Filename: "",
281+
Start: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
282+
End: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
283+
},
284+
},
285+
varName2: &terraform.InputValue{
286+
Value: cty.StringVal(varValue2),
287+
SourceType: terraform.ValueFromInput,
288+
SourceRange: tfdiags.SourceRange{
289+
Filename: "",
290+
Start: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
291+
End: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
292+
},
293+
},
294+
varName3: &terraform.InputValue{
295+
Value: cty.StringVal(varValue3),
296+
SourceType: terraform.ValueFromInput,
297+
SourceRange: tfdiags.SourceRange{
298+
Filename: "",
299+
Start: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
300+
End: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
301+
},
302+
},
303+
},
304+
},
305+
"single conflicting local variable": {
306+
map[string]backend.UnparsedVariableValue{
307+
varName3: testUnparsedVariableValue(varValue3),
308+
},
309+
[]*tfe.VariableCreateOptions{
310+
{
311+
Key: &varName1,
312+
Value: &varValue1,
313+
Category: &catTerraform,
314+
}, {
315+
Key: &varName2,
316+
Value: &varValue2,
317+
Category: &catTerraform,
318+
}, {
319+
Key: &varName3,
320+
Value: &varValue3,
321+
Category: &catTerraform,
322+
},
323+
},
324+
terraform.InputValues{
325+
varName1: &terraform.InputValue{
326+
Value: cty.StringVal(varValue1),
327+
SourceType: terraform.ValueFromInput,
328+
SourceRange: tfdiags.SourceRange{
329+
Filename: "",
330+
Start: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
331+
End: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
332+
},
333+
},
334+
varName2: &terraform.InputValue{
335+
Value: cty.StringVal(varValue2),
336+
SourceType: terraform.ValueFromInput,
337+
SourceRange: tfdiags.SourceRange{
338+
Filename: "",
339+
Start: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
340+
End: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
341+
},
342+
},
343+
varName3: &terraform.InputValue{
344+
Value: cty.StringVal(varValue3),
345+
SourceType: terraform.ValueFromNamedFile,
346+
SourceRange: tfdiags.SourceRange{
347+
Filename: "fake.tfvars",
348+
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
349+
End: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
350+
},
351+
},
352+
},
353+
},
354+
"no-conflicting local variable": {
355+
map[string]backend.UnparsedVariableValue{
356+
varName3: testUnparsedVariableValue(varValue3),
357+
},
358+
[]*tfe.VariableCreateOptions{
359+
{
360+
Key: &varName1,
361+
Value: &varValue1,
362+
Category: &catTerraform,
363+
}, {
364+
Key: &varName2,
365+
Value: &varValue2,
366+
Category: &catTerraform,
367+
},
368+
},
369+
terraform.InputValues{
370+
varName1: &terraform.InputValue{
371+
Value: cty.StringVal(varValue1),
372+
SourceType: terraform.ValueFromInput,
373+
SourceRange: tfdiags.SourceRange{
374+
Filename: "",
375+
Start: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
376+
End: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
377+
},
378+
},
379+
varName2: &terraform.InputValue{
380+
Value: cty.StringVal(varValue2),
381+
SourceType: terraform.ValueFromInput,
382+
SourceRange: tfdiags.SourceRange{
383+
Filename: "",
384+
Start: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
385+
End: tfdiags.SourcePos{Line: 0, Column: 0, Byte: 0},
386+
},
387+
},
388+
varName3: &terraform.InputValue{
389+
Value: cty.StringVal(varValue3),
390+
SourceType: terraform.ValueFromNamedFile,
391+
SourceRange: tfdiags.SourceRange{
392+
Filename: "fake.tfvars",
393+
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
394+
End: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
395+
},
396+
},
397+
},
398+
},
399+
}
400+
401+
for name, test := range tests {
402+
t.Run(name, func(t *testing.T) {
403+
configDir := "./testdata/variables"
404+
405+
b, bCleanup := testBackendDefault(t)
406+
defer bCleanup()
407+
408+
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
409+
defer configCleanup()
410+
411+
workspaceID, err := b.getRemoteWorkspaceID(context.Background(), backend.DefaultStateName)
412+
if err != nil {
413+
t.Fatal(err)
414+
}
415+
416+
streams, _ := terminal.StreamsForTesting(t)
417+
view := views.NewStateLocker(arguments.ViewHuman, views.NewView(streams))
418+
419+
op := &backend.Operation{
420+
ConfigDir: configDir,
421+
ConfigLoader: configLoader,
422+
StateLocker: clistate.NewLocker(0, view),
423+
Workspace: backend.DefaultStateName,
424+
Variables: test.localVariables,
425+
}
426+
427+
for _, v := range test.remoteVariables {
428+
b.client.Variables.Create(context.TODO(), workspaceID, *v)
429+
}
430+
431+
lr, _, diags := b.LocalRun(op)
432+
433+
if diags.HasErrors() {
434+
t.Fatalf("unexpected error\ngot: %s\nwant: <no error>", diags.Err().Error())
435+
}
436+
// When Context() succeeds, this should fail w/ "workspace already locked"
437+
stateMgr, _ := b.StateMgr(backend.DefaultStateName)
438+
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err == nil {
439+
t.Fatal("unexpected success locking state after Context")
440+
}
441+
442+
actual := lr.PlanOpts.SetVariables
443+
expected := test.expectedVariables
444+
445+
for expectedKey := range expected {
446+
actualValue := actual[expectedKey]
447+
expectedValue := expected[expectedKey]
448+
449+
if !reflect.DeepEqual(*actualValue, *expectedValue) {
450+
t.Fatalf("unexpected variable '%s'\ngot: %v\nwant: %v", expectedKey, actualValue, expectedValue)
451+
}
452+
}
453+
})
454+
}
455+
}
456+
457+
type testUnparsedVariableValue string
458+
459+
func (v testUnparsedVariableValue) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
460+
return &terraform.InputValue{
461+
Value: cty.StringVal(string(v)),
462+
SourceType: terraform.ValueFromNamedFile,
463+
SourceRange: tfdiags.SourceRange{
464+
Filename: "fake.tfvars",
465+
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
466+
End: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
467+
},
468+
}, nil
469+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
variable "key1" {
2+
}
3+
4+
variable "key2" {
5+
}
6+
7+
variable "key3" {
8+
}

0 commit comments

Comments
 (0)