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