github.com/hugorut/terraform@v1.1.3/src/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/hugorut/terraform/src/backend" 9 "github.com/hugorut/terraform/src/command/arguments" 10 "github.com/hugorut/terraform/src/command/clistate" 11 "github.com/hugorut/terraform/src/command/views" 12 "github.com/hugorut/terraform/src/configs" 13 "github.com/hugorut/terraform/src/initwd" 14 "github.com/hugorut/terraform/src/states/statemgr" 15 "github.com/hugorut/terraform/src/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 }