From 4ff4582dff335be2112478a1d2de42dbf056f220 Mon Sep 17 00:00:00 2001 From: Khosrow Moossavi Date: Wed, 26 Feb 2020 12:08:24 -0500 Subject: [PATCH] feat: Show sensitivity of the output value in rendered result (#207) --- cmd/root.go | 2 + examples/output_values.json | 32 +++++---- internal/format/document.go | 10 ++- internal/format/json_test.go | 1 + internal/format/pretty.go | 4 +- internal/format/table.go | 28 ++++---- .../document/document-OutputValues.golden | 29 ++++++-- .../testdata/json/json-OutputValues.golden | 12 ++-- .../pretty/pretty-OutputValues.golden | 11 ++- .../testdata/table/table-OutputValues.golden | 12 ++-- .../testdata/yaml/yaml-OutputValues.golden | 6 +- internal/module/module.go | 20 ++++-- internal/types/types.go | 9 +-- pkg/tfconf/input.go | 14 ++-- pkg/tfconf/output.go | 71 ++++++++++++++++++- pkg/tmpl/template.go | 18 ----- 16 files changed, 190 insertions(+), 89 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 6ae132a..081d422 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,6 +29,8 @@ var rootCmd = &cobra.Command{ settings.ShowInputs = oppositeBool("no-inputs") settings.ShowOutputs = oppositeBool("no-outputs") + settings.OutputValues = options.OutputValues + settings.ShowColor = oppositeBool("no-color") settings.SortByName = oppositeBool("no-sort") settings.ShowRequired = oppositeBool("no-required") diff --git a/examples/output_values.json b/examples/output_values.json index 0d87d07..099b92c 100644 --- a/examples/output_values.json +++ b/examples/output_values.json @@ -1,23 +1,27 @@ { "output-0.12": { - "sensitive": false, - "type": "string", - "value": "" + "sensitive": true, + "type": "string", + "value": "sensitive-content-should-be-hidden" }, "output-1": { - "sensitive": false, - "type": "int", - "value": 1 + "sensitive": false, + "type": "int", + "value": 1 }, "output-2": { - "sensitive": false, - "type": "array", - "value": ["jack", "lola"] + "sensitive": false, + "type": "array", + "value": [ + "jack", + "lola" + ] }, "unquoted": { - "sensitive": false, - "type": "map", - "value": {"leon":"cat"} + "sensitive": false, + "type": "map", + "value": { + "leon": "cat" + } } - } - \ No newline at end of file +} diff --git a/internal/format/document.go b/internal/format/document.go index a057862..60dc805 100644 --- a/internal/format/document.go +++ b/internal/format/document.go @@ -77,7 +77,7 @@ const ( Type: {{ tostring .Type | type }} {{ if or .HasDefault (not isRequired) }} - Default: {{ default "n/a" .Value | value }} + Default: {{ default "n/a" .GetValue | value }} {{- end }} ` @@ -93,8 +93,12 @@ const ( {{ indent 1 }} {{ name .Name }} Description: {{ tostring .Description | sanitizeDoc }} - {{ if $.Settings.OutputValues }} - Value: {{ .Value | sanitizeInterface | sanitizeDoc }} + + {{ if $.Settings.OutputValues }} + {{- $sensitive := ternary .Sensitive "" .GetValue -}} + Value: {{ value $sensitive | sanitizeDoc }} + + Sensitive: {{ ternary (.Sensitive) "yes" "no" }} {{ end }} {{ end }} {{ end }} diff --git a/internal/format/json_test.go b/internal/format/json_test.go index b324961..73cae95 100644 --- a/internal/format/json_test.go +++ b/internal/format/json_test.go @@ -280,6 +280,7 @@ func TestJsonEscapeCharacters(t *testing.T) { assert.Nil(err) assert.Equal(expected, actual) } + func TestJsonOutputValues(t *testing.T) { assert := assert.New(t) settings := testutil.Settings().WithSections().With(&print.Settings{ diff --git a/internal/format/pretty.go b/internal/format/pretty.go index 1e93d8b..538a25f 100644 --- a/internal/format/pretty.go +++ b/internal/format/pretty.go @@ -38,7 +38,7 @@ const ( {{- with .Module.Inputs }} {{- printf "\n" -}} {{- range . }} - {{ printf "input.%s" .Name | colorize "\033[36m" }} ({{ default "required" .Value }}) + {{ printf "input.%s" .Name | colorize "\033[36m" }} ({{ default "required" .GetValue }}) {{ tostring .Description | trimSuffix "\n" | default "n/a" | colorize "\033[90m" }} {{ end }} {{- printf "\n" -}} @@ -54,7 +54,7 @@ const ( {{ printf "output.%s" .Name | colorize "\033[36m" }} {{- if $.Settings.OutputValues -}} {{- printf " " -}} - ({{ sanitizeInterface .Value }}) + ({{ ternary .Sensitive "" .GetValue }}) {{- end }} {{ tostring .Description | trimSuffix "\n" | default "n/a" | colorize "\033[90m" }} {{ end }} diff --git a/internal/format/table.go b/internal/format/table.go index 8c2de19..a6ee4f8 100644 --- a/internal/format/table.go +++ b/internal/format/table.go @@ -39,19 +39,13 @@ const ( {{ if not .Module.Inputs }} No input. {{ else }} - {{ if not .Settings.ShowRequired }} - | Name | Description | Type | Default | - |------|-------------|------|---------| - {{- else }} - | Name | Description | Type | Default | Required | - |------|-------------|------|---------|:--------:| - {{- end }} + | Name | Description | Type | Default |{{ if .Settings.ShowRequired }} Required |{{ end }} + |------|-------------|------|---------|{{ if .Settings.ShowRequired }}:--------:|{{ end }} {{- range .Module.Inputs }} - {{- if not $.Settings.ShowRequired }} - | {{ name .Name }} | {{ tostring .Description | sanitizeTbl }} | {{ tostring .Type | type | sanitizeTbl }} | {{ value .Value | sanitizeTbl }} | - {{- else }} - | {{ name .Name }} | {{ tostring .Description | sanitizeTbl }} | {{ tostring .Type | type | sanitizeTbl }} | {{ value .Value | sanitizeTbl }} | {{ ternary (.Value) "no" "yes" }} | - {{- end }} + | {{ name .Name }} | {{ tostring .Description | sanitizeTbl }} | {{ tostring .Type | type | sanitizeTbl }} | {{ value .GetValue | sanitizeTbl }} | + {{- if $.Settings.ShowRequired -}} + {{ printf " " }}{{ ternary (.GetValue) "no" "yes" }} | + {{- end -}} {{- end }} {{ end }} {{ end -}} @@ -63,10 +57,14 @@ const ( {{ if not .Module.Outputs }} No output. {{ else }} - | Name | Description |{{ if $.Settings.OutputValues }} Value |{{ end }} - |------|-------------|{{ if $.Settings.OutputValues }}-------|{{ end }} + | Name | Description |{{ if .Settings.OutputValues }} Value | Sensitive |{{ end }} + |------|-------------|{{ if .Settings.OutputValues }}-------|:---------:|{{ end }} {{- range .Module.Outputs }} - | {{ name .Name }} | {{ tostring .Description | sanitizeTbl }} |{{ if $.Settings.OutputValues }} {{ .Value | sanitizeInterface | sanitizeTbl }} |{{ end }} + | {{ name .Name }} | {{ tostring .Description | sanitizeTbl }} | + {{- if $.Settings.OutputValues -}} + {{- $sensitive := ternary .Sensitive "" .GetValue -}} + {{ printf " " }}{{ value $sensitive | sanitizeTbl }} | {{ ternary (.Sensitive) "yes" "no" }} | + {{- end -}} {{- end }} {{ end }} {{ end -}} diff --git a/internal/format/testdata/document/document-OutputValues.golden b/internal/format/testdata/document/document-OutputValues.golden index 3c4e261..97c9348 100644 --- a/internal/format/testdata/document/document-OutputValues.golden +++ b/internal/format/testdata/document/document-OutputValues.golden @@ -303,22 +303,43 @@ The following outputs are exported: Description: It's unquoted output. -Value: map["leon":"cat"] +Value: + +```json +{ + "leon": "cat" +} +``` + +Sensitive: no ### output-2 Description: It's output number two. -Value: ["jack" "lola"] +Value: + +```json +[ + "jack", + "lola" +] +``` + +Sensitive: no ### output-1 Description: It's output number one. -Value: 1 +Value: `1` + +Sensitive: no ### output-0.12 Description: terraform 0.12 only -Value: "" +Value: `` + +Sensitive: yes diff --git a/internal/format/testdata/json/json-OutputValues.golden b/internal/format/testdata/json/json-OutputValues.golden index 7101fc6..8d09aa4 100644 --- a/internal/format/testdata/json/json-OutputValues.golden +++ b/internal/format/testdata/json/json-OutputValues.golden @@ -171,7 +171,8 @@ "description": "It's unquoted output.", "value": { "leon": "cat" - } + }, + "sensitive": false }, { "name": "output-2", @@ -179,17 +180,20 @@ "value": [ "jack", "lola" - ] + ], + "sensitive": false }, { "name": "output-1", "description": "It's output number one.", - "value": 1 + "value": 1, + "sensitive": false }, { "name": "output-0.12", "description": "terraform 0.12 only", - "value": "" + "value": "", + "sensitive": true } ], "providers": [ diff --git a/internal/format/testdata/pretty/pretty-OutputValues.golden b/internal/format/testdata/pretty/pretty-OutputValues.golden index 5385cc4..7eaeca0 100644 --- a/internal/format/testdata/pretty/pretty-OutputValues.golden +++ b/internal/format/testdata/pretty/pretty-OutputValues.golden @@ -154,15 +154,20 @@ It spans over multiple lines. -output.unquoted (map["leon":"cat"]) +output.unquoted ({ + "leon": "cat" +}) It's unquoted output. -output.output-2 (["jack" "lola"]) +output.output-2 ([ + "jack", + "lola" +]) It's output number two. output.output-1 (1) It's output number one. -output.output-0.12 ("") +output.output-0.12 () terraform 0.12 only diff --git a/internal/format/testdata/table/table-OutputValues.golden b/internal/format/testdata/table/table-OutputValues.golden index f9bea46..e881b5b 100644 --- a/internal/format/testdata/table/table-OutputValues.golden +++ b/internal/format/testdata/table/table-OutputValues.golden @@ -75,9 +75,9 @@ followed by another line of text. ## Outputs -| Name | Description | Value | -|------|-------------|-------| -| unquoted | It's unquoted output. | map["leon":"cat"] | -| output-2 | It's output number two. | ["jack" "lola"] | -| output-1 | It's output number one. | 1 | -| output-0.12 | terraform 0.12 only | "" | +| Name | Description | Value | Sensitive | +|------|-------------|-------|:---------:| +| unquoted | It's unquoted output. |
{
"leon": "cat"
}
| no | +| output-2 | It's output number two. |
[
"jack",
"lola"
]
| no | +| output-1 | It's output number one. | `1` | no | +| output-0.12 | terraform 0.12 only | `` | yes | diff --git a/internal/format/testdata/yaml/yaml-OutputValues.golden b/internal/format/testdata/yaml/yaml-OutputValues.golden index 019aa02..f6fb3f1 100644 --- a/internal/format/testdata/yaml/yaml-OutputValues.golden +++ b/internal/format/testdata/yaml/yaml-OutputValues.golden @@ -163,17 +163,21 @@ outputs: description: It's unquoted output. value: leon: cat + sensitive: false - name: output-2 description: It's output number two. value: - jack - lola + sensitive: false - name: output-1 description: It's output number one. value: 1 + sensitive: false - name: output-0.12 description: terraform 0.12 only - value: "" + value: + sensitive: true providers: - name: tls alias: null diff --git a/internal/module/module.go b/internal/module/module.go index c5db982..a7f5405 100644 --- a/internal/module/module.go +++ b/internal/module/module.go @@ -113,6 +113,14 @@ func loadInputs(tfmodule *tfconfig.Module) ([]*tfconf.Input, []*tfconf.Input, [] func loadOutputs(tfmodule *tfconfig.Module, options *Options) []*tfconf.Output { outputs := make([]*tfconf.Output, 0, len(tfmodule.Outputs)) + values := make(map[string]*TerraformOutput, 0) + if options.OutputValues { + var err error + values, err = loadOutputValues(options) + if err != nil { + log.Fatal(err) + } + } for _, o := range tfmodule.Outputs { description := o.Description if description == "" { @@ -125,16 +133,14 @@ func loadOutputs(tfmodule *tfconfig.Module, options *Options) []*tfconf.Output { Filename: o.Pos.Filename, Line: o.Pos.Line, }, + ShowValue: options.OutputValues, } if options.OutputValues { - terraformOutputs, err := loadOutputValues(options) - if err != nil { - log.Fatal(err) - } - if terraformOutputs[output.Name].Sensitive { - output.Value = "" + output.Sensitive = values[output.Name].Sensitive + if values[output.Name].Sensitive { + output.Value = types.ValueOf(``) } else { - output.Value = terraformOutputs[output.Name].Value + output.Value = types.ValueOf(values[output.Name].Value) } } outputs = append(outputs, output) diff --git a/internal/types/types.go b/internal/types/types.go index 9c37655..b70e771 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -7,8 +7,9 @@ import ( "strings" ) -// Default is a default value of an input +// Value is a default value of an input or output. // it can be of several types: +// // - Nil // - String // - Empty @@ -16,15 +17,15 @@ import ( // - Bool // - List // - Map -type Default interface { +type Value interface { HasDefault() bool } // ValueOf returns actual value of a variable -// casted to 'Default' interface. This is done +// casted to 'Value' interface. This is done // to be able to attach specific marshaller func // to the type (if such a custom function was needed) -func ValueOf(v interface{}) Default { +func ValueOf(v interface{}) Value { if v == nil { return new(Nil) } diff --git a/pkg/tfconf/input.go b/pkg/tfconf/input.go index a2d6900..c85be1f 100644 --- a/pkg/tfconf/input.go +++ b/pkg/tfconf/input.go @@ -8,17 +8,17 @@ import ( // Input represents a Terraform input. type Input struct { - Name string `json:"name" yaml:"name"` - Type types.String `json:"type" yaml:"type"` - Description types.String `json:"description" yaml:"description"` - Default types.Default `json:"default" yaml:"default"` - Position Position `json:"-" yaml:"-"` + Name string `json:"name" yaml:"name"` + Type types.String `json:"type" yaml:"type"` + Description types.String `json:"description" yaml:"description"` + Default types.Value `json:"default" yaml:"default"` + Position Position `json:"-" yaml:"-"` } -// Value returns JSON representation of the 'Default' value, which is an 'interface'. +// GetValue returns JSON representation of the 'Default' value, which is an 'interface'. // If 'Default' is a primitive type, the primitive value of 'Default' will be returned // and not the JSON formatted of it. -func (i *Input) Value() string { +func (i *Input) GetValue() string { marshaled, err := json.MarshalIndent(i.Default, "", " ") if err != nil { panic(err) diff --git a/pkg/tfconf/output.go b/pkg/tfconf/output.go index 1bb53de..6c5c167 100644 --- a/pkg/tfconf/output.go +++ b/pkg/tfconf/output.go @@ -1,6 +1,9 @@ package tfconf import ( + "bytes" + "encoding/json" + "github.com/segmentio/terraform-docs/internal/types" ) @@ -8,6 +11,72 @@ import ( type Output struct { Name string `json:"name" yaml:"name"` Description types.String `json:"description" yaml:"description"` - Value interface{} `json:"value,omitempty" yaml:"value,omitempty"` + Value types.Value `json:"value,omitempty" yaml:"value,omitempty"` + Sensitive bool `json:"sensitive,omitempty" yaml:"sensitive,omitempty"` Position Position `json:"-" yaml:"-"` + ShowValue bool `json:"-" yaml:"-"` +} + +type withvalue struct { + Name string `json:"name" yaml:"name"` + Description types.String `json:"description" yaml:"description"` + Value types.Value `json:"value" yaml:"value"` + Sensitive bool `json:"sensitive" yaml:"sensitive"` + Position Position `json:"-" yaml:"-"` + ShowValue bool `json:"-" yaml:"-"` +} + +// GetValue returns JSON representation of the 'Value', which is an 'interface'. +// If 'Value' is a primitive type, the primitive value of 'Value' will be returned +// and not the JSON formatted of it. +func (o *Output) GetValue() string { + marshaled, err := json.MarshalIndent(o.Value, "", " ") + if err != nil { + panic(err) + } + if value := string(marshaled); value != "null" { + return value + } + return "" +} + +// HasDefault indicates if a Terraform output has a default value set. +func (o *Output) HasDefault() bool { + return o.Value.HasDefault() +} + +// MarshalJSON custom yaml marshal function to take +// '--output-values' flag into consideration. It means +// if the flag is not set Value and Sensitive fields +// are set to 'omitempty', otherwise if output values +// are being shown 'omitempty' gets explicitly removed +// to show even empty and false values. +func (o *Output) MarshalJSON() ([]byte, error) { + fn := func(oo interface{}) ([]byte, error) { + buf := new(bytes.Buffer) + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + if err := enc.Encode(oo); err != nil { + panic(err) + } + return buf.Bytes(), nil + } + if o.ShowValue { + return fn(withvalue(*o)) + } + return fn(*o) + +} + +// MarshalYAML custom yaml marshal function to take +// '--output-values' flag into consideration. It means +// if the flag is not set Value and Sensitive fields +// are set to 'omitempty', otherwise if output values +// are being shown 'omitempty' gets explicitly removed +// to show even empty and false values. +func (o *Output) MarshalYAML() (interface{}, error) { + if o.ShowValue { + return withvalue(*o), nil + } + return o, nil } diff --git a/pkg/tmpl/template.go b/pkg/tmpl/template.go index 751a12e..43940c1 100644 --- a/pkg/tmpl/template.go +++ b/pkg/tmpl/template.go @@ -161,24 +161,6 @@ func builtinFuncs(settings *print.Settings) template.FuncMap { "sanitizeTbl": func(s string) string { return sanitizeItemForTable(s, settings) }, - "sanitizeInterface": func(i interface{}) string { - var v string - switch x := fmt.Sprintf("%T", i); x { - case "[]interface {}": - v = fmt.Sprintf("%q", i) - case "map[string]interface {}": - v = fmt.Sprintf("%q", i) - case "float64": - v = fmt.Sprintf("%g", i) - case "int": - v = fmt.Sprintf("%d", i) - case "string": - v = fmt.Sprintf("%#v", i) - case "bool": - v = fmt.Sprintf("%t", i) - } - return v - }, } }