github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/cloud/backend_context_test.go (about)

     1  package cloud
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	tfe "github.com/hashicorp/go-tfe"
     8  	"github.com/cycloidio/terraform/backend"
     9  	"github.com/cycloidio/terraform/command/arguments"
    10  	"github.com/cycloidio/terraform/command/clistate"
    11  	"github.com/cycloidio/terraform/command/views"
    12  	"github.com/cycloidio/terraform/configs"
    13  	"github.com/cycloidio/terraform/initwd"
    14  	"github.com/cycloidio/terraform/states/statemgr"
    15  	"github.com/cycloidio/terraform/terminal"
    16  	"github.com/zclconf/go-cty/cty"
    17  )
    18  
    19  func TestRemoteStoredVariableValue(t *testing.T) {
    20  	tests := map[string]struct {
    21  		Def       *tfe.Variable
    22  		Want      cty.Value
    23  		WantError string
    24  	}{
    25  		"string literal": {
    26  			&tfe.Variable{
    27  				Key:       "test",
    28  				Value:     "foo",
    29  				HCL:       false,
    30  				Sensitive: false,
    31  			},
    32  			cty.StringVal("foo"),
    33  			``,
    34  		},
    35  		"string HCL": {
    36  			&tfe.Variable{
    37  				Key:       "test",
    38  				Value:     `"foo"`,
    39  				HCL:       true,
    40  				Sensitive: false,
    41  			},
    42  			cty.StringVal("foo"),
    43  			``,
    44  		},
    45  		"list HCL": {
    46  			&tfe.Variable{
    47  				Key:       "test",
    48  				Value:     `[]`,
    49  				HCL:       true,
    50  				Sensitive: false,
    51  			},
    52  			cty.EmptyTupleVal,
    53  			``,
    54  		},
    55  		"null HCL": {
    56  			&tfe.Variable{
    57  				Key:       "test",
    58  				Value:     `null`,
    59  				HCL:       true,
    60  				Sensitive: false,
    61  			},
    62  			cty.NullVal(cty.DynamicPseudoType),
    63  			``,
    64  		},
    65  		"literal sensitive": {
    66  			&tfe.Variable{
    67  				Key:       "test",
    68  				HCL:       false,
    69  				Sensitive: true,
    70  			},
    71  			cty.UnknownVal(cty.String),
    72  			``,
    73  		},
    74  		"HCL sensitive": {
    75  			&tfe.Variable{
    76  				Key:       "test",
    77  				HCL:       true,
    78  				Sensitive: true,
    79  			},
    80  			cty.DynamicVal,
    81  			``,
    82  		},
    83  		"HCL computation": {
    84  			// This (stored expressions containing computation) is not a case
    85  			// we intentionally supported, but it became possible for remote
    86  			// operations in Terraform 0.12 (due to Terraform Cloud/Enterprise
    87  			// just writing the HCL verbatim into generated `.tfvars` files).
    88  			// We support it here for consistency, and we continue to support
    89  			// it in both places for backward-compatibility. In practice,
    90  			// there's little reason to do computation in a stored variable
    91  			// value because references are not supported.
    92  			&tfe.Variable{
    93  				Key:       "test",
    94  				Value:     `[for v in ["a"] : v]`,
    95  				HCL:       true,
    96  				Sensitive: false,
    97  			},
    98  			cty.TupleVal([]cty.Value{cty.StringVal("a")}),
    99  			``,
   100  		},
   101  		"HCL syntax error": {
   102  			&tfe.Variable{
   103  				Key:       "test",
   104  				Value:     `[`,
   105  				HCL:       true,
   106  				Sensitive: false,
   107  			},
   108  			cty.DynamicVal,
   109  			`Invalid expression for var.test: The value of variable "test" is marked in the remote workspace as being specified in HCL syntax, but the given value is not valid HCL. Stored variable values must be valid literal expressions and may not contain references to other variables or calls to functions.`,
   110  		},
   111  		"HCL with references": {
   112  			&tfe.Variable{
   113  				Key:       "test",
   114  				Value:     `foo.bar`,
   115  				HCL:       true,
   116  				Sensitive: false,
   117  			},
   118  			cty.DynamicVal,
   119  			`Invalid expression for var.test: The value of variable "test" is marked in the remote workspace as being specified in HCL syntax, but the given value is not valid HCL. Stored variable values must be valid literal expressions and may not contain references to other variables or calls to functions.`,
   120  		},
   121  	}
   122  
   123  	for name, test := range tests {
   124  		t.Run(name, func(t *testing.T) {
   125  			v := &remoteStoredVariableValue{
   126  				definition: test.Def,
   127  			}
   128  			// This ParseVariableValue implementation ignores the parsing mode,
   129  			// so we'll just always parse literal here. (The parsing mode is
   130  			// selected by the remote server, not by our local configuration.)
   131  			gotIV, diags := v.ParseVariableValue(configs.VariableParseLiteral)
   132  			if test.WantError != "" {
   133  				if !diags.HasErrors() {
   134  					t.Fatalf("missing expected error\ngot:  <no error>\nwant: %s", test.WantError)
   135  				}
   136  				errStr := diags.Err().Error()
   137  				if errStr != test.WantError {
   138  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", errStr, test.WantError)
   139  				}
   140  			} else {
   141  				if diags.HasErrors() {
   142  					t.Fatalf("unexpected error\ngot:  %s\nwant: <no error>", diags.Err().Error())
   143  				}
   144  				got := gotIV.Value
   145  				if !test.Want.RawEquals(got) {
   146  					t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   147  				}
   148  			}
   149  		})
   150  	}
   151  }
   152  
   153  func TestRemoteContextWithVars(t *testing.T) {
   154  	catTerraform := tfe.CategoryTerraform
   155  	catEnv := tfe.CategoryEnv
   156  
   157  	tests := map[string]struct {
   158  		Opts      *tfe.VariableCreateOptions
   159  		WantError string
   160  	}{
   161  		"Terraform variable": {
   162  			&tfe.VariableCreateOptions{
   163  				Category: &catTerraform,
   164  			},
   165  			`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.`,
   166  		},
   167  		"environment variable": {
   168  			&tfe.VariableCreateOptions{
   169  				Category: &catEnv,
   170  			},
   171  			``,
   172  		},
   173  	}
   174  
   175  	for name, test := range tests {
   176  		t.Run(name, func(t *testing.T) {
   177  			configDir := "./testdata/empty"
   178  
   179  			b, bCleanup := testBackendWithName(t)
   180  			defer bCleanup()
   181  
   182  			_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
   183  			defer configCleanup()
   184  
   185  			workspaceID, err := b.getRemoteWorkspaceID(context.Background(), testBackendSingleWorkspaceName)
   186  			if err != nil {
   187  				t.Fatal(err)
   188  			}
   189  
   190  			streams, _ := terminal.StreamsForTesting(t)
   191  			view := views.NewStateLocker(arguments.ViewHuman, views.NewView(streams))
   192  
   193  			op := &backend.Operation{
   194  				ConfigDir:    configDir,
   195  				ConfigLoader: configLoader,
   196  				StateLocker:  clistate.NewLocker(0, view),
   197  				Workspace:    testBackendSingleWorkspaceName,
   198  			}
   199  
   200  			v := test.Opts
   201  			if v.Key == nil {
   202  				key := "key"
   203  				v.Key = &key
   204  			}
   205  			b.client.Variables.Create(context.TODO(), workspaceID, *v)
   206  
   207  			_, _, diags := b.LocalRun(op)
   208  
   209  			if test.WantError != "" {
   210  				if !diags.HasErrors() {
   211  					t.Fatalf("missing expected error\ngot:  <no error>\nwant: %s", test.WantError)
   212  				}
   213  				errStr := diags.Err().Error()
   214  				if errStr != test.WantError {
   215  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", errStr, test.WantError)
   216  				}
   217  				// When Context() returns an error, it should unlock the state,
   218  				// so re-locking it is expected to succeed.
   219  				stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
   220  				if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   221  					t.Fatalf("unexpected error locking state: %s", err.Error())
   222  				}
   223  			} else {
   224  				if diags.HasErrors() {
   225  					t.Fatalf("unexpected error\ngot:  %s\nwant: <no error>", diags.Err().Error())
   226  				}
   227  				// When Context() succeeds, this should fail w/ "workspace already locked"
   228  				stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
   229  				if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err == nil {
   230  					t.Fatal("unexpected success locking state after Context")
   231  				}
   232  			}
   233  		})
   234  	}
   235  }