From 29d45b2f7c312de2a19688c3fd444aed9d747102 Mon Sep 17 00:00:00 2001 From: Mike Ball Date: Sat, 27 Jan 2024 08:58:28 -0500 Subject: [PATCH] Fix issue #20 "unexpected end of JSON input" error (#64) * correct input reading logic This addresses https://github.com/dineshba/tf-summarize/issues/20 and ensures input is read from the plan file -- and not via STDIN -- if a plan file argument is provided. This also seeks to improve some of the error messaging to be a bit more clear. * demo issue 20 fix via GH Actions Ideally, tf-summarize would feature a suite of automated tests verifying its functionality. In absenece of that, this demos the issue #20 fix via GH Actions. * use consistent TF version when invoking TF - ensure the generation of the `example` directory data is done in a consistent, reproducible fashion - ensure GH Actions uses the same version of TF expected by the `example` directory - add plan and plan JSON files to source control, for testing purposes * add automated test verifying STDIN vs file-provided input This adds a basic automated test verifying the validity of the issue #20 fix. * remove `demo` job from Build GH Actions workflow Per code review request, @dineshba would prefer this be kept in a separate file. --- .github/workflows/build.yml | 39 ++++++++------- .github/workflows/demo.yml | 97 +++++++++++++++++++------------------ .gitignore | 2 + Makefile | 22 ++++++++- example/.terraform-version | 1 + example/tfplan.json | 1 + main.go | 2 +- main_test.go | 95 ++++++++++++++++++++++++++++++++++++ reader/reader.go | 24 +++++---- testdata/basic.txt | 9 ++++ 10 files changed, 214 insertions(+), 78 deletions(-) create mode 100644 example/.terraform-version create mode 100644 example/tfplan.json create mode 100644 main_test.go create mode 100644 testdata/basic.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index deeca95..009e368 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,34 +2,33 @@ name: Build on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: - build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Run Gosec Security Scanner - uses: securego/gosec@master - with: - args: -exclude=G204 ./... + - name: Run Gosec Security Scanner + uses: securego/gosec@master + with: + args: -exclude=G204 ./... - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.21' + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.21" - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: latest + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest - - name: Test - run: go test -v ./... + - name: Test + run: go test -v ./... - - name: Build - run: go build + - name: Build + run: go build diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 065aa68..bb89155 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -11,49 +11,54 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v2 - with: - terraform_wrapper: false - - - name: Install terraform-plan-summary - run: | - REPO="dineshba/tf-summarize" - curl -LO https://github.com/$REPO/releases/latest/download/tf-summarize_linux_amd64.zip - tmpDir=$(mktemp -d -t tmp.XXXXXXXXXX) - mv tf-summarize_linux_amd64.zip $tmpDir - cd $tmpDir - unzip tf-summarize_linux_amd64.zip - chmod +x tf-summarize - echo $PWD >> $GITHUB_PATH - - - name: Print tf-summarize version and help - run: | - tf-summarize -v - tf-summarize -h - - - name: Terraform Init - run: terraform init - working-directory: ./example - - - name: Terraform Plan - run: terraform plan -out=tfplan -refresh=false # -refresh=false is only for demo workflow - working-directory: ./example - - - name: summary in table format - run: terraform show -json tfplan | tf-summarize - working-directory: ./example - - - name: summary in tree format - run: terraform show -json tfplan | tf-summarize -tree - working-directory: ./example - - - name: summary in separate tree format - run: terraform show -json tfplan | tf-summarize -separate-tree - working-directory: ./example - - - name: summary in draw visual tree format - run: terraform show -json tfplan | tf-summarize -tree -draw - working-directory: ./example + - uses: actions/checkout@v3 + + - name: Set Terraform version + id: set-terraform-version + run: echo "terraform-version=$(cat example/.terraform-version)" >> $GITHUB_OUTPUT + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_wrapper: false + terraform_version: ${{ steps.set-terraform-version.outputs.terraform-version }} + + - name: Install terraform-plan-summary + run: | + REPO="dineshba/tf-summarize" + curl -LO https://github.com/$REPO/releases/latest/download/tf-summarize_linux_amd64.zip + tmpDir=$(mktemp -d -t tmp.XXXXXXXXXX) + mv tf-summarize_linux_amd64.zip $tmpDir + cd $tmpDir + unzip tf-summarize_linux_amd64.zip + chmod +x tf-summarize + echo $PWD >> $GITHUB_PATH + + - name: Print tf-summarize version and help + run: | + tf-summarize -v + tf-summarize -h + + - name: Terraform Init + run: terraform init + working-directory: ./example + + - name: Terraform Plan + run: terraform plan -out=tfplan -refresh=false # -refresh=false is only for demo workflow + working-directory: ./example + + - name: summary in table format + run: terraform show -json tfplan | tf-summarize + working-directory: ./example + + - name: summary in tree format + run: terraform show -json tfplan | tf-summarize -tree + working-directory: ./example + + - name: summary in separate tree format + run: terraform show -json tfplan | tf-summarize -separate-tree + working-directory: ./example + + - name: summary in draw visual tree format + run: terraform show -json tfplan | tf-summarize -tree -draw + working-directory: ./example diff --git a/.gitignore b/.gitignore index 5064a9e..704d484 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ tf-summarize **/.envrc **/.terraform *.swp + +example/tfplan diff --git a/Makefile b/Makefile index 440fb42..861ea7c 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +TERRAFORM_VERSION:=$(shell cat example/.terraform-version) + .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}' @@ -17,4 +19,22 @@ test: lint i: install ## build and install to /usr/local/bin/ lint: - golangci-lint run --timeout 10m -v \ No newline at end of file + 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: + $(call generate-example,$(TERRAFORM_VERSION)) +.PHONY: example diff --git a/example/.terraform-version b/example/.terraform-version new file mode 100644 index 0000000..65087b4 --- /dev/null +++ b/example/.terraform-version @@ -0,0 +1 @@ +1.1.4 diff --git a/example/tfplan.json b/example/tfplan.json new file mode 100644 index 0000000..a151ff1 --- /dev/null +++ b/example/tfplan.json @@ -0,0 +1 @@ +{"format_version":"1.0","terraform_version":"1.1.4","planned_values":{"root_module":{"resources":[{"address":"github_repository.terraform_plan_summary","mode":"managed","type":"github_repository","name":"terraform_plan_summary","provider_name":"registry.terraform.io/integrations/github","schema_version":0,"values":{"allow_auto_merge":false,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_squash_merge":true,"archive_on_destroy":null,"archived":false,"auto_init":null,"delete_branch_on_merge":false,"description":"A command-line utility to print the summary of the terraform plan","gitignore_template":null,"has_downloads":true,"has_issues":true,"has_projects":true,"has_wiki":true,"homepage_url":null,"ignore_vulnerability_alerts_during_read":null,"is_template":null,"license_template":null,"name":"terraform-plan-summary","pages":[],"template":[],"topics":["summary","terraform"],"visibility":"public","vulnerability_alerts":false},"sensitive_values":{"branches":[],"pages":[],"template":[],"topics":[false,false]}}],"child_modules":[{"resources":[{"address":"module.github[\"demo-repository\"].github_branch.development","mode":"managed","type":"github_branch","name":"development","provider_name":"registry.terraform.io/hashicorp/github","schema_version":0,"values":{"branch":"development","repository":"demo-repository","source_branch":"main"},"sensitive_values":{}},{"address":"module.github[\"demo-repository\"].github_branch.main","mode":"managed","type":"github_branch","name":"main","provider_name":"registry.terraform.io/hashicorp/github","schema_version":0,"values":{"branch":"main","repository":"demo-repository","source_branch":"main"},"sensitive_values":{}},{"address":"module.github[\"demo-repository\"].github_repository.repository","mode":"managed","type":"github_repository","name":"repository","provider_name":"registry.terraform.io/hashicorp/github","schema_version":0,"values":{"allow_auto_merge":false,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_squash_merge":true,"archive_on_destroy":null,"archived":false,"auto_init":null,"delete_branch_on_merge":false,"description":"description of the repo","gitignore_template":null,"has_downloads":true,"has_issues":true,"has_projects":true,"has_wiki":true,"homepage_url":null,"ignore_vulnerability_alerts_during_read":null,"is_template":null,"license_template":null,"name":"demo-repository","pages":[],"template":[],"topics":["tags"],"visibility":"public","vulnerability_alerts":false},"sensitive_values":{"branches":[],"pages":[],"template":[],"topics":[false]}}],"address":"module.github[\"demo-repository\"]"},{"resources":[{"address":"module.github[\"terraform-plan-summary\"].github_branch.demo","mode":"managed","type":"github_branch","name":"demo","provider_name":"registry.terraform.io/hashicorp/github","schema_version":0,"sensitive_values":false},{"address":"module.github[\"terraform-plan-summary\"].github_branch.development","mode":"managed","type":"github_branch","name":"development","provider_name":"registry.terraform.io/hashicorp/github","schema_version":0,"values":{"branch":"development","repository":"terraform-plan-summary","source_branch":"main"},"sensitive_values":{}},{"address":"module.github[\"terraform-plan-summary\"].github_branch.main","mode":"managed","type":"github_branch","name":"main","provider_name":"registry.terraform.io/hashicorp/github","schema_version":0,"values":{"branch":"main","repository":"terraform-plan-summary","source_branch":"main"},"sensitive_values":{}},{"address":"module.github[\"terraform-plan-summary\"].github_repository.repository","mode":"managed","type":"github_repository","name":"repository","provider_name":"registry.terraform.io/hashicorp/github","schema_version":0,"values":{"allow_auto_merge":false,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_squash_merge":true,"archive_on_destroy":null,"archived":false,"auto_init":null,"delete_branch_on_merge":false,"description":"A command-line utility to print the summary of the terraform plan","gitignore_template":null,"has_downloads":true,"has_issues":true,"has_projects":true,"has_wiki":true,"homepage_url":null,"ignore_vulnerability_alerts_during_read":null,"is_template":null,"license_template":null,"name":"terraform-plan-summary","pages":[],"template":[],"topics":["summary","terraform"],"visibility":"public","vulnerability_alerts":false},"sensitive_values":{"branches":[],"pages":[],"template":[],"topics":[false,false]}}],"address":"module.github[\"terraform-plan-summary\"]"}]}},"resource_drift":[{"address":"module.github[\"terraform-plan-summary\"].github_branch.demo","module_address":"module.github[\"terraform-plan-summary\"]","mode":"managed","type":"github_branch","name":"demo","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["delete"],"before":{"branch":"demo","etag":"W/\"2c138304ec0f031d3ffd19d45dae737be855a74a8d7e48f4fae4fc9213a6dbf6\"","id":"terraform-plan-summary:demo","ref":"refs/heads/demo","repository":"terraform-plan-summary","sha":"2e3d2c0b4513373c72b96b29592b62581e271af9","source_branch":"main","source_sha":null},"after":null,"after_unknown":{},"before_sensitive":{},"after_sensitive":false}},{"address":"module.github[\"terraform-plan-summary\"].github_branch.main","module_address":"module.github[\"terraform-plan-summary\"]","mode":"managed","type":"github_branch","name":"main","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["delete"],"before":{"branch":"main","etag":"W/\"4d429c1f4818d4314dcd07dd1e034b2bab0014a46bf9088635e37fd6decc64b8\"","id":"terraform-plan-summary:main","ref":"refs/heads/main","repository":"terraform-plan-summary","sha":"ad0cb343cc26f08985e5517bd2da8f0483beaa4b","source_branch":"main","source_sha":null},"after":null,"after_unknown":{},"before_sensitive":{},"after_sensitive":false}},{"address":"module.github[\"terraform-plan-summary\"].github_repository.repository","module_address":"module.github[\"terraform-plan-summary\"]","mode":"managed","type":"github_repository","name":"repository","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["delete"],"before":{"allow_auto_merge":false,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_squash_merge":true,"archive_on_destroy":null,"archived":false,"auto_init":false,"branches":[{"name":"main","protected":false},{"name":"trail","protected":false}],"default_branch":"main","delete_branch_on_merge":false,"description":"A command-line utility to print the summary of the terraform plan","etag":"W/\"7536d6c2714d2e49890a3d1ebed0a31e89b6412f973e0d631089836e25229f29\"","full_name":"dineshba/terraform-plan-summary","git_clone_url":"git://github.com/dineshba/terraform-plan-summary.git","gitignore_template":null,"has_downloads":true,"has_issues":true,"has_projects":true,"has_wiki":true,"homepage_url":"","html_url":"https://github.com/dineshba/terraform-plan-summary","http_clone_url":"https://github.com/dineshba/terraform-plan-summary.git","id":"terraform-plan-summary","ignore_vulnerability_alerts_during_read":null,"is_template":false,"license_template":null,"name":"terraform-plan-summary","node_id":"R_kgDOGq54_w","pages":[],"private":false,"repo_id":447641855,"ssh_clone_url":"git@github.com:dineshba/terraform-plan-summary.git","svn_url":"https://github.com/dineshba/terraform-plan-summary","template":[],"topics":["summary","terraform"],"visibility":"public","vulnerability_alerts":true},"after":null,"after_unknown":{},"before_sensitive":{"branches":[{},{}],"pages":[],"template":[],"topics":[false,false]},"after_sensitive":false}}],"resource_changes":[{"address":"github_repository.terraform_plan_summary","mode":"managed","type":"github_repository","name":"terraform_plan_summary","provider_name":"registry.terraform.io/integrations/github","change":{"actions":["create"],"before":null,"after":{"allow_auto_merge":false,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_squash_merge":true,"archive_on_destroy":null,"archived":false,"auto_init":null,"delete_branch_on_merge":false,"description":"A command-line utility to print the summary of the terraform plan","gitignore_template":null,"has_downloads":true,"has_issues":true,"has_projects":true,"has_wiki":true,"homepage_url":null,"ignore_vulnerability_alerts_during_read":null,"is_template":null,"license_template":null,"name":"terraform-plan-summary","pages":[],"template":[],"topics":["summary","terraform"],"visibility":"public","vulnerability_alerts":false},"after_unknown":{"branches":true,"default_branch":true,"etag":true,"full_name":true,"git_clone_url":true,"html_url":true,"http_clone_url":true,"id":true,"node_id":true,"pages":[],"private":true,"repo_id":true,"ssh_clone_url":true,"svn_url":true,"template":[],"topics":[false,false]},"before_sensitive":false,"after_sensitive":{"branches":[],"pages":[],"template":[],"topics":[false,false]}}},{"address":"module.github[\"demo-repository\"].github_branch.development","module_address":"module.github[\"demo-repository\"]","mode":"managed","type":"github_branch","name":"development","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["create"],"before":null,"after":{"branch":"development","repository":"demo-repository","source_branch":"main"},"after_unknown":{"etag":true,"id":true,"ref":true,"sha":true,"source_sha":true},"before_sensitive":false,"after_sensitive":{}}},{"address":"module.github[\"demo-repository\"].github_branch.main","module_address":"module.github[\"demo-repository\"]","mode":"managed","type":"github_branch","name":"main","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["create"],"before":null,"after":{"branch":"main","repository":"demo-repository","source_branch":"main"},"after_unknown":{"etag":true,"id":true,"ref":true,"sha":true,"source_sha":true},"before_sensitive":false,"after_sensitive":{}}},{"address":"module.github[\"demo-repository\"].github_repository.repository","module_address":"module.github[\"demo-repository\"]","mode":"managed","type":"github_repository","name":"repository","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["create"],"before":null,"after":{"allow_auto_merge":false,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_squash_merge":true,"archive_on_destroy":null,"archived":false,"auto_init":null,"delete_branch_on_merge":false,"description":"description of the repo","gitignore_template":null,"has_downloads":true,"has_issues":true,"has_projects":true,"has_wiki":true,"homepage_url":null,"ignore_vulnerability_alerts_during_read":null,"is_template":null,"license_template":null,"name":"demo-repository","pages":[],"template":[],"topics":["tags"],"visibility":"public","vulnerability_alerts":false},"after_unknown":{"branches":true,"default_branch":true,"etag":true,"full_name":true,"git_clone_url":true,"html_url":true,"http_clone_url":true,"id":true,"node_id":true,"pages":[],"private":true,"repo_id":true,"ssh_clone_url":true,"svn_url":true,"template":[],"topics":[false]},"before_sensitive":false,"after_sensitive":{"branches":[],"pages":[],"template":[],"topics":[false]}}},{"address":"module.github[\"terraform-plan-summary\"].github_branch.demo","module_address":"module.github[\"terraform-plan-summary\"]","mode":"managed","type":"github_branch","name":"demo","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["no-op"],"before":null,"after":null,"after_unknown":{},"before_sensitive":false,"after_sensitive":false},"action_reason":"delete_because_no_resource_config"},{"address":"module.github[\"terraform-plan-summary\"].github_branch.development","module_address":"module.github[\"terraform-plan-summary\"]","mode":"managed","type":"github_branch","name":"development","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["create"],"before":null,"after":{"branch":"development","repository":"terraform-plan-summary","source_branch":"main"},"after_unknown":{"etag":true,"id":true,"ref":true,"sha":true,"source_sha":true},"before_sensitive":false,"after_sensitive":{}}},{"address":"module.github[\"terraform-plan-summary\"].github_branch.main","module_address":"module.github[\"terraform-plan-summary\"]","mode":"managed","type":"github_branch","name":"main","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["create"],"before":null,"after":{"branch":"main","repository":"terraform-plan-summary","source_branch":"main"},"after_unknown":{"etag":true,"id":true,"ref":true,"sha":true,"source_sha":true},"before_sensitive":false,"after_sensitive":{}}},{"address":"module.github[\"terraform-plan-summary\"].github_repository.repository","module_address":"module.github[\"terraform-plan-summary\"]","mode":"managed","type":"github_repository","name":"repository","provider_name":"registry.terraform.io/hashicorp/github","change":{"actions":["create"],"before":null,"after":{"allow_auto_merge":false,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_squash_merge":true,"archive_on_destroy":null,"archived":false,"auto_init":null,"delete_branch_on_merge":false,"description":"A command-line utility to print the summary of the terraform plan","gitignore_template":null,"has_downloads":true,"has_issues":true,"has_projects":true,"has_wiki":true,"homepage_url":null,"ignore_vulnerability_alerts_during_read":null,"is_template":null,"license_template":null,"name":"terraform-plan-summary","pages":[],"template":[],"topics":["summary","terraform"],"visibility":"public","vulnerability_alerts":false},"after_unknown":{"branches":true,"default_branch":true,"etag":true,"full_name":true,"git_clone_url":true,"html_url":true,"http_clone_url":true,"id":true,"node_id":true,"pages":[],"private":true,"repo_id":true,"ssh_clone_url":true,"svn_url":true,"template":[],"topics":[false,false]},"before_sensitive":false,"after_sensitive":{"branches":[],"pages":[],"template":[],"topics":[false,false]}}}],"configuration":{"provider_config":{"github":{"name":"github","version_constraint":"4.23.0","expressions":{"owner":{"constant_value":"dineshba"}}}},"root_module":{"resources":[{"address":"github_repository.terraform_plan_summary","mode":"managed","type":"github_repository","name":"terraform_plan_summary","provider_config_key":"github","expressions":{"description":{"constant_value":"A command-line utility to print the summary of the terraform plan"},"has_downloads":{"constant_value":true},"has_issues":{"constant_value":true},"has_projects":{"constant_value":true},"has_wiki":{"constant_value":true},"name":{"constant_value":"terraform-plan-summary"},"topics":{"constant_value":["summary","terraform"]},"visibility":{"constant_value":"public"},"vulnerability_alerts":{"constant_value":false}},"schema_version":0}],"module_calls":{"github":{"source":"./github","expressions":{"description":{"references":["each.value.description","each.value"]},"name":{"references":["each.key"]},"topics":{"references":["each.value.topics","each.value"]}},"for_each_expression":{"references":["local.repos"]},"module":{"resources":[{"address":"github_branch.development","mode":"managed","type":"github_branch","name":"development","provider_config_key":"github:github","expressions":{"branch":{"constant_value":"development"},"repository":{"references":["github_repository.repository.name","github_repository.repository"]}},"schema_version":0},{"address":"github_branch.main","mode":"managed","type":"github_branch","name":"main","provider_config_key":"github:github","expressions":{"branch":{"constant_value":"main"},"repository":{"references":["github_repository.repository.name","github_repository.repository"]}},"schema_version":0},{"address":"github_repository.repository","mode":"managed","type":"github_repository","name":"repository","provider_config_key":"github:github","expressions":{"description":{"references":["var.description"]},"has_downloads":{"constant_value":true},"has_issues":{"constant_value":true},"has_projects":{"constant_value":true},"has_wiki":{"constant_value":true},"name":{"references":["var.name"]},"topics":{"references":["var.topics"]},"visibility":{"constant_value":"public"},"vulnerability_alerts":{"constant_value":false}},"schema_version":0}],"variables":{"description":{},"name":{},"topics":{}}}}}}}} diff --git a/main.go b/main.go index 73df1af..837c137 100644 --- a/main.go +++ b/main.go @@ -38,7 +38,7 @@ func main() { err := validateFlags(*tree, *separateTree, *drawable, *md, args) logIfErrorAndExit("invalid input flags: %s\n", err, flag.Usage) - newReader, err := reader.CreateReader(os.Stdin, args) + newReader, err := reader.CreateReader(args) logIfErrorAndExit("error creating input reader: %s\n", err, flag.Usage) input, err := newReader.Read() diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..80ec18a --- /dev/null +++ b/main_test.go @@ -0,0 +1,95 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "strings" + "testing" +) + +const ( + testVersion string = "test" + testExecutable string = "tf-summarize-test" +) + +func TestMain(m *testing.M) { + // compile a 'tf-summarize' for use in running tests + exe := exec.Command("go", "build", "-ldflags", fmt.Sprintf("-X main.version=%s", testVersion), "-o", testExecutable) + err := exe.Run() + if err != nil { + os.Exit(1) + } + + m.Run() + + // delete the compiled tf-summarize + err = os.Remove(testExecutable) + if err != nil { + log.Fatal(err) + } +} + +func TestVersionArg(t *testing.T) { + args := []string{ + "-v", + } + + for _, arg := range args { + t.Run(fmt.Sprintf("when tf-summarize is passed '%s'", arg), func(t *testing.T) { + output, err := exec.Command(fmt.Sprintf("./%s", testExecutable), arg).CombinedOutput() + if err != nil { + t.Errorf("expected '%s' not to cause error; got '%v'", arg, err) + } + + if !strings.Contains(string(output), testVersion) { + t.Errorf("expected '%s' to output version '%s'; got '%s'", arg, testVersion, output) + } + }) + } +} + +func TestTFSummarize(t *testing.T) { + tests := []struct { + command string + expectedError error + expectedOutput string + }{{ + command: fmt.Sprintf("./%s -md example/tfplan.json", testExecutable), + expectedOutput: "basic.txt", + }, { + command: fmt.Sprintf("cat example/tfplan.json | ./%s -md", testExecutable), + expectedOutput: "basic.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) + } + + b, err := os.ReadFile(fmt.Sprintf("testdata/%s", test.expectedOutput)) + if err != nil { + t.Errorf("error reading file '%s': '%v'", test.expectedOutput, err) + } + + expected := string(b) + + if test.expectedError != nil && err == nil { + t.Errorf("expected error '%s'; got '%v'", test.expectedError.Error(), err) + } + + if test.expectedError != nil && err != nil && test.expectedError.Error() != err.Error() { + t.Errorf("expected error '%s'; got '%v'", test.expectedError.Error(), err.Error()) + } + + if string(output) != expected { + t.Logf("expected output: \n%s", expected) + t.Logf("got output: \n%s", output) + t.Errorf("received unexpected output from '%s'", test.command) + } + }) + } +} diff --git a/reader/reader.go b/reader/reader.go index 5caead1..4319fa3 100644 --- a/reader/reader.go +++ b/reader/reader.go @@ -2,9 +2,10 @@ package reader import ( "bufio" + "errors" "fmt" "io" - "os" + "strings" ) type Reader interface { @@ -22,19 +23,22 @@ func readFile(f io.Reader) ([]byte, error) { input = append(input, line...) } if err != io.EOF { - return nil, fmt.Errorf("error reading file: %s", err.Error()) + return nil, fmt.Errorf("error reading input: %s", err.Error()) + } + if len(input) == 0 { + return nil, errors.New("no input data; expected input via a non-empty file or via STDIN") } return input, nil } -func CreateReader(stdin *os.File, args []string) (Reader, error) { - stat, _ := stdin.Stat() - if (stat.Mode() & os.ModeCharDevice) == 0 { - return NewStdinReader(), nil +func CreateReader(args []string) (Reader, error) { + if len(args) > 1 { + return nil, fmt.Errorf("expected input via a single filename argument or via STDIN; received multiple arguments: %s", strings.Join(args, ", ")) } - if len(args) < 1 { - return nil, fmt.Errorf("should have either stdin input through pipe or first argument should be file") + + if len(args) == 1 { + return NewFileReader(args[0]), nil } - fileName := args[0] - return NewFileReader(fileName), nil + + return NewStdinReader(), nil } diff --git a/testdata/basic.txt b/testdata/basic.txt new file mode 100644 index 0000000..bb145c0 --- /dev/null +++ b/testdata/basic.txt @@ -0,0 +1,9 @@ +| CHANGE | RESOURCE | +|--------|------------------------------------------------------------------------| +| add | `github_repository.terraform_plan_summary` | +| | `module.github["demo-repository"].github_branch.development` | +| | `module.github["demo-repository"].github_branch.main` | +| | `module.github["demo-repository"].github_repository.repository` | +| | `module.github["terraform-plan-summary"].github_branch.development` | +| | `module.github["terraform-plan-summary"].github_branch.main` | +| | `module.github["terraform-plan-summary"].github_repository.repository` |