@@ -2,6 +2,9 @@ package remote
22
33import (
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\n got: %s\n want: <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'\n got: %v\n want: %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+ }
0 commit comments