`terraform show` and `terraform providers schema -json` should return valid json (#20697)
* command/providers schemas: return empty json object if config parses successfully but no providers found * command/show (state): return an empty object if state is nil
This commit is contained in:
parent
035e89696c
commit
9d0d564ec7
|
@ -124,9 +124,11 @@ func Marshal(
|
|||
}
|
||||
|
||||
// output.PriorState
|
||||
output.PriorState, err = jsonstate.Marshal(sf, stateSchemas)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling prior state: %s", err)
|
||||
if sf != nil && !sf.State.Empty() {
|
||||
output.PriorState, err = jsonstate.Marshal(sf, stateSchemas)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling prior state: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// output.Config
|
||||
|
|
|
@ -13,8 +13,8 @@ const FormatVersion = "0.1"
|
|||
|
||||
// providers is the top-level object returned when exporting provider schemas
|
||||
type providers struct {
|
||||
FormatVersion string `json:"format_version"`
|
||||
Schemas map[string]Provider `json:"provider_schemas"`
|
||||
FormatVersion string `json:"format_version"`
|
||||
Schemas map[string]*Provider `json:"provider_schemas,omitempty"`
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
|
@ -24,7 +24,7 @@ type Provider struct {
|
|||
}
|
||||
|
||||
func newProviders() *providers {
|
||||
schemas := make(map[string]Provider)
|
||||
schemas := make(map[string]*Provider)
|
||||
return &providers{
|
||||
FormatVersion: FormatVersion,
|
||||
Schemas: schemas,
|
||||
|
@ -32,10 +32,6 @@ func newProviders() *providers {
|
|||
}
|
||||
|
||||
func Marshal(s *terraform.Schemas) ([]byte, error) {
|
||||
if len(s.Providers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
providers := newProviders()
|
||||
|
||||
for k, v := range s.Providers {
|
||||
|
@ -46,9 +42,9 @@ func Marshal(s *terraform.Schemas) ([]byte, error) {
|
|||
return ret, err
|
||||
}
|
||||
|
||||
func marshalProvider(tps *terraform.ProviderSchema) Provider {
|
||||
func marshalProvider(tps *terraform.ProviderSchema) *Provider {
|
||||
if tps == nil {
|
||||
return Provider{}
|
||||
return &Provider{}
|
||||
}
|
||||
|
||||
var ps *schema
|
||||
|
@ -66,7 +62,7 @@ func marshalProvider(tps *terraform.ProviderSchema) Provider {
|
|||
ds = marshalSchemas(tps.DataSources, tps.ResourceTypeSchemaVersions)
|
||||
}
|
||||
|
||||
return Provider{
|
||||
return &Provider{
|
||||
Provider: ps,
|
||||
ResourceSchemas: rs,
|
||||
DataSourceSchemas: ds,
|
||||
|
|
|
@ -14,15 +14,15 @@ import (
|
|||
func TestMarshalProvider(t *testing.T) {
|
||||
tests := []struct {
|
||||
Input *terraform.ProviderSchema
|
||||
Want Provider
|
||||
Want *Provider
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
Provider{},
|
||||
&Provider{},
|
||||
},
|
||||
{
|
||||
testProvider(),
|
||||
Provider{
|
||||
&Provider{
|
||||
Provider: &schema{
|
||||
Block: &block{
|
||||
Attributes: map[string]*attribute{
|
||||
|
|
|
@ -23,9 +23,9 @@ const FormatVersion = "0.1"
|
|||
// state is the top-level representation of the json format of a terraform
|
||||
// state.
|
||||
type state struct {
|
||||
FormatVersion string `json:"format_version,omitempty"`
|
||||
TerraformVersion string `json:"terraform_version"`
|
||||
Values stateValues `json:"values,omitempty"`
|
||||
FormatVersion string `json:"format_version,omitempty"`
|
||||
TerraformVersion string `json:"terraform_version,omitempty"`
|
||||
Values *stateValues `json:"values,omitempty"`
|
||||
}
|
||||
|
||||
// stateValues is the common representation of resolved values for both the prior
|
||||
|
@ -121,14 +121,17 @@ func newState() *state {
|
|||
|
||||
// Marshal returns the json encoding of a terraform state.
|
||||
func Marshal(sf *statefile.File, schemas *terraform.Schemas) ([]byte, error) {
|
||||
output := newState()
|
||||
|
||||
if sf == nil || sf.State.Empty() {
|
||||
return nil, nil
|
||||
ret, err := json.Marshal(output)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
output := newState()
|
||||
if sf.TerraformVersion != nil {
|
||||
output.TerraformVersion = sf.TerraformVersion.String()
|
||||
}
|
||||
|
||||
// output.StateValues
|
||||
err := output.marshalStateValues(sf.State, schemas)
|
||||
if err != nil {
|
||||
|
@ -155,7 +158,7 @@ func (jsonstate *state) marshalStateValues(s *states.State, schemas *terraform.S
|
|||
return err
|
||||
}
|
||||
|
||||
jsonstate.Values = sv
|
||||
jsonstate.Values = &sv
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
@ -31,62 +32,73 @@ func TestProvidersSchema_error(t *testing.T) {
|
|||
func TestProvidersSchema_output(t *testing.T) {
|
||||
// there's only one test at this time. This can be refactored to have
|
||||
// multiple test cases in individual directories as needed.
|
||||
inputDir := "test-fixtures/providers-schema"
|
||||
td := tempDir(t)
|
||||
copy.CopyDir(inputDir, td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := showFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
// `terrafrom init`
|
||||
ic := &InitCommand{
|
||||
Meta: m,
|
||||
providerInstaller: &mockProviderInstaller{
|
||||
Providers: map[string][]string{
|
||||
"test": []string{"1.2.3"},
|
||||
},
|
||||
Dir: m.pluginDir(),
|
||||
},
|
||||
}
|
||||
if code := ic.Run([]string{}); code != 0 {
|
||||
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
||||
}
|
||||
|
||||
// flush the init output from the mock ui
|
||||
ui.OutputWriter.Reset()
|
||||
|
||||
// `terraform provider schemas` command
|
||||
pc := &ProvidersSchemaCommand{Meta: m}
|
||||
if code := pc.Run([]string{"-json"}); code != 0 {
|
||||
t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
var got, want providerSchemas
|
||||
|
||||
gotString := ui.OutputWriter.String()
|
||||
json.Unmarshal([]byte(gotString), &got)
|
||||
|
||||
wantFile, err := os.Open("output.json")
|
||||
fixtureDir := "test-fixtures/providers-schema"
|
||||
testDirs, err := ioutil.ReadDir(fixtureDir)
|
||||
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) {
|
||||
fmt.Println(gotString)
|
||||
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, entry := range testDirs {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
t.Run(entry.Name(), func(t *testing.T) {
|
||||
td := tempDir(t)
|
||||
inputDir := filepath.Join(fixtureDir, entry.Name())
|
||||
copy.CopyDir(inputDir, td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := showFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
// `terrafrom init`
|
||||
ic := &InitCommand{
|
||||
Meta: m,
|
||||
providerInstaller: &mockProviderInstaller{
|
||||
Providers: map[string][]string{
|
||||
"test": []string{"1.2.3"},
|
||||
},
|
||||
Dir: m.pluginDir(),
|
||||
},
|
||||
}
|
||||
if code := ic.Run([]string{}); code != 0 {
|
||||
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
||||
}
|
||||
|
||||
// flush the init output from the mock ui
|
||||
ui.OutputWriter.Reset()
|
||||
|
||||
// `terraform provider schemas` command
|
||||
pc := &ProvidersSchemaCommand{Meta: m}
|
||||
if code := pc.Run([]string{"-json"}); code != 0 {
|
||||
t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
var got, want providerSchemas
|
||||
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type providerSchemas struct {
|
||||
|
|
|
@ -140,14 +140,6 @@ func (c *ShowCommand) Run(args []string) int {
|
|||
}
|
||||
}
|
||||
|
||||
// This is an odd-looking check, because it's ok if we have a plan and an
|
||||
// empty state, and we've already validated that any command-line arguments
|
||||
// have been read successfully
|
||||
if plan == nil && stateFile == nil {
|
||||
c.Ui.Output("No state.")
|
||||
return 0
|
||||
}
|
||||
|
||||
if plan != nil {
|
||||
if jsonOutput == true {
|
||||
config := ctx.Config()
|
||||
|
@ -188,6 +180,8 @@ func (c *ShowCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if jsonOutput == true {
|
||||
// At this point, it is possible that there is neither state nor a plan.
|
||||
// That's ok, we'll just return an empty object.
|
||||
jsonState, err := jsonstate.Marshal(stateFile, schemas)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Failed to marshal state to json: %s", err))
|
||||
|
@ -195,6 +189,10 @@ func (c *ShowCommand) Run(args []string) int {
|
|||
}
|
||||
c.Ui.Output(string(jsonState))
|
||||
} else {
|
||||
if stateFile == nil {
|
||||
c.Ui.Output("No state.")
|
||||
return 0
|
||||
}
|
||||
c.Ui.Output(format.State(&format.StateOpts{
|
||||
State: stateFile.State,
|
||||
Color: c.Colorize(),
|
||||
|
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -219,6 +220,89 @@ func TestShow_json_output(t *testing.T) {
|
|||
}
|
||||
json.Unmarshal([]byte(byteValue), &want)
|
||||
|
||||
if !cmp.Equal(got, want) {
|
||||
fmt.Println(ui.OutputWriter.String())
|
||||
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// similar test as above, without the plan
|
||||
func TestShow_json_output_state(t *testing.T) {
|
||||
fixtureDir := "test-fixtures/show-json-state"
|
||||
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) {
|
||||
td := tempDir(t)
|
||||
inputDir := filepath.Join(fixtureDir, entry.Name())
|
||||
copy.CopyDir(inputDir, td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := showFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
// init
|
||||
ic := &InitCommand{
|
||||
Meta: m,
|
||||
providerInstaller: &mockProviderInstaller{
|
||||
Providers: map[string][]string{
|
||||
"test": []string{"1.2.3"},
|
||||
},
|
||||
Dir: m.pluginDir(),
|
||||
},
|
||||
}
|
||||
if code := ic.Run([]string{}); code != 0 {
|
||||
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
||||
}
|
||||
|
||||
// flush the plan output from the mock ui
|
||||
ui.OutputWriter.Reset()
|
||||
sc := &ShowCommand{
|
||||
Meta: m,
|
||||
}
|
||||
|
||||
if code := sc.Run([]string{"-json"}); code != 0 {
|
||||
t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
// compare ui output to wanted output
|
||||
type state struct {
|
||||
FormatVersion string `json:"format_version,omitempty"`
|
||||
TerraformVersion string `json:"terraform_version"`
|
||||
Values map[string]interface{} `json:"values,omitempty"`
|
||||
}
|
||||
var got, want state
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"format_version": "0.1"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"format_version": "0.1"
|
||||
}
|
Loading…
Reference in New Issue