diff --git a/README.md b/README.md index 15f2f45..9498667 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,19 @@ be lower in older language versions. The primary way to use this repository is as a Go library, but as a convenience it also contains a CLI tool called `terraform-config-inspect`, installed automatically by the `go get` command above, that allows viewing module -information in either a Markdown-like format or in JSON format. +information in either a Markdown-like format or in JSON format. It also +supports parsing Terraform stacks using the `--stack` flag. ```sh $ terraform-config-inspect path/to/module ``` +For Terraform stacks: + +```sh +$ terraform-config-inspect --stack path/to/stack +``` + ```markdown # Module `path/to/module` @@ -139,6 +146,85 @@ $ terraform-config-inspect --json path/to/module } ``` +For Terraform stacks, you can use the `--stack` flag: + +```sh +$ terraform-config-inspect --stack path/to/stack +``` + +```markdown +# Terraform Stack: path/to/stack + +## Variables + +- **regions** (set(string)) +- **identity_token** (string) +- **default_tags** (map(string)): A map of default tags to apply to all AWS resources + +## Outputs + +- **lambda_urls**: URLs to invoke lambda functions + +## Components + +- **s3** (source: `./s3`) +- **lambda** (source: `./lambda`) +- **api_gateway** (source: `./api-gateway`) + +## Required Providers + +- **aws** (source: `hashicorp/aws`) +- **archive** (source: `hashicorp/archive`) +``` + +```sh +$ terraform-config-inspect --stack --json path/to/stack +``` + +```json +{ + "path": "path/to/stack", + "variables": { + "regions": { + "name": "regions", + "type": "set(string)", + "default": null, + "required": true, + "pos": { + "filename": "path/to/stack/variables.tfstack.hcl", + "line": 4 + } + } + }, + "outputs": { + "lambda_urls": { + "name": "lambda_urls", + "description": "URLs to invoke lambda functions", + "pos": { + "filename": "path/to/stack/outputs.tfcomponent.hcl", + "line": 1 + }, + "type": "list(string)" + } + }, + "required_providers": { + "aws": { + "source": "hashicorp/aws" + } + }, + "components": { + "s3": { + "name": "s3", + "source": "./s3", + "pos": { + "filename": "path/to/stack/components.tfcomponent.hcl", + "line": 4 + } + } + } +} +``` + ## Contributing This library and tool are intentionally focused on only extracting simple diff --git a/main.go b/main.go index d97988c..253439d 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( ) var showJSON = flag.Bool("json", false, "produce JSON-formatted output") +var parseStack = flag.Bool("stack", false, "parse as Terraform stack instead of module") func main() { flag.Parse() @@ -24,16 +25,31 @@ func main() { dir = "." } - module, _ := tfconfig.LoadModule(dir) + if *parseStack { + stack, diags := tfconfig.LoadStack(dir) + stack.Diagnostics = diags - if *showJSON { - showModuleJSON(module) + if *showJSON { + showStackJSON(stack) + } else { + showStackMarkdown(stack) + } + + if stack.Diagnostics.HasErrors() { + os.Exit(1) + } } else { - showModuleMarkdown(module) - } + module, _ := tfconfig.LoadModule(dir) + + if *showJSON { + showModuleJSON(module) + } else { + showModuleMarkdown(module) + } - if module.Diagnostics.HasErrors() { - os.Exit(1) + if module.Diagnostics.HasErrors() { + os.Exit(1) + } } } @@ -54,3 +70,84 @@ func showModuleMarkdown(module *tfconfig.Module) { os.Exit(2) } } + +func showStackJSON(stack *tfconfig.Stack) { + j, err := json.MarshalIndent(stack, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "error producing JSON: %s\n", err) + os.Exit(2) + } + os.Stdout.Write(j) + os.Stdout.Write([]byte{'\n'}) +} + +func showStackMarkdown(stack *tfconfig.Stack) { + fmt.Printf("# Terraform Stack: %s\n\n", stack.Path) + + if len(stack.Variables) > 0 { + fmt.Printf("## Variables\n\n") + for name, variable := range stack.Variables { + fmt.Printf("- **%s** (%s)", name, variable.Type) + if variable.Description != "" { + fmt.Printf(": %s", variable.Description) + } + fmt.Printf("\n") + } + fmt.Printf("\n") + } + + if len(stack.Outputs) > 0 { + fmt.Printf("## Outputs\n\n") + for name, output := range stack.Outputs { + fmt.Printf("- **%s**", name) + if output.Description != "" { + fmt.Printf(": %s", output.Description) + } + fmt.Printf("\n") + } + fmt.Printf("\n") + } + + if len(stack.Components) > 0 { + fmt.Printf("## Components\n\n") + for name, component := range stack.Components { + fmt.Printf("- **%s** (source: `%s`)\n", name, component.Source) + } + fmt.Printf("\n") + } + + if len(stack.RequiredProviders) > 0 { + fmt.Printf("## Required Providers\n\n") + for name, provider := range stack.RequiredProviders { + fmt.Printf("- **%s**", name) + if provider.Source != "" { + fmt.Printf(" (source: `%s`)", provider.Source) + } + if len(provider.VersionConstraints) > 0 { + fmt.Printf(" version: %s", provider.VersionConstraints[0]) + } + fmt.Printf("\n") + } + } + + if len(stack.Diagnostics) > 0 { + fmt.Printf("## Problems\n\n") + for _, diag := range stack.Diagnostics { + severity := "" + switch diag.Severity { + case tfconfig.DiagError: + severity = "Error: " + case tfconfig.DiagWarning: + severity = "Warning: " + } + fmt.Printf("## %s%s", severity, diag.Summary) + if diag.Pos != nil { + fmt.Printf("\n\n(at `%s` line %d)", diag.Pos.Filename, diag.Pos.Line) + } + if diag.Detail != "" { + fmt.Printf("\n\n%s", diag.Detail) + } + fmt.Printf("\n\n") + } + } +}