Skip to content

Commit 41d790d

Browse files
authored
Merge pull request #11 from mdb/html-output
add support for '-output html' option
2 parents 4783e46 + 36454ce commit 41d790d

File tree

5 files changed

+268
-28
lines changed

5 files changed

+268
-28
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = 0.1.0
1+
VERSION = 0.1.1
22
SOURCE = ./...
33

44
.PHONY: help \

README.md

Lines changed: 147 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ A CLI to generate Terraform outputs documentation.
77
## What's the use case?
88

99
`terraputs` analyzes the contents of a [terraform state](https://www.terraform.io/docs/language/state/index.html)
10-
file and generates Markdown documentation from its [outputs](https://www.terraform.io/docs/language/values/outputs.html).
10+
file and generates Markdown or HTML documentation from its [outputs](https://www.terraform.io/docs/language/values/outputs.html).
1111

1212
A common workflow might execute `terraputs -state $(terraform show -json) > outputs.md` after each
1313
invocation of `terraform apply`, then commit `outputs.md` to source control or publish its contents to
@@ -17,6 +17,19 @@ offer up-to-date documentation about resources managed by a Terraform project.
1717

1818
## Usage
1919

20+
```
21+
terraputs -h
22+
Usage of terraputs:
23+
-heading string
24+
Optional; the heading text for use in the printed output. (default "Outputs")
25+
-output string
26+
Optional; the output format. Supported values: md, html. (default "md")
27+
-state string
28+
Optional; the state JSON output by 'terraform show -json'. Read from stdin if omitted
29+
-state-file string
30+
Optional; the path to a local file containing 'terraform show -json' output
31+
```
32+
2033
A few typical usage examples:
2134

2235
```
@@ -31,7 +44,11 @@ terraform show -json | terraputs -heading "Terraform Outputs"
3144
terraputs < terraform.tfstate
3245
```
3346

34-
Example output:
47+
### Results examples
48+
49+
<details>
50+
51+
<summary>Example markdown output</summary>
3552

3653
```
3754
# Terraform Outputs
@@ -47,20 +64,15 @@ Terraform state outputs.
4764
| a_string | foo | string
4865
```
4966

50-
## More options
67+
</details>
5168

52-
```
53-
terraputs -h
54-
Usage of terraputs:
55-
-heading string
56-
Optional; the heading text for use in the printed markdown (default "Outputs")
57-
-state string
58-
Optional; the state JSON output by 'terraform show -json', read from stdin if omitted
59-
-state-file string
60-
Optional; the path to a local file containing 'terraform show -json' output
61-
```
69+
<details>
70+
71+
<summary>Markdown output rendered via GitHub-flavored markdown</summary>
72+
73+
# Terraform Outputs
6274

63-
## Example output table formatted by GitHub
75+
Terraform state outputs.
6476

6577
| Output | Value | Type
6678
| --- | --- | --- |
@@ -70,10 +82,125 @@ Usage of terraputs:
7082
| a_sensitive_value | sensitive; redacted | string
7183
| a_string | foo | string
7284

73-
## TODO
85+
</details>
86+
87+
<details>
88+
89+
<summary>Example HTML output</summary>
90+
91+
```html
92+
<h2>Outputs</h2>
93+
<p>Terraform state outputs.</p>
94+
<table>
95+
<tr>
96+
<th>Output</th>
97+
<th>Value</th>
98+
<th>Type</th>
99+
</tr>
100+
101+
<tr>
102+
<td>a_basic_map</td>
103+
<td><pre>{
104+
"foo": "bar",
105+
"number": 42
106+
}</pre></td>
107+
<td>map[string]interface {}</td>
108+
</tr>
109+
110+
<tr>
111+
<td>a_list</td>
112+
<td><pre>[
113+
"foo",
114+
"bar"
115+
]</pre></td>
116+
<td>[]interface {}</td>
117+
</tr>
118+
119+
<tr>
120+
<td>a_nested_map</td>
121+
<td><pre>{
122+
"baz": {
123+
"bar": "baz",
124+
"id": "123"
125+
},
126+
"foo": "bar",
127+
"number": 42
128+
}</pre></td>
129+
<td>map[string]interface {}</td>
130+
</tr>
131+
132+
<tr>
133+
<td>a_sensitive_value</td>
134+
<td><pre>sensitive; redacted</pre></td>
135+
<td>string</td>
136+
</tr>
137+
138+
<tr>
139+
<td>a_string</td>
140+
<td><pre>"foo"</pre></td>
141+
<td>string</td>
142+
</tr>
143+
144+
</table>
145+
```
74146

75-
* provide the ability to pass a custom template
76-
* improve the formatting and readability of lists and maps
77-
* create automated releases
78-
* create a GitHub Action
79-
* publish an OCI image
147+
</details>
148+
149+
<details>
150+
<summary>HTML output rendered via GitHub-flavored markdown</summary>
151+
152+
<h2>Outputs</h2>
153+
<p>Terraform state outputs.</p>
154+
<table>
155+
<tr>
156+
<th>Output</th>
157+
<th>Value</th>
158+
<th>Type</th>
159+
</tr>
160+
161+
<tr>
162+
<td>a_basic_map</td>
163+
<td><pre>{
164+
"foo": "bar",
165+
"number": 42
166+
}</pre></td>
167+
<td>map[string]interface {}</td>
168+
</tr>
169+
170+
<tr>
171+
<td>a_list</td>
172+
<td><pre>[
173+
"foo",
174+
"bar"
175+
]</pre></td>
176+
<td>[]interface {}</td>
177+
</tr>
178+
179+
<tr>
180+
<td>a_nested_map</td>
181+
<td><pre>{
182+
"baz": {
183+
"bar": "baz",
184+
"id": "123"
185+
},
186+
"foo": "bar",
187+
"number": 42
188+
}</pre></td>
189+
<td>map[string]interface {}</td>
190+
</tr>
191+
192+
<tr>
193+
<td>a_sensitive_value</td>
194+
<td><pre>sensitive; redacted</pre></td>
195+
<td>string</td>
196+
</tr>
197+
198+
<tr>
199+
<td>a_string</td>
200+
<td><pre>"foo"</pre></td>
201+
<td>string</td>
202+
</tr>
203+
204+
</table>
205+
206+
</details>

main.go

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ var (
2525
)
2626

2727
const (
28-
stateDesc string = "Optional; the state JSON output by 'terraform show -json', read from stdin if omitted"
28+
stateDesc string = "Optional; the state JSON output by 'terraform show -json'. Read from stdin if omitted"
2929
stateFileDesc string = "Optional; the path to a local file containing 'terraform show -json' output"
30-
headingDesc string = "Optional; the heading text for use in the printed markdown"
30+
headingDesc string = "Optional; the heading text for use in the printed output."
31+
outputDesc string = "Optional; the output format. Supported values: md, html."
3132
versionDesc string = "Print the current version and exit"
3233
defaultHeading string = "Outputs"
34+
defaultOutput string = "md"
35+
sensitive string = "sensitive; redacted"
3336
)
3437

3538
type data struct {
@@ -39,12 +42,25 @@ type data struct {
3942

4043
func value(output tfjson.StateOutput) string {
4144
if output.Sensitive {
42-
return "sensitive; redacted"
45+
return sensitive
4346
}
4447

4548
return fmt.Sprintf("%v", output.Value)
4649
}
4750

51+
func prettyPrintValue(output tfjson.StateOutput) template.HTML {
52+
if output.Sensitive {
53+
return template.HTML(sensitive)
54+
}
55+
56+
pretty, err := json.MarshalIndent(output.Value, "", " ")
57+
if err != nil {
58+
exit(err)
59+
}
60+
61+
return template.HTML(string(pretty))
62+
}
63+
4864
func dataType(output tfjson.StateOutput) string {
4965
return fmt.Sprintf("%T", output.Value)
5066
}
@@ -53,6 +69,7 @@ func main() {
5369
stateJSON := flag.String("state", "", stateDesc)
5470
stateFile := flag.String("state-file", "", stateFileDesc)
5571
heading := flag.String("heading", defaultHeading, headingDesc)
72+
output := flag.String("output", defaultOutput, outputDesc)
5673
flag.Parse()
5774

5875
args := flag.Args()
@@ -84,10 +101,16 @@ func main() {
84101
exit(err)
85102
}
86103

87-
t, err := template.New("markdown.tmpl").Funcs(template.FuncMap{
88-
"value": value,
89-
"dataType": dataType,
90-
}).ParseFS(templates, "templates/markdown.tmpl")
104+
tmpl, err := getTemplatePath(*output)
105+
if err != nil {
106+
exit(err)
107+
}
108+
109+
t, err := template.New(strings.Split(tmpl, "/")[1]).Funcs(template.FuncMap{
110+
"value": value,
111+
"dataType": dataType,
112+
"prettyPrint": prettyPrintValue,
113+
}).ParseFS(templates, tmpl)
91114
if err != nil {
92115
exit(err)
93116
}
@@ -106,6 +129,17 @@ func main() {
106129
}
107130
}
108131

132+
func getTemplatePath(output string) (string, error) {
133+
switch output {
134+
case "html":
135+
return "templates/html.tmpl", nil
136+
case "md":
137+
return "templates/markdown.tmpl", nil
138+
default:
139+
return "", fmt.Errorf("'%s' is not a supported output format. Supported formats: 'md' (default), 'html'", output)
140+
}
141+
}
142+
109143
func exit(err error) {
110144
fmt.Fprintf(os.Stderr, "%s", err.Error())
111145
os.Exit(1)

main_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ func TestHelpFlag(t *testing.T) {
3737
stateFileDesc,
3838
"-heading string",
3939
headingDesc,
40+
"-output string",
41+
outputDesc,
4042
}
4143

4244
tests := []struct {
@@ -195,6 +197,63 @@ Terraform state outputs.
195197
196198
`,
197199
}, {
200+
command: `./terraputs -state-file testdata/basic/show.json -heading foo -output html`,
201+
expectedOutput: `<h2>foo</h2>
202+
<p>Terraform state outputs.</p>
203+
<table>
204+
<tr>
205+
<th>Output</th>
206+
<th>Value</th>
207+
<th>Type</th>
208+
</tr>
209+
210+
<tr>
211+
<td>a_basic_map</td>
212+
<td><pre>{
213+
"foo": "bar",
214+
"number": 42
215+
}</pre></td>
216+
<td>map[string]interface {}</td>
217+
</tr>
218+
219+
<tr>
220+
<td>a_list</td>
221+
<td><pre>[
222+
"foo",
223+
"bar"
224+
]</pre></td>
225+
<td>[]interface {}</td>
226+
</tr>
227+
228+
<tr>
229+
<td>a_nested_map</td>
230+
<td><pre>{
231+
"baz": {
232+
"bar": "baz",
233+
"id": "123"
234+
},
235+
"foo": "bar",
236+
"number": 42
237+
}</pre></td>
238+
<td>map[string]interface {}</td>
239+
</tr>
240+
241+
<tr>
242+
<td>a_sensitive_value</td>
243+
<td><pre>sensitive; redacted</pre></td>
244+
<td>string</td>
245+
</tr>
246+
247+
<tr>
248+
<td>a_string</td>
249+
<td><pre>"foo"</pre></td>
250+
<td>string</td>
251+
</tr>
252+
253+
</table>
254+
`,
255+
}, {
256+
198257
command: `./terraputs -state-file testdata/nooutputs/show.json -heading foo`,
199258
expectedOutput: `# foo
200259
@@ -222,6 +281,10 @@ Terraform state outputs.
222281
command: `./terraputs -state $(cat testdata/basic/show.json) -state-file testdata/basic/show.json -heading foo`,
223282
expectedError: errors.New("exit status 1"),
224283
expectedOutput: "'-state' and '-state-file' are mutually exclusive; specify just one",
284+
}, {
285+
command: `./terraputs -state $(cat testdata/basic/show.json) -output foo`,
286+
expectedError: errors.New("exit status 1"),
287+
expectedOutput: "'foo' is not a supported output format. Supported formats: 'md' (default), 'html'",
225288
}}
226289

227290
for _, test := range tests {

templates/html.tmpl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<h2>{{ .Heading }}</h2>
2+
<p>Terraform state outputs.</p>
3+
<table>
4+
<tr>
5+
<th>Output</th>
6+
<th>Value</th>
7+
<th>Type</th>
8+
</tr>
9+
{{ range $key, $value := .Outputs }}
10+
<tr>
11+
<td>{{ $key }}</td>
12+
<td><pre>{{ prettyPrint $value }}</pre></td>
13+
<td>{{ dataType $value }}</td>
14+
</tr>
15+
{{ end }}
16+
</table>

0 commit comments

Comments
 (0)