feat: Show sensitivity of the output value in rendered result (#207)

This commit is contained in:
Khosrow Moossavi
2020-02-26 12:08:24 -05:00
committed by GitHub
parent f55fd6cc5b
commit 4ff4582dff
16 changed files with 190 additions and 89 deletions

View File

@@ -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")

View File

@@ -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"
}
}
}
}

View File

@@ -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 "<sensitive>" .GetValue -}}
Value: {{ value $sensitive | sanitizeDoc }}
Sensitive: {{ ternary (.Sensitive) "yes" "no" }}
{{ end }}
{{ end }}
{{ end }}

View File

@@ -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{

View File

@@ -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 "<sensitive>" .GetValue }})
{{- end }}
{{ tostring .Description | trimSuffix "\n" | default "n/a" | colorize "\033[90m" }}
{{ end }}

View File

@@ -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 "<sensitive>" .GetValue -}}
{{ printf " " }}{{ value $sensitive | sanitizeTbl }} | {{ ternary (.Sensitive) "yes" "no" }} |
{{- end -}}
{{- end }}
{{ end }}
{{ end -}}

View File

@@ -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>`
Sensitive: yes

View File

@@ -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>",
"sensitive": true
}
],
"providers": [

View File

@@ -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 (<sensitive>)
terraform 0.12 only

View File

@@ -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. | <pre>{<br> "leon": "cat"<br>}</pre> | no |
| output-2 | It's output number two. | <pre>[<br> "jack",<br> "lola"<br>]</pre> | no |
| output-1 | It's output number one. | `1` | no |
| output-0.12 | terraform 0.12 only | `<sensitive>` | yes |

View File

@@ -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>
sensitive: true
providers:
- name: tls
alias: null

View File

@@ -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 = "<sensitive>"
output.Sensitive = values[output.Name].Sensitive
if values[output.Name].Sensitive {
output.Value = types.ValueOf(`<sensitive>`)
} else {
output.Value = terraformOutputs[output.Name].Value
output.Value = types.ValueOf(values[output.Name].Value)
}
}
outputs = append(outputs, output)

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
},
}
}