github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/builtin/providers/terraform/data_source_state_test.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "testing" 7 8 "github.com/apparentlymart/go-dump/dump" 9 "github.com/muratcelep/terraform/not-internal/backend" 10 "github.com/muratcelep/terraform/not-internal/configs/configschema" 11 "github.com/muratcelep/terraform/not-internal/states/statemgr" 12 "github.com/muratcelep/terraform/not-internal/tfdiags" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 func TestResource(t *testing.T) { 17 if err := dataSourceRemoteStateGetSchema().Block.InternalValidate(); err != nil { 18 t.Fatalf("err: %s", err) 19 } 20 } 21 22 func TestState_basic(t *testing.T) { 23 var tests = map[string]struct { 24 Config cty.Value 25 Want cty.Value 26 Err bool 27 }{ 28 "basic": { 29 cty.ObjectVal(map[string]cty.Value{ 30 "backend": cty.StringVal("local"), 31 "config": cty.ObjectVal(map[string]cty.Value{ 32 "path": cty.StringVal("./testdata/basic.tfstate"), 33 }), 34 }), 35 cty.ObjectVal(map[string]cty.Value{ 36 "backend": cty.StringVal("local"), 37 "config": cty.ObjectVal(map[string]cty.Value{ 38 "path": cty.StringVal("./testdata/basic.tfstate"), 39 }), 40 "outputs": cty.ObjectVal(map[string]cty.Value{ 41 "foo": cty.StringVal("bar"), 42 }), 43 "defaults": cty.NullVal(cty.DynamicPseudoType), 44 "workspace": cty.NullVal(cty.String), 45 }), 46 false, 47 }, 48 "workspace": { 49 cty.ObjectVal(map[string]cty.Value{ 50 "backend": cty.StringVal("local"), 51 "workspace": cty.StringVal(backend.DefaultStateName), 52 "config": cty.ObjectVal(map[string]cty.Value{ 53 "path": cty.StringVal("./testdata/basic.tfstate"), 54 }), 55 }), 56 cty.ObjectVal(map[string]cty.Value{ 57 "backend": cty.StringVal("local"), 58 "workspace": cty.StringVal(backend.DefaultStateName), 59 "config": cty.ObjectVal(map[string]cty.Value{ 60 "path": cty.StringVal("./testdata/basic.tfstate"), 61 }), 62 "outputs": cty.ObjectVal(map[string]cty.Value{ 63 "foo": cty.StringVal("bar"), 64 }), 65 "defaults": cty.NullVal(cty.DynamicPseudoType), 66 }), 67 false, 68 }, 69 "_local": { 70 cty.ObjectVal(map[string]cty.Value{ 71 "backend": cty.StringVal("_local"), 72 "config": cty.ObjectVal(map[string]cty.Value{ 73 "path": cty.StringVal("./testdata/basic.tfstate"), 74 }), 75 }), 76 cty.ObjectVal(map[string]cty.Value{ 77 "backend": cty.StringVal("_local"), 78 "config": cty.ObjectVal(map[string]cty.Value{ 79 "path": cty.StringVal("./testdata/basic.tfstate"), 80 }), 81 "outputs": cty.ObjectVal(map[string]cty.Value{ 82 "foo": cty.StringVal("bar"), 83 }), 84 "defaults": cty.NullVal(cty.DynamicPseudoType), 85 "workspace": cty.NullVal(cty.String), 86 }), 87 false, 88 }, 89 "complex outputs": { 90 cty.ObjectVal(map[string]cty.Value{ 91 "backend": cty.StringVal("local"), 92 "config": cty.ObjectVal(map[string]cty.Value{ 93 "path": cty.StringVal("./testdata/complex_outputs.tfstate"), 94 }), 95 }), 96 cty.ObjectVal(map[string]cty.Value{ 97 "backend": cty.StringVal("local"), 98 "config": cty.ObjectVal(map[string]cty.Value{ 99 "path": cty.StringVal("./testdata/complex_outputs.tfstate"), 100 }), 101 "outputs": cty.ObjectVal(map[string]cty.Value{ 102 "computed_map": cty.MapVal(map[string]cty.Value{ 103 "key1": cty.StringVal("value1"), 104 }), 105 "computed_set": cty.ListVal([]cty.Value{ 106 cty.StringVal("setval1"), 107 cty.StringVal("setval2"), 108 }), 109 "map": cty.MapVal(map[string]cty.Value{ 110 "key": cty.StringVal("test"), 111 "test": cty.StringVal("test"), 112 }), 113 "set": cty.ListVal([]cty.Value{ 114 cty.StringVal("test1"), 115 cty.StringVal("test2"), 116 }), 117 }), 118 "defaults": cty.NullVal(cty.DynamicPseudoType), 119 "workspace": cty.NullVal(cty.String), 120 }), 121 false, 122 }, 123 "null outputs": { 124 cty.ObjectVal(map[string]cty.Value{ 125 "backend": cty.StringVal("local"), 126 "config": cty.ObjectVal(map[string]cty.Value{ 127 "path": cty.StringVal("./testdata/null_outputs.tfstate"), 128 }), 129 }), 130 cty.ObjectVal(map[string]cty.Value{ 131 "backend": cty.StringVal("local"), 132 "config": cty.ObjectVal(map[string]cty.Value{ 133 "path": cty.StringVal("./testdata/null_outputs.tfstate"), 134 }), 135 "outputs": cty.ObjectVal(map[string]cty.Value{ 136 "map": cty.NullVal(cty.Map(cty.String)), 137 "list": cty.NullVal(cty.List(cty.String)), 138 }), 139 "defaults": cty.NullVal(cty.DynamicPseudoType), 140 "workspace": cty.NullVal(cty.String), 141 }), 142 false, 143 }, 144 "defaults": { 145 cty.ObjectVal(map[string]cty.Value{ 146 "backend": cty.StringVal("local"), 147 "config": cty.ObjectVal(map[string]cty.Value{ 148 "path": cty.StringVal("./testdata/empty.tfstate"), 149 }), 150 "defaults": cty.ObjectVal(map[string]cty.Value{ 151 "foo": cty.StringVal("bar"), 152 }), 153 }), 154 cty.ObjectVal(map[string]cty.Value{ 155 "backend": cty.StringVal("local"), 156 "config": cty.ObjectVal(map[string]cty.Value{ 157 "path": cty.StringVal("./testdata/empty.tfstate"), 158 }), 159 "defaults": cty.ObjectVal(map[string]cty.Value{ 160 "foo": cty.StringVal("bar"), 161 }), 162 "outputs": cty.ObjectVal(map[string]cty.Value{ 163 "foo": cty.StringVal("bar"), 164 }), 165 "workspace": cty.NullVal(cty.String), 166 }), 167 false, 168 }, 169 "missing": { 170 cty.ObjectVal(map[string]cty.Value{ 171 "backend": cty.StringVal("local"), 172 "config": cty.ObjectVal(map[string]cty.Value{ 173 "path": cty.StringVal("./testdata/missing.tfstate"), // intentionally not present on disk 174 }), 175 }), 176 cty.ObjectVal(map[string]cty.Value{ 177 "backend": cty.StringVal("local"), 178 "config": cty.ObjectVal(map[string]cty.Value{ 179 "path": cty.StringVal("./testdata/missing.tfstate"), 180 }), 181 "defaults": cty.NullVal(cty.DynamicPseudoType), 182 "outputs": cty.EmptyObjectVal, 183 "workspace": cty.NullVal(cty.String), 184 }), 185 true, 186 }, 187 "wrong type for config": { 188 cty.ObjectVal(map[string]cty.Value{ 189 "backend": cty.StringVal("local"), 190 "config": cty.StringVal("nope"), 191 }), 192 cty.NilVal, 193 true, 194 }, 195 "wrong type for config with unknown backend": { 196 cty.ObjectVal(map[string]cty.Value{ 197 "backend": cty.UnknownVal(cty.String), 198 "config": cty.StringVal("nope"), 199 }), 200 cty.NilVal, 201 true, 202 }, 203 "wrong type for config with unknown config": { 204 cty.ObjectVal(map[string]cty.Value{ 205 "backend": cty.StringVal("local"), 206 "config": cty.UnknownVal(cty.String), 207 }), 208 cty.NilVal, 209 true, 210 }, 211 "wrong type for defaults": { 212 cty.ObjectVal(map[string]cty.Value{ 213 "backend": cty.StringVal("local"), 214 "config": cty.ObjectVal(map[string]cty.Value{ 215 "path": cty.StringVal("./testdata/basic.tfstate"), 216 }), 217 "defaults": cty.StringVal("nope"), 218 }), 219 cty.NilVal, 220 true, 221 }, 222 "config as map": { 223 cty.ObjectVal(map[string]cty.Value{ 224 "backend": cty.StringVal("local"), 225 "config": cty.MapVal(map[string]cty.Value{ 226 "path": cty.StringVal("./testdata/empty.tfstate"), 227 }), 228 }), 229 cty.ObjectVal(map[string]cty.Value{ 230 "backend": cty.StringVal("local"), 231 "config": cty.MapVal(map[string]cty.Value{ 232 "path": cty.StringVal("./testdata/empty.tfstate"), 233 }), 234 "defaults": cty.NullVal(cty.DynamicPseudoType), 235 "outputs": cty.EmptyObjectVal, 236 "workspace": cty.NullVal(cty.String), 237 }), 238 false, 239 }, 240 "defaults as map": { 241 cty.ObjectVal(map[string]cty.Value{ 242 "backend": cty.StringVal("local"), 243 "config": cty.ObjectVal(map[string]cty.Value{ 244 "path": cty.StringVal("./testdata/basic.tfstate"), 245 }), 246 "defaults": cty.MapValEmpty(cty.String), 247 }), 248 cty.ObjectVal(map[string]cty.Value{ 249 "backend": cty.StringVal("local"), 250 "config": cty.ObjectVal(map[string]cty.Value{ 251 "path": cty.StringVal("./testdata/basic.tfstate"), 252 }), 253 "defaults": cty.MapValEmpty(cty.String), 254 "outputs": cty.ObjectVal(map[string]cty.Value{ 255 "foo": cty.StringVal("bar"), 256 }), 257 "workspace": cty.NullVal(cty.String), 258 }), 259 false, 260 }, 261 "nonexistent backend": { 262 cty.ObjectVal(map[string]cty.Value{ 263 "backend": cty.StringVal("nonexistent"), 264 "config": cty.ObjectVal(map[string]cty.Value{ 265 "path": cty.StringVal("./testdata/basic.tfstate"), 266 }), 267 }), 268 cty.NilVal, 269 true, 270 }, 271 "null config": { 272 cty.ObjectVal(map[string]cty.Value{ 273 "backend": cty.StringVal("local"), 274 "config": cty.NullVal(cty.DynamicPseudoType), 275 }), 276 cty.NilVal, 277 true, 278 }, 279 } 280 for name, test := range tests { 281 t.Run(name, func(t *testing.T) { 282 schema := dataSourceRemoteStateGetSchema().Block 283 config, err := schema.CoerceValue(test.Config) 284 if err != nil { 285 t.Fatalf("unexpected error: %s", err) 286 } 287 288 diags := dataSourceRemoteStateValidate(config) 289 290 var got cty.Value 291 if !diags.HasErrors() && config.IsWhollyKnown() { 292 var moreDiags tfdiags.Diagnostics 293 got, moreDiags = dataSourceRemoteStateRead(config) 294 diags = diags.Append(moreDiags) 295 } 296 297 if test.Err { 298 if !diags.HasErrors() { 299 t.Fatal("succeeded; want error") 300 } 301 } else if diags.HasErrors() { 302 t.Fatalf("unexpected errors: %s", diags.Err()) 303 } 304 305 if test.Want != cty.NilVal && !test.Want.RawEquals(got) { 306 t.Errorf("wrong result\nconfig: %sgot: %swant: %s", dump.Value(config), dump.Value(got), dump.Value(test.Want)) 307 } 308 }) 309 } 310 } 311 312 func TestState_validation(t *testing.T) { 313 // The main test TestState_basic covers both validation and reading of 314 // state snapshots, so this additional test is here only to verify that 315 // the validation step in isolation does not attempt to configure 316 // the backend. 317 overrideBackendFactories = map[string]backend.InitFn{ 318 "failsconfigure": func() backend.Backend { 319 return backendFailsConfigure{} 320 }, 321 } 322 defer func() { 323 // undo our overrides so we won't affect other tests 324 overrideBackendFactories = nil 325 }() 326 327 schema := dataSourceRemoteStateGetSchema().Block 328 config, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ 329 "backend": cty.StringVal("failsconfigure"), 330 "config": cty.EmptyObjectVal, 331 })) 332 if err != nil { 333 t.Fatalf("unexpected error: %s", err) 334 } 335 336 diags := dataSourceRemoteStateValidate(config) 337 if diags.HasErrors() { 338 t.Fatalf("unexpected errors\n%s", diags.Err().Error()) 339 } 340 } 341 342 type backendFailsConfigure struct{} 343 344 func (b backendFailsConfigure) ConfigSchema() *configschema.Block { 345 log.Printf("[TRACE] backendFailsConfigure.ConfigSchema") 346 return &configschema.Block{} // intentionally empty configuration schema 347 } 348 349 func (b backendFailsConfigure) PrepareConfig(given cty.Value) (cty.Value, tfdiags.Diagnostics) { 350 // No special actions to take here 351 return given, nil 352 } 353 354 func (b backendFailsConfigure) Configure(config cty.Value) tfdiags.Diagnostics { 355 log.Printf("[TRACE] backendFailsConfigure.Configure(%#v)", config) 356 var diags tfdiags.Diagnostics 357 diags = diags.Append(fmt.Errorf("Configure should never be called")) 358 return diags 359 } 360 361 func (b backendFailsConfigure) StateMgr(workspace string) (statemgr.Full, error) { 362 return nil, fmt.Errorf("StateMgr not implemented") 363 } 364 365 func (b backendFailsConfigure) DeleteWorkspace(name string) error { 366 return fmt.Errorf("DeleteWorkspace not implemented") 367 } 368 369 func (b backendFailsConfigure) Workspaces() ([]string, error) { 370 return nil, fmt.Errorf("Workspaces not implemented") 371 }