From 5df9cd0f52aec8feda6da13a0ae4762162a0c02a Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Fri, 11 Jan 2019 15:13:55 -0800 Subject: [PATCH] command/show: tests for -json output (#19980) * command/show: added test scaffold for json output More test cases will be added once the basic shape of the tests is validated. - command/json* packages now sort resources by address, matching behavior elsewhere - using cmp in tests instead of reflect.DeepEqual for the diffs - updating expected output in tests to match sorting --- command/jsonconfig/config.go | 4 + command/jsonplan/plan.go | 5 + command/jsonplan/values.go | 5 +- command/jsonstate/state.go | 5 + command/show_test.go | 101 +++++++++++++ .../show-json/basic-create/main.tf | 10 ++ .../show-json/basic-create/output.json | 77 ++++++++++ .../show-json/basic-delete/main.tf | 10 ++ .../show-json/basic-delete/output.json | 137 ++++++++++++++++++ .../show-json/basic-delete/terraform.tfstate | 37 +++++ .../show-json/basic-update/main.tf | 10 ++ .../show-json/basic-update/output.json | 93 ++++++++++++ .../show-json/basic-update/terraform.tfstate | 23 +++ 13 files changed, 514 insertions(+), 3 deletions(-) create mode 100644 command/test-fixtures/show-json/basic-create/main.tf create mode 100644 command/test-fixtures/show-json/basic-create/output.json create mode 100644 command/test-fixtures/show-json/basic-delete/main.tf create mode 100644 command/test-fixtures/show-json/basic-delete/output.json create mode 100644 command/test-fixtures/show-json/basic-delete/terraform.tfstate create mode 100644 command/test-fixtures/show-json/basic-update/main.tf create mode 100644 command/test-fixtures/show-json/basic-update/output.json create mode 100644 command/test-fixtures/show-json/basic-update/terraform.tfstate diff --git a/command/jsonconfig/config.go b/command/jsonconfig/config.go index 1ca605c5a..bc83565a3 100644 --- a/command/jsonconfig/config.go +++ b/command/jsonconfig/config.go @@ -3,6 +3,7 @@ package jsonconfig import ( "encoding/json" "fmt" + "sort" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs" @@ -244,5 +245,8 @@ func marshalResources(resources map[string]*configs.Resource, schemas *terraform rs = append(rs, r) } + sort.Slice(rs, func(i, j int) bool { + return rs[i].Address < rs[j].Address + }) return rs, nil } diff --git a/command/jsonplan/plan.go b/command/jsonplan/plan.go index 4d3f36acd..c5cbeb19b 100644 --- a/command/jsonplan/plan.go +++ b/command/jsonplan/plan.go @@ -3,6 +3,7 @@ package jsonplan import ( "encoding/json" "fmt" + "sort" "github.com/zclconf/go-cty/cty" @@ -210,6 +211,10 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform } + sort.Slice(p.ResourceChanges, func(i, j int) bool { + return p.ResourceChanges[i].Address < p.ResourceChanges[j].Address + }) + return nil } diff --git a/command/jsonplan/values.go b/command/jsonplan/values.go index 3dfb9c42d..a9fd4bf45 100644 --- a/command/jsonplan/values.go +++ b/command/jsonplan/values.go @@ -38,9 +38,8 @@ func marshalAttributeValues(value cty.Value, schema *configschema.Block) attribu return ret } -// marshalPlannedOutputs takes a list of changes and returns two output maps, -// the former with output values and the latter with true/false in place of -// values indicating whether the values are known at plan time. +// marshalPlannedOutputs takes a list of changes and returns a map of output +// values func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) { if changes.Outputs == nil { // No changes - we're done here! diff --git a/command/jsonstate/state.go b/command/jsonstate/state.go index 6cab99a5c..094bbb261 100644 --- a/command/jsonstate/state.go +++ b/command/jsonstate/state.go @@ -3,6 +3,7 @@ package jsonstate import ( "encoding/json" "fmt" + "sort" "github.com/zclconf/go-cty/cty" ctyjson "github.com/zclconf/go-cty/cty/json" @@ -273,5 +274,9 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform. } + sort.Slice(ret, func(i, j int) bool { + return ret[i].Address < ret[j].Address + }) + return ret, nil } diff --git a/command/show_test.go b/command/show_test.go index ba8e48927..a64aa1385 100644 --- a/command/show_test.go +++ b/command/show_test.go @@ -1,9 +1,13 @@ package command import ( + "encoding/json" + "io/ioutil" + "os" "path/filepath" "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/plans" @@ -150,6 +154,92 @@ func TestShow_state(t *testing.T) { } } +func TestPlan_json_output(t *testing.T) { + fixtureDir := "test-fixtures/show-json" + testDirs, err := ioutil.ReadDir(fixtureDir) + if err != nil { + t.Fatal(err) + } + + for _, entry := range testDirs { + if !entry.IsDir() { + continue + } + + t.Run(entry.Name(), func(t *testing.T) { + inputDir := filepath.Join(fixtureDir, entry.Name()) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + if err := os.Chdir(inputDir); err != nil { + t.Fatalf("err: %s", err) + } + defer os.Chdir(cwd) + + p := showFixtureProvider() + ui := new(cli.MockUi) + pc := &PlanCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args := []string{ + "-out=terraform.plan", + } + + if code := pc.Run(args); code != 0 { + t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String()) + } + + // flush the plan output from the mock ui + ui.OutputWriter.Reset() + sc := &ShowCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args = []string{ + "-json", + "terraform.plan", + } + defer os.Remove("terraform.plan") + + if code := sc.Run(args); code != 0 { + t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String()) + } + + // compare ui output to wanted output + var got, want plan + + gotString := ui.OutputWriter.String() + json.Unmarshal([]byte(gotString), &got) + + wantFile, err := os.Open("output.json") + if err != nil { + t.Fatalf("err: %s", err) + } + defer wantFile.Close() + byteValue, err := ioutil.ReadAll(wantFile) + if err != nil { + t.Fatalf("err: %s", err) + } + json.Unmarshal([]byte(byteValue), &want) + + if !cmp.Equal(got, want) { + t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) + } + + }) + + } +} + // showFixtureSchema returns a schema suitable for processing the configuration // in test-fixtures/show. This schema should be assigned to a mock provider // named "test". @@ -225,3 +315,14 @@ func showFixturePlanFile(t *testing.T) string { plan, ) } + +// this simplified plan struct allows us to preserve field order when marshaling +// the command output. +type plan struct { + FormatVersion string `json:"format_version,omitempty"` + PlannedValues map[string]interface{} `json:"planned_values,omitempty"` + ResourceChanges []interface{} `json:"resource_changes,omitempty"` + OutputChanges map[string]interface{} `json:"output_changes,omitempty"` + PriorState string `json:"prior_state,omitempty"` + Config string `json:"configuration,omitempty"` +} diff --git a/command/test-fixtures/show-json/basic-create/main.tf b/command/test-fixtures/show-json/basic-create/main.tf new file mode 100644 index 000000000..52fea375d --- /dev/null +++ b/command/test-fixtures/show-json/basic-create/main.tf @@ -0,0 +1,10 @@ +variable "test_var" { + default = "bar" +} +resource "test_instance" "test" { + ami = var.test_var +} + +output "test" { + value = var.test_var +} \ No newline at end of file diff --git a/command/test-fixtures/show-json/basic-create/output.json b/command/test-fixtures/show-json/basic-create/output.json new file mode 100644 index 000000000..da287aad9 --- /dev/null +++ b/command/test-fixtures/show-json/basic-create/output.json @@ -0,0 +1,77 @@ +{ + "format_version": "0.1", + "planned_values": { + "outputs": { + "test": { + "sensitive": false, + "value": "bar" + } + }, + "root_module": { + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_name": "test", + "schema_version": 0 + } + ] + } + }, + "resource_changes": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "deposed": true, + "change": { + "actions": [ + "Create" + ], + "before": null, + "after_unknown": { + "ami": false, + "id": true + } + } + } + ], + "output_changes": { + "test": { + "actions": [ + "Create" + ], + "before": null, + "after": "bar", + "after_unknown": false + } + }, + "configuration": { + "root_module": { + "outputs": { + "test": { + "expression": { + "references": [ + "var.test_var" + ] + } + } + }, + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_config_key": "provider.test", + "schema_version": 0, + "count_expression": {}, + "for_each_expression": {} + } + ] + } + } +} \ No newline at end of file diff --git a/command/test-fixtures/show-json/basic-delete/main.tf b/command/test-fixtures/show-json/basic-delete/main.tf new file mode 100644 index 000000000..52fea375d --- /dev/null +++ b/command/test-fixtures/show-json/basic-delete/main.tf @@ -0,0 +1,10 @@ +variable "test_var" { + default = "bar" +} +resource "test_instance" "test" { + ami = var.test_var +} + +output "test" { + value = var.test_var +} \ No newline at end of file diff --git a/command/test-fixtures/show-json/basic-delete/output.json b/command/test-fixtures/show-json/basic-delete/output.json new file mode 100644 index 000000000..cce569d0d --- /dev/null +++ b/command/test-fixtures/show-json/basic-delete/output.json @@ -0,0 +1,137 @@ +{ + "format_version": "0.1", + "planned_values": { + "outputs": { + "test": { + "sensitive": false, + "value": "bar" + } + }, + "root_module": { + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_name": "test", + "schema_version": 0, + "values": { + "ami": {}, + "id": {} + } + } + ] + } + }, + "resource_changes": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "deposed": true, + "change": { + "actions": [ + "Update" + ], + "before": { + "ami": "foo", + "id": null + }, + "after": { + "ami": "bar", + "id": null + }, + "after_unknown": { + "ami": false, + "id": false + } + } + }, + { + "address": "test_instance.test-delete", + "mode": "managed", + "type": "test_instance", + "name": "test-delete", + "deposed": true, + "change": { + "actions": [ + "Delete" + ], + "before": { + "ami": "foo", + "id": null + }, + "after": null, + "after_unknown": false + } + } + ], + "output_changes": { + "test": { + "actions": [ + "Create" + ], + "before": null, + "after": "bar", + "after_unknown": false + } + }, + "prior_state": { + "format_version": "0.1", + "values": { + "root_module": { + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_name": "test", + "values": { + "ami": {}, + "id": {} + } + }, + { + "address": "test_instance.test-delete", + "mode": "managed", + "type": "test_instance", + "name": "test-delete", + "provider_name": "test", + "values": { + "ami": {}, + "id": {} + } + } + ] + } + } + }, + "configuration": { + "root_module": { + "outputs": { + "test": { + "expression": { + "references": [ + "var.test_var" + ] + } + } + }, + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_config_key": "provider.test", + "schema_version": 0, + "count_expression": {}, + "for_each_expression": {} + } + ] + } + } +} \ No newline at end of file diff --git a/command/test-fixtures/show-json/basic-delete/terraform.tfstate b/command/test-fixtures/show-json/basic-delete/terraform.tfstate new file mode 100644 index 000000000..4cf6ed7fb --- /dev/null +++ b/command/test-fixtures/show-json/basic-delete/terraform.tfstate @@ -0,0 +1,37 @@ +{ + "version": 4, + "terraform_version": "0.12.0", + "serial": 7, + "lineage": "configuredUnchanged", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider": "provider.test", + "instances": [ + { + "schema_version": 0, + "attributes": { + "ami": "foo" + } + } + ] + }, + { + "mode": "managed", + "type": "test_instance", + "name": "test-delete", + "provider": "provider.test", + "instances": [ + { + "schema_version": 0, + "attributes": { + "ami": "foo" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/command/test-fixtures/show-json/basic-update/main.tf b/command/test-fixtures/show-json/basic-update/main.tf new file mode 100644 index 000000000..52fea375d --- /dev/null +++ b/command/test-fixtures/show-json/basic-update/main.tf @@ -0,0 +1,10 @@ +variable "test_var" { + default = "bar" +} +resource "test_instance" "test" { + ami = var.test_var +} + +output "test" { + value = var.test_var +} \ No newline at end of file diff --git a/command/test-fixtures/show-json/basic-update/output.json b/command/test-fixtures/show-json/basic-update/output.json new file mode 100644 index 000000000..ceca5d2b2 --- /dev/null +++ b/command/test-fixtures/show-json/basic-update/output.json @@ -0,0 +1,93 @@ +{ + "format_version": "0.1", + "planned_values": { + "outputs": { + "test": { + "sensitive": false, + "value": "bar" + } + }, + "root_module": {} + }, + "resource_changes": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "deposed": true, + "change": { + "actions": [ + "NoOp" + ], + "before": { + "ami": "bar", + "id": null + }, + "after": { + "ami": "bar", + "id": null + }, + "after_unknown": { + "ami": false, + "id": false + } + } + } + ], + "output_changes": { + "test": { + "actions": [ + "Create" + ], + "before": null, + "after": "bar", + "after_unknown": false + } + }, + "prior_state": { + "format_version": "0.1", + "values": { + "root_module": { + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_name": "test", + "values": { + "ami": {}, + "id": {} + } + } + ] + } + } + }, + "configuration": { + "root_module": { + "outputs": { + "test": { + "expression": { + "references": [ + "var.test_var" + ] + } + } + }, + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_config_key": "provider.test", + "schema_version": 0, + "count_expression": {}, + "for_each_expression": {} + } + ] + } + } +} \ No newline at end of file diff --git a/command/test-fixtures/show-json/basic-update/terraform.tfstate b/command/test-fixtures/show-json/basic-update/terraform.tfstate new file mode 100644 index 000000000..f980cdfd5 --- /dev/null +++ b/command/test-fixtures/show-json/basic-update/terraform.tfstate @@ -0,0 +1,23 @@ +{ + "version": 4, + "terraform_version": "0.12.0", + "serial": 7, + "lineage": "configuredUnchanged", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider": "provider.test", + "instances": [ + { + "schema_version": 0, + "attributes": { + "ami": "bar" + } + } + ] + } + ] +} \ No newline at end of file