Skip to content
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
88 changes: 87 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down Expand Up @@ -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
Expand Down
111 changes: 104 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
}
}
}

Expand All @@ -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")
}
}
}