From 31cdef0f67699a7bb571ee5f79f6703c27bdc5be Mon Sep 17 00:00:00 2001 From: Gretchen Shelby-Dormer <35184207+gshel@users.noreply.github.com> Date: Wed, 19 Feb 2020 11:46:43 -0500 Subject: [PATCH] feat: Extract and render output values from Terraform (#191) * Allow users to pass '--output-values' Pass empty string to CreateModule.outputValuePath * Fix bug causing 'pretty' tests to fail * Link formats documentation in README (#181) Co-authored-by: Khosrow Moossavi * docs: Auto generate formats document from examples (#192) * Auto generate formats document from examples * fix lint issues * refactor: Add tfconf.Options to load Module with (#193) * Update Changelog * Allow users to pass '--output-values' Read the outputValuesPath from an env variable Use an env var with a path for '--output-values' Update Changelog Use an env var with a path for '--output-values' Update Changelog properly write output values for evrythng but yaml * Fix failing json+yaml tests * Remove unneeded code block from output.go * Remove unused import statement from output.go * Fix some noob mistakes * Create two flags to use for output value injection * Fix bug vanilla commands+build test to fail * Modify all tests and add new for outputvalues * Modify to include many output types * Optimize imports to appease checkfmt * Create loadOutputValues function * Fix linter issue * Code review fixes. Hopefully the final commit! * appease linter * Not allow sensitive output values to be injected * Remove trailing slash from tests * Remove default values from `--output-values-from` Co-authored-by: Martyn Ranyard Co-authored-by: Khosrow Moossavi --- cmd/root.go | 12 +- examples/output_values.json | 23 ++ internal/pkg/print/json/json_test.go | 82 +++++- .../json/testdata/json-OutputValues.golden | 175 ++++++++++++ .../pkg/print/markdown/document/document.go | 5 +- .../print/markdown/document/document_test.go | 105 +++++-- ...> document-IndentationBelowAllowed.golden} | 0 .../testdata/document-OutputValues.golden | 268 ++++++++++++++++++ internal/pkg/print/markdown/table/table.go | 6 +- .../pkg/print/markdown/table/table_test.go | 103 +++++-- .../table/testdata/table-OutputValues.golden | 76 +++++ internal/pkg/print/pretty/pretty.go | 6 +- internal/pkg/print/pretty/pretty_test.go | 83 +++++- .../testdata/pretty-OutputValues.golden | 147 ++++++++++ internal/pkg/print/settings.go | 5 + .../yaml/testdata/yaml-OutputValues.golden | 161 +++++++++++ internal/pkg/print/yaml/yaml_test.go | 78 ++++- internal/pkg/testutil/testing.go | 37 +-- internal/pkg/tfconf/module.go | 63 +++- internal/pkg/tfconf/options.go | 4 +- internal/pkg/tfconf/output.go | 14 +- internal/pkg/tmpl/template.go | 18 ++ 22 files changed, 1359 insertions(+), 112 deletions(-) create mode 100644 examples/output_values.json create mode 100644 internal/pkg/print/json/testdata/json-OutputValues.golden rename internal/pkg/print/markdown/document/testdata/{document-IndentationBellowAllowed.golden => document-IndentationBelowAllowed.golden} (100%) create mode 100644 internal/pkg/print/markdown/document/testdata/document-OutputValues.golden create mode 100644 internal/pkg/print/markdown/table/testdata/table-OutputValues.golden create mode 100644 internal/pkg/print/pretty/testdata/pretty-OutputValues.golden create mode 100644 internal/pkg/print/yaml/testdata/yaml-OutputValues.golden diff --git a/cmd/root.go b/cmd/root.go index 2616e17..2ab9450 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,6 +11,7 @@ import ( ) var settings = print.NewSettings() +var options = tfconf.Options{} // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ @@ -35,6 +36,8 @@ var rootCmd = &cobra.Command{ settings.ShowInputs = !noinputs settings.ShowOutputs = !nooutputs + settings.OutputValues = options.OutputValues + settings.ShowColor = !nocolor settings.SortByName = !nosort settings.ShowRequired = !norequired @@ -51,6 +54,9 @@ func init() { rootCmd.PersistentFlags().BoolVar(new(bool), "no-sort", false, "do no sort items") rootCmd.PersistentFlags().BoolVar(&settings.SortByRequired, "sort-by-required", false, "sort items by name and print required ones first") + rootCmd.PersistentFlags().BoolVar(&options.OutputValues, "output-values", false, "inject output values into outputs") + rootCmd.PersistentFlags().StringVar(&options.OutputValuesPath, "output-values-from", "", "inject output values from file into outputs") + //----------------------------- // deprecated - will be removed //----------------------------- @@ -87,10 +93,8 @@ func FormatterCmds() []*cobra.Command { } func doPrint(path string, fn func(*tfconf.Module) (string, error)) { - options := &tfconf.Options{ - Path: path, - } - module, err := tfconf.CreateModule(options) + options.Path = path + module, err := tfconf.CreateModule(&options) if err != nil { log.Fatal(err) } diff --git a/examples/output_values.json b/examples/output_values.json new file mode 100644 index 0000000..0d87d07 --- /dev/null +++ b/examples/output_values.json @@ -0,0 +1,23 @@ +{ + "output-0.12": { + "sensitive": false, + "type": "string", + "value": "" + }, + "output-1": { + "sensitive": false, + "type": "int", + "value": 1 + }, + "output-2": { + "sensitive": false, + "type": "array", + "value": ["jack", "lola"] + }, + "unquoted": { + "sensitive": false, + "type": "map", + "value": {"leon":"cat"} + } + } + \ No newline at end of file diff --git a/internal/pkg/print/json/json_test.go b/internal/pkg/print/json/json_test.go index da8b822..82b5c4a 100644 --- a/internal/pkg/print/json/json_test.go +++ b/internal/pkg/print/json/json_test.go @@ -5,6 +5,7 @@ import ( "github.com/segmentio/terraform-docs/internal/pkg/print" "github.com/segmentio/terraform-docs/internal/pkg/testutil" + "github.com/segmentio/terraform-docs/internal/pkg/tfconf" "github.com/stretchr/testify/assert" ) @@ -12,7 +13,10 @@ func TestJson(t *testing.T) { assert := assert.New(t) settings := testutil.Settings().WithSections().Build() - module, expected, err := testutil.GetExpected("json") + expected, err := testutil.GetExpected("json") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -27,7 +31,10 @@ func TestJsonSortByName(t *testing.T) { SortByName: true, }).Build() - module, expected, err := testutil.GetExpected("json-SortByName") + expected, err := testutil.GetExpected("json-SortByName") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -43,7 +50,10 @@ func TestJsonSortByRequired(t *testing.T) { SortByRequired: true, }).Build() - module, expected, err := testutil.GetExpected("json-SortByRequired") + expected, err := testutil.GetExpected("json-SortByRequired") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -61,7 +71,10 @@ func TestJsonNoHeader(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("json-NoHeader") + expected, err := testutil.GetExpected("json-NoHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -79,7 +92,10 @@ func TestJsonNoProviders(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("json-NoProviders") + expected, err := testutil.GetExpected("json-NoProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -97,7 +113,10 @@ func TestJsonNoInputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("json-NoInputs") + expected, err := testutil.GetExpected("json-NoInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -115,7 +134,10 @@ func TestJsonNoOutputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("json-NoOutputs") + expected, err := testutil.GetExpected("json-NoOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -133,7 +155,10 @@ func TestJsonOnlyHeader(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("json-OnlyHeader") + expected, err := testutil.GetExpected("json-OnlyHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -151,7 +176,10 @@ func TestJsonOnlyProviders(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("json-OnlyProviders") + expected, err := testutil.GetExpected("json-OnlyProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -169,7 +197,10 @@ func TestJsonOnlyInputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("json-OnlyInputs") + expected, err := testutil.GetExpected("json-OnlyInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -187,7 +218,10 @@ func TestJsonOnlyOutputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("json-OnlyOutputs") + expected, err := testutil.GetExpected("json-OnlyOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -202,7 +236,31 @@ func TestJsonEscapeCharacters(t *testing.T) { EscapeCharacters: true, }).Build() - module, expected, err := testutil.GetExpected("json-EscapeCharacters") + expected, err := testutil.GetExpected("json-EscapeCharacters") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) + assert.Nil(err) + + actual, err := Print(module, settings) + + assert.Nil(err) + assert.Equal(expected, actual) +} +func TestJsonOutputValues(t *testing.T) { + assert := assert.New(t) + settings := testutil.Settings().WithSections().With(&print.Settings{ + OutputValues: true, + }).Build() + + expected, err := testutil.GetExpected("json-OutputValues") + assert.Nil(err) + + options := &tfconf.Options{ + OutputValues: true, + OutputValuesPath: "output_values.json", + } + module, err := testutil.GetModule(options) assert.Nil(err) actual, err := Print(module, settings) diff --git a/internal/pkg/print/json/testdata/json-OutputValues.golden b/internal/pkg/print/json/testdata/json-OutputValues.golden new file mode 100644 index 0000000..c282438 --- /dev/null +++ b/internal/pkg/print/json/testdata/json-OutputValues.golden @@ -0,0 +1,175 @@ +{ + "header": "Usage:\n\nExample of 'foo_bar' module in `foo_bar.tf`.\n\n- list item 1\n- list item 2\n\nEven inline **formatting** in _here_ is possible.\nand some [link](https://domain.com/)\n\n* list item 3\n* list item 4\n\n```hcl\nmodule \"foo_bar\" {\n source = \"github.com/foo/bar\"\n\n id = \"1234567890\"\n name = \"baz\"\n\n zones = [\"us-east-1\", \"us-west-1\"]\n\n tags = {\n Name = \"baz\"\n Created-By = \"first.last@email.com\"\n Date-Created = \"20180101\"\n }\n}\n```\n\nHere is some trailing text after code block,\nfollowed by another line of text.\n\n| Name | Description |\n|------|-----------------|\n| Foo | Foo description |\n| Bar | Bar description |", + "inputs": [ + { + "name": "unquoted", + "type": "any", + "description": null, + "default": null + }, + { + "name": "string-3", + "type": "string", + "description": null, + "default": "" + }, + { + "name": "string-2", + "type": "string", + "description": "It's string number two.", + "default": null + }, + { + "name": "string-1", + "type": "string", + "description": "It's string number one.", + "default": "bar" + }, + { + "name": "map-3", + "type": "map", + "description": null, + "default": {} + }, + { + "name": "map-2", + "type": "map", + "description": "It's map number two.", + "default": null + }, + { + "name": "map-1", + "type": "map", + "description": "It's map number one.", + "default": { + "a": 1, + "b": 2, + "c": 3 + } + }, + { + "name": "list-3", + "type": "list", + "description": null, + "default": [] + }, + { + "name": "list-2", + "type": "list", + "description": "It's list number two.", + "default": null + }, + { + "name": "list-1", + "type": "list", + "description": "It's list number one.", + "default": [ + "a", + "b", + "c" + ] + }, + { + "name": "input_with_underscores", + "type": "any", + "description": "A variable with underscores.", + "default": null + }, + { + "name": "input-with-pipe", + "type": "string", + "description": "It includes v1 | v2 | v3", + "default": "v1" + }, + { + "name": "input-with-code-block", + "type": "list", + "description": "This is a complicated one. We need a newline. \nAnd an example in a code block\n```\ndefault = [\n \"machine rack01:neptune\"\n]\n```\n", + "default": [ + "name rack:location" + ] + }, + { + "name": "long_type", + "type": "object({\n name = string,\n foo = object({ foo = string, bar = string }),\n bar = object({ foo = string, bar = string }),\n fizz = list(string),\n buzz = list(string)\n })", + "description": "This description is itself markdown.\n\nIt spans over multiple lines.\n", + "default": { + "bar": { + "bar": "bar", + "foo": "bar" + }, + "buzz": [ + "fizz", + "buzz" + ], + "fizz": [], + "foo": { + "bar": "foo", + "foo": "foo" + }, + "name": "hello" + } + }, + { + "name": "no-escape-default-value", + "type": "string", + "description": "The description contains `something_with_underscore`. Defaults to 'VALUE_WITH_UNDERSCORE'.", + "default": "VALUE_WITH_UNDERSCORE" + }, + { + "name": "with-url", + "type": "string", + "description": "The description contains url. https://www.domain.com/foo/bar_baz.html", + "default": "" + } + ], + "outputs": [ + { + "name": "unquoted", + "description": "It's unquoted output.", + "value": { + "leon": "cat" + } + }, + { + "name": "output-2", + "description": "It's output number two.", + "value": [ + "jack", + "lola" + ] + }, + { + "name": "output-1", + "description": "It's output number one.", + "value": 1 + }, + { + "name": "output-0.12", + "description": "terraform 0.12 only", + "value": "" + } + ], + "providers": [ + { + "name": "tls", + "alias": null, + "version": null + }, + { + "name": "aws", + "alias": null, + "version": ">= 2.15.0" + }, + { + "name": "aws", + "alias": "ident", + "version": ">= 2.15.0" + }, + { + "name": "null", + "alias": null, + "version": null + } + ] +} \ No newline at end of file diff --git a/internal/pkg/print/markdown/document/document.go b/internal/pkg/print/markdown/document/document.go index f1a7855..38cc7e0 100644 --- a/internal/pkg/print/markdown/document/document.go +++ b/internal/pkg/print/markdown/document/document.go @@ -94,7 +94,10 @@ const ( {{ indent 1 }} {{ name .Name }} Description: {{ tostring .Description | sanitizeDoc }} - {{- end }} + {{ if $.Settings.OutputValues }} + Value: {{ .Value | sanitizeInterface | sanitizeDoc }} + {{ end }} + {{ end }} {{ end }} {{ end -}} ` diff --git a/internal/pkg/print/markdown/document/document_test.go b/internal/pkg/print/markdown/document/document_test.go index ee0d741..95e0725 100644 --- a/internal/pkg/print/markdown/document/document_test.go +++ b/internal/pkg/print/markdown/document/document_test.go @@ -5,6 +5,7 @@ import ( "github.com/segmentio/terraform-docs/internal/pkg/print" "github.com/segmentio/terraform-docs/internal/pkg/testutil" + "github.com/segmentio/terraform-docs/internal/pkg/tfconf" "github.com/stretchr/testify/assert" ) @@ -12,7 +13,10 @@ func TestDocument(t *testing.T) { assert := assert.New(t) settings := testutil.Settings().WithSections().Build() - module, expected, err := testutil.GetExpected("document") + expected, err := testutil.GetExpected("document") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -27,7 +31,10 @@ func TestDocumentWithRequired(t *testing.T) { ShowRequired: true, }).Build() - module, expected, err := testutil.GetExpected("document-WithRequired") + expected, err := testutil.GetExpected("document-WithRequired") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -42,7 +49,10 @@ func TestDocumentSortByName(t *testing.T) { SortByName: true, }).Build() - module, expected, err := testutil.GetExpected("document-SortByName") + expected, err := testutil.GetExpected("document-SortByName") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -58,7 +68,10 @@ func TestDocumentSortByRequired(t *testing.T) { SortByRequired: true, }).Build() - module, expected, err := testutil.GetExpected("document-SortByRequired") + expected, err := testutil.GetExpected("document-SortByRequired") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -76,7 +89,10 @@ func TestDocumentNoHeader(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("document-NoHeader") + expected, err := testutil.GetExpected("document-NoHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -94,7 +110,10 @@ func TestDocumentNoProviders(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("document-NoProviders") + expected, err := testutil.GetExpected("document-NoProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -112,7 +131,10 @@ func TestDocumentNoInputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("document-NoInputs") + expected, err := testutil.GetExpected("document-NoInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -130,7 +152,10 @@ func TestDocumentNoOutputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("document-NoOutputs") + expected, err := testutil.GetExpected("document-NoOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -148,7 +173,10 @@ func TestDocumentOnlyHeader(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("document-OnlyHeader") + expected, err := testutil.GetExpected("document-OnlyHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -166,7 +194,10 @@ func TestDocumentOnlyProviders(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("document-OnlyProviders") + expected, err := testutil.GetExpected("document-OnlyProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -184,7 +215,10 @@ func TestDocumentOnlyInputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("document-OnlyInputs") + expected, err := testutil.GetExpected("document-OnlyInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -202,7 +236,10 @@ func TestDocumentOnlyOutputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("document-OnlyOutputs") + expected, err := testutil.GetExpected("document-OnlyOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -217,7 +254,10 @@ func TestDocumentEscapeCharacters(t *testing.T) { EscapeCharacters: true, }).Build() - module, expected, err := testutil.GetExpected("document-EscapeCharacters") + expected, err := testutil.GetExpected("document-EscapeCharacters") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -226,13 +266,16 @@ func TestDocumentEscapeCharacters(t *testing.T) { assert.Equal(expected, actual) } -func TestDocumentIndentationBellowAllowed(t *testing.T) { +func TestDocumentIndentationBelowAllowed(t *testing.T) { assert := assert.New(t) settings := testutil.Settings().WithSections().With(&print.Settings{ MarkdownIndent: 0, }).Build() - module, expected, err := testutil.GetExpected("document-IndentationBellowAllowed") + expected, err := testutil.GetExpected("document-IndentationBelowAllowed") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -247,7 +290,10 @@ func TestDocumentIndentationAboveAllowed(t *testing.T) { MarkdownIndent: 10, }).Build() - module, expected, err := testutil.GetExpected("document-IndentationAboveAllowed") + expected, err := testutil.GetExpected("document-IndentationAboveAllowed") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -262,7 +308,32 @@ func TestDocumentIndentationOfFour(t *testing.T) { MarkdownIndent: 4, }).Build() - module, expected, err := testutil.GetExpected("document-IndentationOfFour") + expected, err := testutil.GetExpected("document-IndentationOfFour") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) + assert.Nil(err) + + actual, err := Print(module, settings) + + assert.Nil(err) + assert.Equal(expected, actual) +} + +func TestDocumentOutputValues(t *testing.T) { + assert := assert.New(t) + settings := testutil.Settings().WithSections().With(&print.Settings{ + OutputValues: true, + }).Build() + + expected, err := testutil.GetExpected("document-OutputValues") + assert.Nil(err) + + options := &tfconf.Options{ + OutputValues: true, + OutputValuesPath: "output_values.json", + } + module, err := testutil.GetModule(options) assert.Nil(err) actual, err := Print(module, settings) diff --git a/internal/pkg/print/markdown/document/testdata/document-IndentationBellowAllowed.golden b/internal/pkg/print/markdown/document/testdata/document-IndentationBelowAllowed.golden similarity index 100% rename from internal/pkg/print/markdown/document/testdata/document-IndentationBellowAllowed.golden rename to internal/pkg/print/markdown/document/testdata/document-IndentationBelowAllowed.golden diff --git a/internal/pkg/print/markdown/document/testdata/document-OutputValues.golden b/internal/pkg/print/markdown/document/testdata/document-OutputValues.golden new file mode 100644 index 0000000..23894aa --- /dev/null +++ b/internal/pkg/print/markdown/document/testdata/document-OutputValues.golden @@ -0,0 +1,268 @@ +Usage: + +Example of 'foo_bar' module in `foo_bar.tf`. + +- list item 1 +- list item 2 + +Even inline **formatting** in _here_ is possible. +and some [link](https://domain.com/) + +* list item 3 +* list item 4 + +```hcl +module "foo_bar" { + source = "github.com/foo/bar" + + id = "1234567890" + name = "baz" + + zones = ["us-east-1", "us-west-1"] + + tags = { + Name = "baz" + Created-By = "first.last@email.com" + Date-Created = "20180101" + } +} +``` + +Here is some trailing text after code block, +followed by another line of text. + +| Name | Description | +|------|-----------------| +| Foo | Foo description | +| Bar | Bar description | + +## Providers + +The following providers are used by this module: + +- tls + +- aws (>= 2.15.0) + +- aws.ident (>= 2.15.0) + +- null + +## Inputs + +The following input variables are supported: + +### unquoted + +Description: n/a + +Type: `any` + +Default: n/a + +### string-3 + +Description: n/a + +Type: `string` + +Default: `""` + +### string-2 + +Description: It's string number two. + +Type: `string` + +Default: n/a + +### string-1 + +Description: It's string number one. + +Type: `string` + +Default: `"bar"` + +### map-3 + +Description: n/a + +Type: `map` + +Default: `{}` + +### map-2 + +Description: It's map number two. + +Type: `map` + +Default: n/a + +### map-1 + +Description: It's map number one. + +Type: `map` + +Default: + +```json +{ + "a": 1, + "b": 2, + "c": 3 +} +``` + +### list-3 + +Description: n/a + +Type: `list` + +Default: `[]` + +### list-2 + +Description: It's list number two. + +Type: `list` + +Default: n/a + +### list-1 + +Description: It's list number one. + +Type: `list` + +Default: + +```json +[ + "a", + "b", + "c" +] +``` + +### input_with_underscores + +Description: A variable with underscores. + +Type: `any` + +Default: n/a + +### input-with-pipe + +Description: It includes v1 \| v2 \| v3 + +Type: `string` + +Default: `"v1"` + +### input-with-code-block + +Description: This is a complicated one. We need a newline. +And an example in a code block +``` +default = [ + "machine rack01:neptune" +] +``` + +Type: `list` + +Default: + +```json +[ + "name rack:location" +] +``` + +### long_type + +Description: This description is itself markdown. + +It spans over multiple lines. + +Type: + +```hcl +object({ + name = string, + foo = object({ foo = string, bar = string }), + bar = object({ foo = string, bar = string }), + fizz = list(string), + buzz = list(string) + }) +``` + +Default: + +```json +{ + "bar": { + "bar": "bar", + "foo": "bar" + }, + "buzz": [ + "fizz", + "buzz" + ], + "fizz": [], + "foo": { + "bar": "foo", + "foo": "foo" + }, + "name": "hello" +} +``` + +### no-escape-default-value + +Description: The description contains `something_with_underscore`. Defaults to 'VALUE_WITH_UNDERSCORE'. + +Type: `string` + +Default: `"VALUE_WITH_UNDERSCORE"` + +### with-url + +Description: The description contains url. https://www.domain.com/foo/bar_baz.html + +Type: `string` + +Default: `""` + +## Outputs + +The following outputs are exported: + +### unquoted + +Description: It's unquoted output. + +Value: map["leon":"cat"] + +### output-2 + +Description: It's output number two. + +Value: ["jack" "lola"] + +### output-1 + +Description: It's output number one. + +Value: 1 + +### output-0.12 + +Description: terraform 0.12 only + +Value: "" diff --git a/internal/pkg/print/markdown/table/table.go b/internal/pkg/print/markdown/table/table.go index 37845d6..e9f0977 100644 --- a/internal/pkg/print/markdown/table/table.go +++ b/internal/pkg/print/markdown/table/table.go @@ -64,10 +64,10 @@ const ( {{ if not .Module.Outputs }} No output. {{ else }} - | Name | Description | - |------|-------------| + | Name | Description |{{ if $.Settings.OutputValues }} Value |{{ end }} + |------|-------------|{{ if $.Settings.OutputValues }}-------|{{ end }} {{- range .Module.Outputs }} - | {{ name .Name }} | {{ tostring .Description | sanitizeTbl }} | + | {{ name .Name }} | {{ tostring .Description | sanitizeTbl }} |{{ if $.Settings.OutputValues }} {{ .Value | sanitizeInterface | sanitizeTbl }} |{{ end }} {{- end }} {{ end }} {{ end -}} diff --git a/internal/pkg/print/markdown/table/table_test.go b/internal/pkg/print/markdown/table/table_test.go index 818628f..d105dbe 100644 --- a/internal/pkg/print/markdown/table/table_test.go +++ b/internal/pkg/print/markdown/table/table_test.go @@ -5,6 +5,7 @@ import ( "github.com/segmentio/terraform-docs/internal/pkg/print" "github.com/segmentio/terraform-docs/internal/pkg/testutil" + "github.com/segmentio/terraform-docs/internal/pkg/tfconf" "github.com/stretchr/testify/assert" ) @@ -12,7 +13,10 @@ func TestTable(t *testing.T) { assert := assert.New(t) settings := testutil.Settings().WithSections().Build() - module, expected, err := testutil.GetExpected("table") + expected, err := testutil.GetExpected("table") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -27,7 +31,10 @@ func TestTableWithRequired(t *testing.T) { ShowRequired: true, }).Build() - module, expected, err := testutil.GetExpected("table-WithRequired") + expected, err := testutil.GetExpected("table-WithRequired") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -42,7 +49,10 @@ func TestTableSortByName(t *testing.T) { SortByName: true, }).Build() - module, expected, err := testutil.GetExpected("table-SortByName") + expected, err := testutil.GetExpected("table-SortByName") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -58,7 +68,10 @@ func TestTableSortByRequired(t *testing.T) { SortByRequired: true, }).Build() - module, expected, err := testutil.GetExpected("table-SortByRequired") + expected, err := testutil.GetExpected("table-SortByRequired") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -76,7 +89,10 @@ func TestTableNoHeader(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("table-NoHeader") + expected, err := testutil.GetExpected("table-NoHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -94,7 +110,10 @@ func TestTableNoProviders(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("table-NoProviders") + expected, err := testutil.GetExpected("table-NoProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -112,7 +131,10 @@ func TestTableNoInputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("table-NoInputs") + expected, err := testutil.GetExpected("table-NoInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -130,7 +152,10 @@ func TestTableNoOutputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("table-NoOutputs") + expected, err := testutil.GetExpected("table-NoOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -148,7 +173,10 @@ func TestTableOnlyHeader(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("table-OnlyHeader") + expected, err := testutil.GetExpected("table-OnlyHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -166,7 +194,10 @@ func TestTableOnlyProviders(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("table-OnlyProviders") + expected, err := testutil.GetExpected("table-OnlyProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -184,7 +215,10 @@ func TestTableOnlyInputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("table-OnlyInputs") + expected, err := testutil.GetExpected("table-OnlyInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -202,7 +236,10 @@ func TestTableOnlyOutputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("table-OnlyOutputs") + expected, err := testutil.GetExpected("table-OnlyOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -217,7 +254,10 @@ func TestTableEscapeCharacters(t *testing.T) { EscapeCharacters: true, }).Build() - module, expected, err := testutil.GetExpected("table-EscapeCharacters") + expected, err := testutil.GetExpected("table-EscapeCharacters") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -232,7 +272,10 @@ func TestTableIndentationBellowAllowed(t *testing.T) { MarkdownIndent: 0, }).Build() - module, expected, err := testutil.GetExpected("table-IndentationBellowAllowed") + expected, err := testutil.GetExpected("table-IndentationBellowAllowed") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -247,7 +290,10 @@ func TestTableIndentationAboveAllowed(t *testing.T) { MarkdownIndent: 10, }).Build() - module, expected, err := testutil.GetExpected("table-IndentationAboveAllowed") + expected, err := testutil.GetExpected("table-IndentationAboveAllowed") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -262,7 +308,32 @@ func TestTableIndentationOfFour(t *testing.T) { MarkdownIndent: 4, }).Build() - module, expected, err := testutil.GetExpected("table-IndentationOfFour") + expected, err := testutil.GetExpected("table-IndentationOfFour") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) + assert.Nil(err) + + actual, err := Print(module, settings) + + assert.Nil(err) + assert.Equal(expected, actual) +} + +func TestTableOutputValues(t *testing.T) { + assert := assert.New(t) + settings := testutil.Settings().WithSections().With(&print.Settings{ + OutputValues: true, + }).Build() + + expected, err := testutil.GetExpected("table-OutputValues") + assert.Nil(err) + + options := &tfconf.Options{ + OutputValues: true, + OutputValuesPath: "output_values.json", + } + module, err := testutil.GetModule(options) assert.Nil(err) actual, err := Print(module, settings) diff --git a/internal/pkg/print/markdown/table/testdata/table-OutputValues.golden b/internal/pkg/print/markdown/table/testdata/table-OutputValues.golden new file mode 100644 index 0000000..5180e7b --- /dev/null +++ b/internal/pkg/print/markdown/table/testdata/table-OutputValues.golden @@ -0,0 +1,76 @@ +Usage: + +Example of 'foo_bar' module in `foo_bar.tf`. + +- list item 1 +- list item 2 + +Even inline **formatting** in _here_ is possible. +and some [link](https://domain.com/) + +* list item 3 +* list item 4 + +```hcl +module "foo_bar" { + source = "github.com/foo/bar" + + id = "1234567890" + name = "baz" + + zones = ["us-east-1", "us-west-1"] + + tags = { + Name = "baz" + Created-By = "first.last@email.com" + Date-Created = "20180101" + } +} +``` + +Here is some trailing text after code block, +followed by another line of text. + +| Name | Description | +|------|-----------------| +| Foo | Foo description | +| Bar | Bar description | + +## Providers + +| Name | Version | +|------|---------| +| tls | n/a | +| aws | >= 2.15.0 | +| aws.ident | >= 2.15.0 | +| null | n/a | + +## Inputs + +| Name | Description | Type | Default | +|------|-------------|------|---------| +| unquoted | n/a | `any` | n/a | +| string-3 | n/a | `string` | `""` | +| string-2 | It's string number two. | `string` | n/a | +| string-1 | It's string number one. | `string` | `"bar"` | +| map-3 | n/a | `map` | `{}` | +| map-2 | It's map number two. | `map` | n/a | +| map-1 | It's map number one. | `map` |
{
"a": 1,
"b": 2,
"c": 3
}
| +| list-3 | n/a | `list` | `[]` | +| list-2 | It's list number two. | `list` | n/a | +| list-1 | It's list number one. | `list` |
[
"a",
"b",
"c"
]
| +| input_with_underscores | A variable with underscores. | `any` | n/a | +| input-with-pipe | It includes v1 \| v2 \| v3 | `string` | `"v1"` | +| input-with-code-block | This is a complicated one. We need a newline.
And an example in a code block
default     = [
"machine rack01:neptune"
]
| `list` |
[
"name rack:location"
]
| +| long_type | This description is itself markdown.

It spans over multiple lines. |
object({
name = string,
foo = object({ foo = string, bar = string }),
bar = object({ foo = string, bar = string }),
fizz = list(string),
buzz = list(string)
})
|
{
"bar": {
"bar": "bar",
"foo": "bar"
},
"buzz": [
"fizz",
"buzz"
],
"fizz": [],
"foo": {
"bar": "foo",
"foo": "foo"
},
"name": "hello"
}
| +| no-escape-default-value | The description contains `something_with_underscore`. Defaults to 'VALUE_WITH_UNDERSCORE'. | `string` | `"VALUE_WITH_UNDERSCORE"` | +| with-url | The description contains url. https://www.domain.com/foo/bar_baz.html | `string` | `""` | + +## 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 | "" | diff --git a/internal/pkg/print/pretty/pretty.go b/internal/pkg/print/pretty/pretty.go index ae0f63d..ff9f296 100644 --- a/internal/pkg/print/pretty/pretty.go +++ b/internal/pkg/print/pretty/pretty.go @@ -52,7 +52,11 @@ const ( {{- printf "\n" -}} {{- range . }} {{ printf "output.%s" .Name | colorize "\033[36m" }} - {{ tostring .Description | trimSuffix "\n" | default "n/a" | colorize "\033[90m" }} + {{- if $.Settings.OutputValues -}} + {{- printf " " -}} + ({{ sanitizeInterface .Value }}) + {{- end }} + {{ tostring .Description | trimSuffix "\n" | default "n/a" | colorize "\033[90m" }} {{ end }} {{ end -}} {{ end -}} diff --git a/internal/pkg/print/pretty/pretty_test.go b/internal/pkg/print/pretty/pretty_test.go index 998ccb1..b76ae9d 100644 --- a/internal/pkg/print/pretty/pretty_test.go +++ b/internal/pkg/print/pretty/pretty_test.go @@ -5,6 +5,7 @@ import ( "github.com/segmentio/terraform-docs/internal/pkg/print" "github.com/segmentio/terraform-docs/internal/pkg/testutil" + "github.com/segmentio/terraform-docs/internal/pkg/tfconf" "github.com/stretchr/testify/assert" ) @@ -12,7 +13,10 @@ func TestPretty(t *testing.T) { assert := assert.New(t) settings := testutil.Settings().WithSections().WithColor().Build() - module, expected, err := testutil.GetExpected("pretty") + expected, err := testutil.GetExpected("pretty") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -27,7 +31,10 @@ func TestPrettySortByName(t *testing.T) { SortByName: true, }).Build() - module, expected, err := testutil.GetExpected("pretty-SortByName") + expected, err := testutil.GetExpected("pretty-SortByName") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -43,7 +50,10 @@ func TestPrettySortByRequired(t *testing.T) { SortByRequired: true, }).Build() - module, expected, err := testutil.GetExpected("pretty-SortByRequired") + expected, err := testutil.GetExpected("pretty-SortByRequired") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -61,7 +71,10 @@ func TestPrettyNoHeader(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("pretty-NoHeader") + expected, err := testutil.GetExpected("pretty-NoHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -79,7 +92,10 @@ func TestPrettyNoProviders(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("pretty-NoProviders") + expected, err := testutil.GetExpected("pretty-NoProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -97,7 +113,10 @@ func TestPrettyNoInputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("pretty-NoInputs") + expected, err := testutil.GetExpected("pretty-NoInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -115,7 +134,10 @@ func TestPrettyNoOutputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("pretty-NoOutputs") + expected, err := testutil.GetExpected("pretty-NoOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -133,7 +155,10 @@ func TestPrettyOnlyHeader(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("pretty-OnlyHeader") + expected, err := testutil.GetExpected("pretty-OnlyHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -151,7 +176,10 @@ func TestPrettyOnlyProviders(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("pretty-OnlyProviders") + expected, err := testutil.GetExpected("pretty-OnlyProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -169,7 +197,10 @@ func TestPrettyOnlyInputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("pretty-OnlyInputs") + expected, err := testutil.GetExpected("pretty-OnlyInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -187,7 +218,10 @@ func TestPrettyOnlyOutputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("pretty-OnlyOutputs") + expected, err := testutil.GetExpected("pretty-OnlyOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -202,7 +236,32 @@ func TestPrettyNoColor(t *testing.T) { ShowColor: false, }).Build() - module, expected, err := testutil.GetExpected("pretty-NoColor") + expected, err := testutil.GetExpected("pretty-NoColor") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) + assert.Nil(err) + + actual, err := Print(module, settings) + + assert.Nil(err) + assert.Equal(expected, actual) +} + +func TestPrettyOutputValues(t *testing.T) { + assert := assert.New(t) + settings := testutil.Settings().WithSections().WithColor().With(&print.Settings{ + OutputValues: true, + }).Build() + + expected, err := testutil.GetExpected("pretty-OutputValues") + assert.Nil(err) + + options := &tfconf.Options{ + OutputValues: true, + OutputValuesPath: "output_values.json", + } + module, err := testutil.GetModule(options) assert.Nil(err) actual, err := Print(module, settings) diff --git a/internal/pkg/print/pretty/testdata/pretty-OutputValues.golden b/internal/pkg/print/pretty/testdata/pretty-OutputValues.golden new file mode 100644 index 0000000..e0088a2 --- /dev/null +++ b/internal/pkg/print/pretty/testdata/pretty-OutputValues.golden @@ -0,0 +1,147 @@ + + +Usage: + +Example of 'foo_bar' module in `foo_bar.tf`. + +- list item 1 +- list item 2 + +Even inline **formatting** in _here_ is possible. +and some [link](https://domain.com/) + +* list item 3 +* list item 4 + +```hcl +module "foo_bar" { + source = "github.com/foo/bar" + + id = "1234567890" + name = "baz" + + zones = ["us-east-1", "us-west-1"] + + tags = { + Name = "baz" + Created-By = "first.last@email.com" + Date-Created = "20180101" + } +} +``` + +Here is some trailing text after code block, +followed by another line of text. + +| Name | Description | +|------|-----------------| +| Foo | Foo description | +| Bar | Bar description | + + + +provider.tls + +provider.aws (>= 2.15.0) + +provider.aws.ident (>= 2.15.0) + +provider.null + + + +input.unquoted (required) +n/a + +input.string-3 ("") +n/a + +input.string-2 (required) +It's string number two. + +input.string-1 ("bar") +It's string number one. + +input.map-3 ({}) +n/a + +input.map-2 (required) +It's map number two. + +input.map-1 ({ + "a": 1, + "b": 2, + "c": 3 +}) +It's map number one. + +input.list-3 ([]) +n/a + +input.list-2 (required) +It's list number two. + +input.list-1 ([ + "a", + "b", + "c" +]) +It's list number one. + +input.input_with_underscores (required) +A variable with underscores. + +input.input-with-pipe ("v1") +It includes v1 | v2 | v3 + +input.input-with-code-block ([ + "name rack:location" +]) +This is a complicated one. We need a newline. +And an example in a code block +``` +default = [ + "machine rack01:neptune" +] +``` + +input.long_type ({ + "bar": { + "bar": "bar", + "foo": "bar" + }, + "buzz": [ + "fizz", + "buzz" + ], + "fizz": [], + "foo": { + "bar": "foo", + "foo": "foo" + }, + "name": "hello" +}) +This description is itself markdown. + +It spans over multiple lines. + +input.no-escape-default-value ("VALUE_WITH_UNDERSCORE") +The description contains `something_with_underscore`. Defaults to 'VALUE_WITH_UNDERSCORE'. + +input.with-url ("") +The description contains url. https://www.domain.com/foo/bar_baz.html + + + +output.unquoted (map["leon":"cat"]) +It's unquoted output. + +output.output-2 (["jack" "lola"]) +It's output number two. + +output.output-1 (1) +It's output number one. + +output.output-0.12 ("") +terraform 0.12 only + diff --git a/internal/pkg/print/settings.go b/internal/pkg/print/settings.go index bbabb9f..6c2224b 100644 --- a/internal/pkg/print/settings.go +++ b/internal/pkg/print/settings.go @@ -14,6 +14,10 @@ type Settings struct { // scope: Markdown MarkdownIndent int + // OutputValues ailrghaekrgj + // scope: Global + OutputValues bool + // ShowColor print "colorized" version of result in the terminal (default: true) // scope: Pretty ShowColor bool @@ -53,6 +57,7 @@ func NewSettings() *Settings { EscapeCharacters: true, EscapePipe: true, MarkdownIndent: 2, + OutputValues: false, ShowColor: true, ShowHeader: true, ShowInputs: true, diff --git a/internal/pkg/print/yaml/testdata/yaml-OutputValues.golden b/internal/pkg/print/yaml/testdata/yaml-OutputValues.golden new file mode 100644 index 0000000..b7006a0 --- /dev/null +++ b/internal/pkg/print/yaml/testdata/yaml-OutputValues.golden @@ -0,0 +1,161 @@ +header: |- + Usage: + + Example of 'foo_bar' module in `foo_bar.tf`. + + - list item 1 + - list item 2 + + Even inline **formatting** in _here_ is possible. + and some [link](https://domain.com/) + + * list item 3 + * list item 4 + + ```hcl + module "foo_bar" { + source = "github.com/foo/bar" + + id = "1234567890" + name = "baz" + + zones = ["us-east-1", "us-west-1"] + + tags = { + Name = "baz" + Created-By = "first.last@email.com" + Date-Created = "20180101" + } + } + ``` + + Here is some trailing text after code block, + followed by another line of text. + + | Name | Description | + |------|-----------------| + | Foo | Foo description | + | Bar | Bar description | +inputs: +- name: unquoted + type: any + description: "" + default: null +- name: string-3 + type: string + description: "" + default: "" +- name: string-2 + type: string + description: It's string number two. + default: null +- name: string-1 + type: string + description: It's string number one. + default: bar +- name: map-3 + type: map + description: "" + default: {} +- name: map-2 + type: map + description: It's map number two. + default: null +- name: map-1 + type: map + description: It's map number one. + default: + a: 1 + b: 2 + c: 3 +- name: list-3 + type: list + description: "" + default: [] +- name: list-2 + type: list + description: It's list number two. + default: null +- name: list-1 + type: list + description: It's list number one. + default: + - a + - b + - c +- name: input_with_underscores + type: any + description: A variable with underscores. + default: null +- name: input-with-pipe + type: string + description: It includes v1 | v2 | v3 + default: v1 +- name: input-with-code-block + type: list + description: "This is a complicated one. We need a newline. \nAnd an example in + a code block\n```\ndefault = [\n \"machine rack01:neptune\"\n]\n```\n" + default: + - name rack:location +- name: long_type + type: |- + object({ + name = string, + foo = object({ foo = string, bar = string }), + bar = object({ foo = string, bar = string }), + fizz = list(string), + buzz = list(string) + }) + description: | + This description is itself markdown. + + It spans over multiple lines. + default: + bar: + bar: bar + foo: bar + buzz: + - fizz + - buzz + fizz: [] + foo: + bar: foo + foo: foo + name: hello +- name: no-escape-default-value + type: string + description: The description contains `something_with_underscore`. Defaults to 'VALUE_WITH_UNDERSCORE'. + default: VALUE_WITH_UNDERSCORE +- name: with-url + type: string + description: The description contains url. https://www.domain.com/foo/bar_baz.html + default: "" +outputs: +- name: unquoted + description: It's unquoted output. + value: + leon: cat +- name: output-2 + description: It's output number two. + value: + - jack + - lola +- name: output-1 + description: It's output number one. + value: 1 +- name: output-0.12 + description: terraform 0.12 only + value: "" +providers: +- name: tls + alias: "" + version: "" +- name: aws + alias: "" + version: '>= 2.15.0' +- name: aws + alias: ident + version: '>= 2.15.0' +- name: "null" + alias: "" + version: "" \ No newline at end of file diff --git a/internal/pkg/print/yaml/yaml_test.go b/internal/pkg/print/yaml/yaml_test.go index 93602ad..2d0705d 100644 --- a/internal/pkg/print/yaml/yaml_test.go +++ b/internal/pkg/print/yaml/yaml_test.go @@ -5,6 +5,7 @@ import ( "github.com/segmentio/terraform-docs/internal/pkg/print" "github.com/segmentio/terraform-docs/internal/pkg/testutil" + "github.com/segmentio/terraform-docs/internal/pkg/tfconf" "github.com/stretchr/testify/assert" ) @@ -12,7 +13,10 @@ func TestYaml(t *testing.T) { assert := assert.New(t) settings := testutil.Settings().WithSections().Build() - module, expected, err := testutil.GetExpected("yaml") + expected, err := testutil.GetExpected("yaml") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -27,7 +31,10 @@ func TestYamlSortByName(t *testing.T) { SortByName: true, }).Build() - module, expected, err := testutil.GetExpected("yaml-SortByName") + expected, err := testutil.GetExpected("yaml-SortByName") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -43,7 +50,10 @@ func TestYamlSortByRequired(t *testing.T) { SortByRequired: true, }).Build() - module, expected, err := testutil.GetExpected("yaml-SortByRequired") + expected, err := testutil.GetExpected("yaml-SortByRequired") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -61,7 +71,10 @@ func TestYamlNoHeader(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("yaml-NoHeader") + expected, err := testutil.GetExpected("yaml-NoHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -79,7 +92,10 @@ func TestYamlNoProviders(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("yaml-NoProviders") + expected, err := testutil.GetExpected("yaml-NoProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -97,7 +113,10 @@ func TestYamlNoInputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("yaml-NoInputs") + expected, err := testutil.GetExpected("yaml-NoInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -115,7 +134,10 @@ func TestYamlNoOutputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("yaml-NoOutputs") + expected, err := testutil.GetExpected("yaml-NoOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -133,7 +155,10 @@ func TestYamlOnlyHeader(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("yaml-OnlyHeader") + expected, err := testutil.GetExpected("yaml-OnlyHeader") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -151,7 +176,10 @@ func TestYamlOnlyProviders(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("yaml-OnlyProviders") + expected, err := testutil.GetExpected("yaml-OnlyProviders") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -169,7 +197,10 @@ func TestYamlOnlyInputs(t *testing.T) { ShowOutputs: false, }).Build() - module, expected, err := testutil.GetExpected("yaml-OnlyInputs") + expected, err := testutil.GetExpected("yaml-OnlyInputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) assert.Nil(err) actual, err := Print(module, settings) @@ -187,7 +218,32 @@ func TestYamlOnlyOutputs(t *testing.T) { ShowOutputs: true, }).Build() - module, expected, err := testutil.GetExpected("yaml-OnlyOutputs") + expected, err := testutil.GetExpected("yaml-OnlyOutputs") + assert.Nil(err) + + module, err := testutil.GetModule(new(tfconf.Options)) + assert.Nil(err) + + actual, err := Print(module, settings) + + assert.Nil(err) + assert.Equal(expected, actual) +} + +func TestYamlOutputValues(t *testing.T) { + assert := assert.New(t) + settings := testutil.Settings().WithSections().With(&print.Settings{ + OutputValues: true, + }).Build() + + expected, err := testutil.GetExpected("yaml-OutputValues") + assert.Nil(err) + + options := &tfconf.Options{ + OutputValues: true, + OutputValuesPath: "output_values.json", + } + module, err := testutil.GetModule(options) assert.Nil(err) actual, err := Print(module, settings) diff --git a/internal/pkg/testutil/testing.go b/internal/pkg/testutil/testing.go index 536b790..56b7b42 100644 --- a/internal/pkg/testutil/testing.go +++ b/internal/pkg/testutil/testing.go @@ -9,23 +9,33 @@ import ( "github.com/segmentio/terraform-docs/internal/pkg/tfconf" ) -// GetExpected returns 'example' Module and expected Golden file content -func GetExpected(goldenFile string) (*tfconf.Module, string, error) { +// GetModule returns what is generated by the test params +func GetModule(options *tfconf.Options) (*tfconf.Module, error) { path, err := getExampleFolder() if err != nil { - return nil, "", err + return nil, err } - options := &tfconf.Options{ - Path: path, + options.Path = path + + if options.OutputValues { + options.OutputValuesPath = filepath.Join(path, options.OutputValuesPath) } + module, err := tfconf.CreateModule(options) if err != nil { - return nil, "", err + return nil, err } + return module, err +} - expected, err := readGoldenFile(goldenFile) - - return module, expected, err +// GetExpected returns 'example' Module and expected Golden file content +func GetExpected(name string) (string, error) { + path := filepath.Join(testDataPath(), name+".golden") + bytes, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + return string(bytes), nil } func getExampleFolder() (string, error) { @@ -37,15 +47,6 @@ func getExampleFolder() (string, error) { return path, nil } -func readGoldenFile(name string) (string, error) { - path := filepath.Join(testDataPath(), name+".golden") - bytes, err := ioutil.ReadFile(path) - if err != nil { - return "", err - } - return string(bytes), nil -} - func testDataPath() string { return filepath.Join("testdata") } diff --git a/internal/pkg/tfconf/module.go b/internal/pkg/tfconf/module.go index 918974c..7d9665a 100644 --- a/internal/pkg/tfconf/module.go +++ b/internal/pkg/tfconf/module.go @@ -1,8 +1,11 @@ package tfconf import ( + "encoding/json" "fmt" + "io/ioutil" "log" + "os/exec" "sort" "strings" @@ -22,8 +25,6 @@ type Module struct { Providers []*Provider `json:"providers" yaml:"providers"` RequiredInputs []*Input `json:"-" yaml:"-"` OptionalInputs []*Input `json:"-" yaml:"-"` - - options *Options } // HasInputs indicates if the document has inputs. @@ -68,7 +69,7 @@ func (m *Module) Sort(settings *print.Settings) { } // CreateModule returns new instance of Module with all the inputs and -// outputs dircoverd from provided 'path' containing Terraform config +// outputs discovered from provided 'path' containing Terraform config func CreateModule(options *Options) (*Module, error) { mod := loadModule(options.Path) @@ -122,20 +123,34 @@ func CreateModule(options *Options) (*Module, error) { } } + // output module var outputs = make([]*Output, 0, len(mod.Outputs)) - for _, output := range mod.Outputs { - outputDescription := output.Description + for _, o := range mod.Outputs { + outputDescription := o.Description if outputDescription == "" { - outputDescription = readComment(output.Pos.Filename, output.Pos.Line-1) + outputDescription = readComment(o.Pos.Filename, o.Pos.Line-1) } - outputs = append(outputs, &Output{ - Name: output.Name, + output := &Output{ + Name: o.Name, Description: String(outputDescription), Position: Position{ - Filename: output.Pos.Filename, - Line: output.Pos.Line, + Filename: o.Pos.Filename, + Line: o.Pos.Line, }, - }) + } + if options.OutputValues { + terraformOutputs, err := loadOutputValues(options) + if err != nil { + log.Fatal(err) + } + + if terraformOutputs[output.Name].Sensitive { + output.Value = "" + } else { + output.Value = terraformOutputs[output.Name].Value + } + } + outputs = append(outputs, output) } var providerSet = loadProviders(mod.RequiredProviders, mod.ManagedResources, mod.DataResources) @@ -151,8 +166,6 @@ func CreateModule(options *Options) (*Module, error) { Providers: providers, RequiredInputs: requiredInputs, OptionalInputs: optionalInputs, - - options: options, } return module, nil } @@ -187,3 +200,27 @@ func loadProviders(requiredProviders map[string]*tfconfig.ProviderRequirement, r } return providers } + +func loadOutputValues(options *Options) (map[string]*TerraformOutput, error) { + var out []byte + var err error + + if options.OutputValuesPath == "" { + cmd := exec.Command("terraform", "output", "-json") + cmd.Dir = options.Path + if out, err = cmd.Output(); err != nil { + return nil, fmt.Errorf("caught error while reading the terraform outputs: %v", err) + } + } else { + if out, err = ioutil.ReadFile(options.OutputValuesPath); err != nil { + return nil, fmt.Errorf("caught error while reading the terraform outputs file at %s: %v", options.OutputValuesPath, err) + } + } + + var terraformOutputs map[string]*TerraformOutput + err = json.Unmarshal(out, &terraformOutputs) + if err != nil { + return nil, err + } + return terraformOutputs, err +} diff --git a/internal/pkg/tfconf/options.go b/internal/pkg/tfconf/options.go index e47bdea..61ce408 100644 --- a/internal/pkg/tfconf/options.go +++ b/internal/pkg/tfconf/options.go @@ -2,5 +2,7 @@ package tfconf // Options contains required options to load a Module from path type Options struct { - Path string + Path string + OutputValues bool + OutputValuesPath string } diff --git a/internal/pkg/tfconf/output.go b/internal/pkg/tfconf/output.go index 57cdb22..0f8abca 100644 --- a/internal/pkg/tfconf/output.go +++ b/internal/pkg/tfconf/output.go @@ -2,9 +2,10 @@ package tfconf // Output represents a Terraform output. type Output struct { - Name string `json:"name" yaml:"name"` - Description String `json:"description" yaml:"description"` - Position Position `json:"-" yaml:"-"` + Name string `json:"name" yaml:"name"` + Description String `json:"description" yaml:"description"` + Value interface{} `json:"value,omitempty" yaml:"value,omitempty"` + Position Position `json:"-" yaml:"-"` } type outputsSortedByName []*Output @@ -34,3 +35,10 @@ func (a outputsSortedByPosition) Swap(i, j int) { func (a outputsSortedByPosition) Less(i, j int) bool { return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line } + +// TerraformOutput is used for unmarshalling `terraform outputs --json` into +type TerraformOutput struct { + Sensitive bool `json:"sensitive"` + Type interface{} `json:"type"` + Value interface{} `json:"value"` +} diff --git a/internal/pkg/tmpl/template.go b/internal/pkg/tmpl/template.go index 9063c2e..0b21e70 100644 --- a/internal/pkg/tmpl/template.go +++ b/internal/pkg/tmpl/template.go @@ -160,6 +160,24 @@ func builtinFuncs(settings *print.Settings) map[string]interface{} { "sanitizeTbl": func(s string) string { return markdown.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 + }, } }