github.com/hugorut/terraform@v1.1.3/src/backend/local/backend_refresh_test.go (about) 1 package local 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/hugorut/terraform/src/addrs" 10 "github.com/hugorut/terraform/src/backend" 11 "github.com/hugorut/terraform/src/command/arguments" 12 "github.com/hugorut/terraform/src/command/clistate" 13 "github.com/hugorut/terraform/src/command/views" 14 "github.com/hugorut/terraform/src/configs/configschema" 15 "github.com/hugorut/terraform/src/depsfile" 16 "github.com/hugorut/terraform/src/initwd" 17 "github.com/hugorut/terraform/src/providers" 18 "github.com/hugorut/terraform/src/states" 19 "github.com/hugorut/terraform/src/terminal" 20 "github.com/hugorut/terraform/src/terraform" 21 22 "github.com/zclconf/go-cty/cty" 23 ) 24 25 func TestLocal_refresh(t *testing.T) { 26 b := TestLocal(t) 27 28 p := TestLocalProvider(t, b, "test", refreshFixtureSchema()) 29 testStateFile(t, b.StatePath, testRefreshState()) 30 31 p.ReadResourceFn = nil 32 p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{ 33 "id": cty.StringVal("yes"), 34 })} 35 36 op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh") 37 defer configCleanup() 38 defer done(t) 39 40 run, err := b.Operation(context.Background(), op) 41 if err != nil { 42 t.Fatalf("bad: %s", err) 43 } 44 <-run.Done() 45 46 if !p.ReadResourceCalled { 47 t.Fatal("ReadResource should be called") 48 } 49 50 checkState(t, b.StateOutPath, ` 51 test_instance.foo: 52 ID = yes 53 provider = provider["registry.terraform.io/hashicorp/test"] 54 `) 55 56 // the backend should be unlocked after a run 57 assertBackendStateUnlocked(t, b) 58 } 59 60 func TestLocal_refreshInput(t *testing.T) { 61 b := TestLocal(t) 62 63 schema := &terraform.ProviderSchema{ 64 Provider: &configschema.Block{ 65 Attributes: map[string]*configschema.Attribute{ 66 "value": {Type: cty.String, Optional: true}, 67 }, 68 }, 69 ResourceTypes: map[string]*configschema.Block{ 70 "test_instance": { 71 Attributes: map[string]*configschema.Attribute{ 72 "id": {Type: cty.String, Computed: true}, 73 "foo": {Type: cty.String, Optional: true}, 74 "ami": {Type: cty.String, Optional: true}, 75 }, 76 }, 77 }, 78 } 79 80 p := TestLocalProvider(t, b, "test", schema) 81 testStateFile(t, b.StatePath, testRefreshState()) 82 83 p.ReadResourceFn = nil 84 p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{ 85 "id": cty.StringVal("yes"), 86 })} 87 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { 88 val := req.Config.GetAttr("value") 89 if val.IsNull() || val.AsString() != "bar" { 90 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("incorrect value %#v", val)) 91 } 92 93 return 94 } 95 96 // Enable input asking since it is normally disabled by default 97 b.OpInput = true 98 b.ContextOpts.UIInput = &terraform.MockUIInput{InputReturnString: "bar"} 99 100 op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh-var-unset") 101 defer configCleanup() 102 defer done(t) 103 op.UIIn = b.ContextOpts.UIInput 104 105 run, err := b.Operation(context.Background(), op) 106 if err != nil { 107 t.Fatalf("bad: %s", err) 108 } 109 <-run.Done() 110 111 if !p.ReadResourceCalled { 112 t.Fatal("ReadResource should be called") 113 } 114 115 checkState(t, b.StateOutPath, ` 116 test_instance.foo: 117 ID = yes 118 provider = provider["registry.terraform.io/hashicorp/test"] 119 `) 120 } 121 122 func TestLocal_refreshValidate(t *testing.T) { 123 b := TestLocal(t) 124 p := TestLocalProvider(t, b, "test", refreshFixtureSchema()) 125 testStateFile(t, b.StatePath, testRefreshState()) 126 p.ReadResourceFn = nil 127 p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{ 128 "id": cty.StringVal("yes"), 129 })} 130 131 // Enable validation 132 b.OpValidation = true 133 134 op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh") 135 defer configCleanup() 136 defer done(t) 137 138 run, err := b.Operation(context.Background(), op) 139 if err != nil { 140 t.Fatalf("bad: %s", err) 141 } 142 <-run.Done() 143 144 checkState(t, b.StateOutPath, ` 145 test_instance.foo: 146 ID = yes 147 provider = provider["registry.terraform.io/hashicorp/test"] 148 `) 149 } 150 151 func TestLocal_refreshValidateProviderConfigured(t *testing.T) { 152 b := TestLocal(t) 153 154 schema := &terraform.ProviderSchema{ 155 Provider: &configschema.Block{ 156 Attributes: map[string]*configschema.Attribute{ 157 "value": {Type: cty.String, Optional: true}, 158 }, 159 }, 160 ResourceTypes: map[string]*configschema.Block{ 161 "test_instance": { 162 Attributes: map[string]*configschema.Attribute{ 163 "id": {Type: cty.String, Computed: true}, 164 "ami": {Type: cty.String, Optional: true}, 165 }, 166 }, 167 }, 168 } 169 170 p := TestLocalProvider(t, b, "test", schema) 171 testStateFile(t, b.StatePath, testRefreshState()) 172 p.ReadResourceFn = nil 173 p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{ 174 "id": cty.StringVal("yes"), 175 })} 176 177 // Enable validation 178 b.OpValidation = true 179 180 op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh-provider-config") 181 defer configCleanup() 182 defer done(t) 183 184 run, err := b.Operation(context.Background(), op) 185 if err != nil { 186 t.Fatalf("bad: %s", err) 187 } 188 <-run.Done() 189 190 if !p.ValidateProviderConfigCalled { 191 t.Fatal("Validate provider config should be called") 192 } 193 194 checkState(t, b.StateOutPath, ` 195 test_instance.foo: 196 ID = yes 197 provider = provider["registry.terraform.io/hashicorp/test"] 198 `) 199 } 200 201 // This test validates the state lacking behavior when the inner call to 202 // Context() fails 203 func TestLocal_refresh_context_error(t *testing.T) { 204 b := TestLocal(t) 205 testStateFile(t, b.StatePath, testRefreshState()) 206 op, configCleanup, done := testOperationRefresh(t, "./testdata/apply") 207 defer configCleanup() 208 defer done(t) 209 210 // we coerce a failure in Context() by omitting the provider schema 211 212 run, err := b.Operation(context.Background(), op) 213 if err != nil { 214 t.Fatalf("bad: %s", err) 215 } 216 <-run.Done() 217 if run.Result == backend.OperationSuccess { 218 t.Fatal("operation succeeded; want failure") 219 } 220 assertBackendStateUnlocked(t, b) 221 } 222 223 func TestLocal_refreshEmptyState(t *testing.T) { 224 b := TestLocal(t) 225 226 p := TestLocalProvider(t, b, "test", refreshFixtureSchema()) 227 testStateFile(t, b.StatePath, states.NewState()) 228 229 p.ReadResourceFn = nil 230 p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{ 231 "id": cty.StringVal("yes"), 232 })} 233 234 op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh") 235 defer configCleanup() 236 237 run, err := b.Operation(context.Background(), op) 238 if err != nil { 239 t.Fatalf("bad: %s", err) 240 } 241 <-run.Done() 242 243 output := done(t) 244 245 if stderr := output.Stderr(); stderr != "" { 246 t.Fatalf("expected only warning diags, got errors: %s", stderr) 247 } 248 if got, want := output.Stdout(), "Warning: Empty or non-existent state"; !strings.Contains(got, want) { 249 t.Errorf("wrong diags\n got: %s\nwant: %s", got, want) 250 } 251 252 // the backend should be unlocked after a run 253 assertBackendStateUnlocked(t, b) 254 } 255 256 func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) { 257 t.Helper() 258 259 _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) 260 261 streams, done := terminal.StreamsForTesting(t) 262 view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams)) 263 264 // Many of our tests use an overridden "test" provider that's just in-memory 265 // inside the test process, not a separate plugin on disk. 266 depLocks := depsfile.NewLocks() 267 depLocks.SetProviderOverridden(addrs.MustParseProviderSourceString("registry.terraform.io/hashicorp/test")) 268 269 return &backend.Operation{ 270 Type: backend.OperationTypeRefresh, 271 ConfigDir: configDir, 272 ConfigLoader: configLoader, 273 StateLocker: clistate.NewNoopLocker(), 274 View: view, 275 DependencyLocks: depLocks, 276 }, configCleanup, done 277 } 278 279 // testRefreshState is just a common state that we use for testing refresh. 280 func testRefreshState() *states.State { 281 state := states.NewState() 282 root := state.EnsureModule(addrs.RootModuleInstance) 283 root.SetResourceInstanceCurrent( 284 mustResourceInstanceAddr("test_instance.foo").Resource, 285 &states.ResourceInstanceObjectSrc{ 286 Status: states.ObjectReady, 287 AttrsJSON: []byte(`{"id":"bar"}`), 288 }, 289 mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), 290 ) 291 return state 292 } 293 294 // refreshFixtureSchema returns a schema suitable for processing the 295 // configuration in testdata/refresh . This schema should be 296 // assigned to a mock provider named "test". 297 func refreshFixtureSchema() *terraform.ProviderSchema { 298 return &terraform.ProviderSchema{ 299 ResourceTypes: map[string]*configschema.Block{ 300 "test_instance": { 301 Attributes: map[string]*configschema.Attribute{ 302 "ami": {Type: cty.String, Optional: true}, 303 "id": {Type: cty.String, Computed: true}, 304 }, 305 }, 306 }, 307 } 308 }