Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support -html flag to render output as HTML #70

Merged
merged 5 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
TERRAFORM_VERSION:=$(shell cat example/.terraform-version)

define generate-example
docker run \
--interactive \
--tty \
--volume $(shell pwd):/src \
--workdir /src/example \
--entrypoint /bin/sh \
hashicorp/terraform:$(1) \
-c \
"terraform init && \
terraform plan -out tfplan && \
terraform show -json tfplan > tfplan.json"
endef
dineshba marked this conversation as resolved.
Show resolved Hide resolved

.PHONY: help
help: ## prints help (only for tasks with comment)
@grep -E '^[a-zA-Z._-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
Expand All @@ -13,28 +27,14 @@ build: ## build the binary
install: build ## build and install to /usr/local/bin/
cp $(EXECUTABLE_NAME) /usr/local/bin/$(EXECUTABLE_NAME)

test: lint
go test ./...
test: lint ## go test
go test ./... -count=1
dineshba marked this conversation as resolved.
Show resolved Hide resolved

i: install ## build and install to /usr/local/bin/

lint:
lint: ## lint source code
golangci-lint run --timeout 10m -v

define generate-example
docker run \
--interactive \
--tty \
--volume $(shell pwd):/src \
--workdir /src/example \
--entrypoint /bin/sh \
hashicorp/terraform:$(1) \
-c \
"terraform init && \
terraform plan -out tfplan && \
terraform show -json tfplan > tfplan.json"
endef

example:
example: ## generate example Terraform plan
$(call generate-example,$(TERRAFORM_VERSION))
.PHONY: example
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,16 @@ Usage of tf-summarize [args] [tf-plan.json|tfplan]
-draw
[Optional, used only with -tree or -separate-tree] draw trees instead of plain tree
-html
[Optional] print changes in html format
-json
[Optional] print changes in json format
-md
[Optional, used only with table view] output table as markdown
-out string
[Optional] write output to file
-separate-tree
[Optional] print changes in tree format for each add/delete/change/recreate changes
[Optional] print changes in tree format for add/delete/change/recreate changes
dineshba marked this conversation as resolved.
Show resolved Hide resolved
-tree
[Optional] print changes in tree format
-v print version
Expand Down
2 changes: 2 additions & 0 deletions example/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion example/github/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ resource "github_branch" "main" {
resource "github_branch" "development" {
repository = github_repository.repository.name
branch = "development"
}
}

output "repository_name" {
value = github_repository.repository.name
dineshba marked this conversation as resolved.
Show resolved Hide resolved
}
4 changes: 4 additions & 0 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,7 @@ resource "github_repository" "terraform_plan_summary" {
has_wiki = true
vulnerability_alerts = false
}

output "terraform_plan_summary_repository_name" {
value = github_repository.terraform_plan_summary.name
}
2 changes: 1 addition & 1 deletion example/tfplan.json

Large diffs are not rendered by default.

