| package jsonformat |
| |
| import ( |
| "fmt" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/mitchellh/colorstring" |
| |
| "github.com/hashicorp/terraform/internal/command/format" |
| "github.com/hashicorp/terraform/internal/command/jsonprovider" |
| "github.com/hashicorp/terraform/internal/command/jsonstate" |
| "github.com/hashicorp/terraform/internal/states/statefile" |
| "github.com/hashicorp/terraform/internal/terminal" |
| |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/configs/configschema" |
| "github.com/hashicorp/terraform/internal/providers" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/hashicorp/terraform/internal/terraform" |
| ) |
| |
| func TestState(t *testing.T) { |
| color := &colorstring.Colorize{Colors: colorstring.DefaultColors, Disable: true} |
| |
| tests := []struct { |
| State *format.StateOpts |
| Want string |
| }{ |
| { |
| &format.StateOpts{ |
| State: &states.State{}, |
| Color: color, |
| Schemas: &terraform.Schemas{}, |
| }, |
| "The state file is empty. No resources are represented.\n", |
| }, |
| { |
| &format.StateOpts{ |
| State: basicState(t), |
| Color: color, |
| Schemas: testSchemas(), |
| }, |
| basicStateOutput, |
| }, |
| { |
| &format.StateOpts{ |
| State: nestedState(t), |
| Color: color, |
| Schemas: testSchemas(), |
| }, |
| nestedStateOutput, |
| }, |
| { |
| &format.StateOpts{ |
| State: deposedState(t), |
| Color: color, |
| Schemas: testSchemas(), |
| }, |
| deposedNestedStateOutput, |
| }, |
| { |
| &format.StateOpts{ |
| State: onlyDeposedState(t), |
| Color: color, |
| Schemas: testSchemas(), |
| }, |
| onlyDeposedOutput, |
| }, |
| { |
| &format.StateOpts{ |
| State: stateWithMoreOutputs(t), |
| Color: color, |
| Schemas: testSchemas(), |
| }, |
| stateWithMoreOutputsOutput, |
| }, |
| } |
| |
| for i, tt := range tests { |
| t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { |
| |
| root, outputs, err := jsonstate.MarshalForRenderer(&statefile.File{ |
| State: tt.State.State, |
| }, tt.State.Schemas) |
| |
| if err != nil { |
| t.Errorf("found err: %v", err) |
| return |
| } |
| |
| streams, done := terminal.StreamsForTesting(t) |
| renderer := Renderer{ |
| Colorize: color, |
| Streams: streams, |
| } |
| |
| renderer.RenderHumanState(State{ |
| StateFormatVersion: jsonstate.FormatVersion, |
| RootModule: root, |
| RootModuleOutputs: outputs, |
| ProviderFormatVersion: jsonprovider.FormatVersion, |
| ProviderSchemas: jsonprovider.MarshalForRenderer(tt.State.Schemas), |
| }) |
| |
| result := done(t).All() |
| if diff := cmp.Diff(result, tt.Want); diff != "" { |
| t.Errorf("wrong output\nexpected:\n%s\nactual:\n%s\ndiff:\n%s\n", tt.Want, result, diff) |
| } |
| }) |
| } |
| } |
| |
| func testProvider() *terraform.MockProvider { |
| p := new(terraform.MockProvider) |
| p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { |
| return providers.ReadResourceResponse{NewState: req.PriorState} |
| } |
| |
| p.GetProviderSchemaResponse = testProviderSchema() |
| |
| return p |
| } |
| |
| func testProviderSchema() *providers.GetProviderSchemaResponse { |
| return &providers.GetProviderSchemaResponse{ |
| Provider: providers.Schema{ |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "region": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| ResourceTypes: map[string]providers.Schema{ |
| "test_resource": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "id": {Type: cty.String, Computed: true}, |
| "foo": {Type: cty.String, Optional: true}, |
| "woozles": {Type: cty.String, Optional: true}, |
| }, |
| BlockTypes: map[string]*configschema.NestedBlock{ |
| "nested": { |
| Nesting: configschema.NestingList, |
| Block: configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "compute": {Type: cty.String, Optional: true}, |
| "value": {Type: cty.String, Optional: true}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| DataSources: map[string]providers.Schema{ |
| "test_data_source": { |
| Block: &configschema.Block{ |
| Attributes: map[string]*configschema.Attribute{ |
| "compute": {Type: cty.String, Optional: true}, |
| "value": {Type: cty.String, Computed: true}, |
| }, |
| }, |
| }, |
| }, |
| } |
| } |
| |
| func testSchemas() *terraform.Schemas { |
| provider := testProvider() |
| return &terraform.Schemas{ |
| Providers: map[addrs.Provider]*terraform.ProviderSchema{ |
| addrs.NewDefaultProvider("test"): provider.ProviderSchema(), |
| }, |
| } |
| } |
| |
| const basicStateOutput = `# data.test_data_source.data: |
| data "test_data_source" "data" { |
| compute = "sure" |
| } |
| |
| # test_resource.baz[0]: |
| resource "test_resource" "baz" { |
| woozles = "confuzles" |
| } |
| |
| |
| Outputs: |
| |
| bar = "bar value" |
| ` |
| |
| const nestedStateOutput = `# test_resource.baz[0]: |
| resource "test_resource" "baz" { |
| woozles = "confuzles" |
| |
| nested { |
| value = "42" |
| } |
| } |
| ` |
| |
| const deposedNestedStateOutput = `# test_resource.baz[0]: |
| resource "test_resource" "baz" { |
| woozles = "confuzles" |
| |
| nested { |
| value = "42" |
| } |
| } |
| |
| # test_resource.baz[0]: (deposed object 1234) |
| resource "test_resource" "baz" { |
| woozles = "confuzles" |
| |
| nested { |
| value = "42" |
| } |
| } |
| ` |
| |
| const onlyDeposedOutput = `# test_resource.baz[0]: (deposed object 1234) |
| resource "test_resource" "baz" { |
| woozles = "confuzles" |
| |
| nested { |
| value = "42" |
| } |
| } |
| |
| # test_resource.baz[0]: (deposed object 5678) |
| resource "test_resource" "baz" { |
| woozles = "confuzles" |
| |
| nested { |
| value = "42" |
| } |
| } |
| ` |
| |
| const stateWithMoreOutputsOutput = `# test_resource.baz[0]: |
| resource "test_resource" "baz" { |
| woozles = "confuzles" |
| } |
| |
| |
| Outputs: |
| |
| bool_var = true |
| int_var = 42 |
| map_var = { |
| "first" = "foo" |
| "second" = "bar" |
| } |
| sensitive_var = (sensitive value) |
| string_var = "string value" |
| ` |
| |
| func basicState(t *testing.T) *states.State { |
| state := states.NewState() |
| |
| rootModule := state.RootModule() |
| if rootModule == nil { |
| t.Errorf("root module is nil; want valid object") |
| } |
| |
| rootModule.SetLocalValue("foo", cty.StringVal("foo value")) |
| rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false) |
| rootModule.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_resource", |
| Name: "baz", |
| }.Instance(addrs.IntKey(0)), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| SchemaVersion: 0, |
| AttrsJSON: []byte(`{"woozles":"confuzles"}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| rootModule.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.DataResourceMode, |
| Type: "test_data_source", |
| Name: "data", |
| }.Instance(addrs.NoKey), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| SchemaVersion: 0, |
| AttrsJSON: []byte(`{"compute":"sure"}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| return state |
| } |
| |
| func stateWithMoreOutputs(t *testing.T) *states.State { |
| state := states.NewState() |
| |
| rootModule := state.RootModule() |
| if rootModule == nil { |
| t.Errorf("root module is nil; want valid object") |
| } |
| |
| rootModule.SetOutputValue("string_var", cty.StringVal("string value"), false) |
| rootModule.SetOutputValue("int_var", cty.NumberIntVal(42), false) |
| rootModule.SetOutputValue("bool_var", cty.BoolVal(true), false) |
| rootModule.SetOutputValue("sensitive_var", cty.StringVal("secret!!!"), true) |
| rootModule.SetOutputValue("map_var", cty.MapVal(map[string]cty.Value{ |
| "first": cty.StringVal("foo"), |
| "second": cty.StringVal("bar"), |
| }), false) |
| |
| rootModule.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_resource", |
| Name: "baz", |
| }.Instance(addrs.IntKey(0)), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| SchemaVersion: 0, |
| AttrsJSON: []byte(`{"woozles":"confuzles"}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| return state |
| } |
| |
| func nestedState(t *testing.T) *states.State { |
| state := states.NewState() |
| |
| rootModule := state.RootModule() |
| if rootModule == nil { |
| t.Errorf("root module is nil; want valid object") |
| } |
| |
| rootModule.SetResourceInstanceCurrent( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_resource", |
| Name: "baz", |
| }.Instance(addrs.IntKey(0)), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| SchemaVersion: 0, |
| AttrsJSON: []byte(`{"woozles":"confuzles","nested": [{"value": "42"}]}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| return state |
| } |
| |
| func deposedState(t *testing.T) *states.State { |
| state := nestedState(t) |
| rootModule := state.RootModule() |
| rootModule.SetResourceInstanceDeposed( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_resource", |
| Name: "baz", |
| }.Instance(addrs.IntKey(0)), |
| states.DeposedKey("1234"), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| SchemaVersion: 0, |
| AttrsJSON: []byte(`{"woozles":"confuzles","nested": [{"value": "42"}]}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| return state |
| } |
| |
| // replicate a corrupt resource where only a deposed exists |
| func onlyDeposedState(t *testing.T) *states.State { |
| state := states.NewState() |
| |
| rootModule := state.RootModule() |
| if rootModule == nil { |
| t.Errorf("root module is nil; want valid object") |
| } |
| |
| rootModule.SetResourceInstanceDeposed( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_resource", |
| Name: "baz", |
| }.Instance(addrs.IntKey(0)), |
| states.DeposedKey("1234"), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| SchemaVersion: 0, |
| AttrsJSON: []byte(`{"woozles":"confuzles","nested": [{"value": "42"}]}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| rootModule.SetResourceInstanceDeposed( |
| addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "test_resource", |
| Name: "baz", |
| }.Instance(addrs.IntKey(0)), |
| states.DeposedKey("5678"), |
| &states.ResourceInstanceObjectSrc{ |
| Status: states.ObjectReady, |
| SchemaVersion: 0, |
| AttrsJSON: []byte(`{"woozles":"confuzles","nested": [{"value": "42"}]}`), |
| }, |
| addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider("test"), |
| Module: addrs.RootModule, |
| }, |
| ) |
| return state |
| } |