22 changes: 19 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func main() {
printVersion := flag.Bool("v", false, "print version")
tree := flag.Bool("tree", false, "[Optional] print changes in tree format")
json := flag.Bool("json", false, "[Optional] print changes in json format")
html := flag.Bool("html", false, "[Optional] print changes in html format")
separateTree := flag.Bool("separate-tree", false, "[Optional] print changes in tree format for add/delete/change/recreate changes")
drawable := flag.Bool("draw", false, "[Optional, used only with -tree or -separate-tree] draw trees instead of plain tree")
md := flag.Bool("md", false, "[Optional, used only with table view] output table as markdown")
Expand All @@ -35,7 +36,7 @@ func main() {
}

args := flag.Args()
err := validateFlags(*tree, *separateTree, *drawable, *md, args)
err := validateFlags(*tree, *separateTree, *drawable, *md, *json, *html, args)
logIfErrorAndExit("invalid input flags: %s\n", err, flag.Usage)

newReader, err := reader.CreateReader(args)
Expand All @@ -52,7 +53,7 @@ func main() {

terraformstate.FilterNoOpResources(&terraformState)

newWriter := writer.CreateWriter(*tree, *separateTree, *drawable, *md, *json, terraformState)
newWriter := writer.CreateWriter(*tree, *separateTree, *drawable, *md, *json, *html, terraformState)

var outputFile io.Writer = os.Stdout

Expand Down Expand Up @@ -83,7 +84,7 @@ func logIfErrorAndExit(format string, err error, callback func()) {
}
}

func validateFlags(tree, separateTree, drawable bool, md bool, args []string) error {
func validateFlags(tree, separateTree, drawable bool, md bool, json bool, html bool, args []string) error {
if tree && md {
return fmt.Errorf("both -tree and -md should not be provided")
}
Expand All @@ -96,8 +97,23 @@ func validateFlags(tree, separateTree, drawable bool, md bool, args []string) er
if !tree && !separateTree && drawable {
return fmt.Errorf("drawable should be provided with -tree or -seperate-tree")
}
if multipleTrueVals(md, json, html) {
return fmt.Errorf("only one of -md, -json, or -html should be provided")
}
if len(args) > 1 {
return fmt.Errorf("only one argument is allowed which is filename, but got %v", args)
}
return nil
}

func multipleTrueVals(vals ...bool) bool {
v := []bool{}

for _, val := range vals {
if val {
v = append(v, val)
}
}

return len(v) > 1
}
21 changes: 14 additions & 7 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,20 @@ func TestTFSummarize(t *testing.T) {
}, {
command: fmt.Sprintf("cat example/tfplan.json | ./%s -md", testExecutable),
expectedOutput: "basic.txt",
}, {
command: fmt.Sprintf("cat example/tfplan.json | ./%s -html", testExecutable),
expectedOutput: "basic.html",
}, {
command: fmt.Sprintf("cat example/tfplan.json | ./%s -md -html", testExecutable),
expectedError: fmt.Errorf("exit status 1"),
expectedOutput: "multiple_format_flags_error.txt",
}}

for _, test := range tests {
t.Run(fmt.Sprintf("when tf-summarize is passed '%q'", test.command), func(t *testing.T) {
output, err := exec.Command("/bin/sh", "-c", test.command).CombinedOutput()
if err != nil && test.expectedError == nil {
t.Errorf("expected '%s' not to error; got '%v'", test.command, err)
output, cmdErr := exec.Command("/bin/sh", "-c", test.command).CombinedOutput()
if cmdErr != nil && test.expectedError == nil {
t.Errorf("expected '%s' not to error; got '%v'", test.command, cmdErr)
}

b, err := os.ReadFile(fmt.Sprintf("testdata/%s", test.expectedOutput))
Expand All @@ -77,12 +84,12 @@ func TestTFSummarize(t *testing.T) {

expected := string(b)

if test.expectedError != nil && err == nil {
t.Errorf("expected error '%s'; got '%v'", test.expectedError.Error(), err)
if test.expectedError != nil && cmdErr == nil {
t.Errorf("expected error '%s'; got '%v'", test.expectedError.Error(), cmdErr)
}

if test.expectedError != nil && err != nil && test.expectedError.Error() != err.Error() {
t.Errorf("expected error '%s'; got '%v'", test.expectedError.Error(), err.Error())
if test.expectedError != nil && cmdErr != nil && test.expectedError.Error() != cmdErr.Error() {
t.Errorf("expected error '%s'; got '%v'", test.expectedError.Error(), cmdErr.Error())
}

if string(output) != expected {
Expand Down
34 changes: 34 additions & 0 deletions testdata/basic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<table>
<tr>
<th>CHANGE</th>
<th>RESOURCE</th>
</tr>
<tr>
<td>add</td>
<td>
<ul>
<li><code>github_repository.terraform_plan_summary</code></li>
<li><code>module.github["demo-repository"].github_branch.development</code></li>
<li><code>module.github["demo-repository"].github_branch.main</code></li>
<li><code>module.github["demo-repository"].github_repository.repository</code></li>
<li><code>module.github["terraform-plan-summary"].github_branch.development</code></li>
<li><code>module.github["terraform-plan-summary"].github_branch.main</code></li>
<li><code>module.github["terraform-plan-summary"].github_repository.repository</code></li>
</ul>
</td>
</tr>
</table>
<table>
<tr>
<th>CHANGE</th>
<th>OUTPUT</th>
</tr>
<tr>
<td>add</td>
<td>
<ul>
<li><code>terraform_plan_summary_repository_name</code></li>
</ul>
</td>
</tr>
</table>
4 changes: 4 additions & 0 deletions testdata/basic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
| | `module.github["terraform-plan-summary"].github_branch.development` |
| | `module.github["terraform-plan-summary"].github_branch.main` |
| | `module.github["terraform-plan-summary"].github_repository.repository` |

| CHANGE | OUTPUT |
|--------|------------------------------------------|
| add | `terraform_plan_summary_repository_name` |
19 changes: 19 additions & 0 deletions testdata/multiple_format_flags_error.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
invalid input flags: only one of -md, -json, or -html should be provided

Usage of ./tf-summarize-test [args] [tf-plan.json|tfplan]

-draw
[Optional, used only with -tree or -separate-tree] draw trees instead of plain tree
-html
[Optional] print changes in html format
-json
[Optional] print changes in json format
-md
[Optional, used only with table view] output table as markdown
-out string
[Optional] write output to file
-separate-tree
[Optional] print changes in tree format for add/delete/change/recreate changes
-tree
[Optional] print changes in tree format
-v print version
50 changes: 50 additions & 0 deletions writer/html.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package writer

import (
"io"
"path"
"text/template"

"github.com/dineshba/tf-summarize/terraformstate"
)

// HTMLWriter is a Writer that writes HTML.
type HTMLWriter struct {
dineshba marked this conversation as resolved.
Show resolved Hide resolved
ResourceChanges map[string]terraformstate.ResourceChanges
OutputChanges map[string][]string
}

// Write outputs the HTML summary to the io.Writer it's passed.
func (t HTMLWriter) Write(writer io.Writer) error {
templatesDir := "templates"
rcTmpl := "resourceChanges.html"
tmpl, err := template.New(rcTmpl).ParseFS(templates, path.Join(templatesDir, rcTmpl))
if err != nil {
return err
}

err = tmpl.Execute(writer, t)
if err != nil {
return err
}

if !hasOutputChanges(t.OutputChanges) {
return nil
}

ocTmpl := "outputChanges.html"
outputTmpl, err := template.New(ocTmpl).ParseFS(templates, path.Join(templatesDir, ocTmpl))
if err != nil {
return err
}

return outputTmpl.Execute(writer, t)
}

// NewHTMLWriter returns a new HTMLWriter with the configuration it's passed.
func NewHTMLWriter(changes map[string]terraformstate.ResourceChanges, outputChanges map[string][]string) Writer {
return HTMLWriter{
ResourceChanges: changes,
OutputChanges: outputChanges,
}
}
2 changes: 1 addition & 1 deletion writer/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (t TableWriter) Write(writer io.Writer) error {
table.Render()

// Disable the Output Summary if there are no outputs to display
if len(t.outputChanges["add"]) > 0 || len(t.outputChanges["delete"]) > 0 || len(t.outputChanges["update"]) > 0 {
dineshba marked this conversation as resolved.
Show resolved Hide resolved
if hasOutputChanges(t.outputChanges) {
tableString = make([][]string, 0, 4)
for _, change := range tableOrder {
changedOutputs := t.outputChanges[change]
Expand Down
14 changes: 14 additions & 0 deletions writer/templates/outputChanges.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<table>
<tr>
<th>CHANGE</th>
<th>OUTPUT</th>
</tr>{{ range $change, $outputs := .OutputChanges }}{{ $length := len $outputs }}{{ if gt $length 0 }}
<tr>
<td>{{ $change }}</td>
<td>
<ul>{{ range $i, $o := $outputs }}
<li><code>{{ $o }}</code></li>{{ end }}
</ul>
</td>
</tr>{{ end }}{{ end }}
</table>
14 changes: 14 additions & 0 deletions writer/templates/resourceChanges.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<table>
<tr>
<th>CHANGE</th>
<th>RESOURCE</th>
</tr>{{ range $change, $resources := .ResourceChanges }}{{ $length := len $resources }}{{ if gt $length 0 }}
<tr>
<td>{{ $change }}</td>
<td>
<ul>{{ range $i, $r := $resources }}
<li><code>{{ $r.Address }}</code></li>{{ end }}
</ul>
</td>
</tr>{{ end }}{{ end }}
</table>
21 changes: 21 additions & 0 deletions writer/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package writer

import "embed"

// Embed the templates directory in the compiled binary.
//
//go:embed templates
var templates embed.FS

func hasOutputChanges(opChanges map[string][]string) bool {
hasChanges := false

for _, v := range opChanges {
if len(v) > 0 {
hasChanges = true
break
}
}

return hasChanges
}
Loading
Loading