From 8f74fd445301187ee96d31c396cad42a11f921bb Mon Sep 17 00:00:00 2001 From: Khosrow Moossavi Date: Tue, 28 May 2024 11:45:42 -0400 Subject: [PATCH] feat: ignore outputs, providers, resources with comments Prepend any type of resource, including `resource`, `data`, `module`, `variable`, and `output` with a comment including "terraform-docs-ignore" to exclude it from the generated output. Signed-off-by: Khosrow Moossavi --- docs/how-to/generate-terraform-tfvars.md | 2 +- docs/how-to/github-action.md | 2 +- docs/how-to/ignore-resources.md | 74 ++++++++++++++++ docs/how-to/include-examples.md | 2 +- docs/how-to/insert-output-to-file.md | 2 +- docs/how-to/pre-commit-hooks.md | 2 +- docs/how-to/recursive-submodules.md | 2 +- examples/main.tf | 18 ++++ examples/outputs.tf | 5 ++ examples/variables.tf | 5 ++ go.mod | 2 +- terraform/load.go | 49 +++++++++-- terraform/load_test.go | 84 +++++++++++++++++++ terraform/testdata/full-example/main.tf | 14 ++++ terraform/testdata/full-example/outputs.tf | 5 ++ terraform/testdata/full-example/variables.tf | 2 +- terraform/testdata/no-requirements/main.tf | 0 terraform/testdata/no-resources/main.tf | 0 terraform/testdata/read-comments/variables.tf | 5 ++ 19 files changed, 261 insertions(+), 14 deletions(-) create mode 100644 docs/how-to/ignore-resources.md create mode 100644 terraform/testdata/no-requirements/main.tf create mode 100644 terraform/testdata/no-resources/main.tf diff --git a/docs/how-to/generate-terraform-tfvars.md b/docs/how-to/generate-terraform-tfvars.md index fe0f126..14508c1 100644 --- a/docs/how-to/generate-terraform-tfvars.md +++ b/docs/how-to/generate-terraform-tfvars.md @@ -4,7 +4,7 @@ description: "How to generate terraform.tfvars file with terraform-docs" menu: docs: parent: "how-to" -weight: 207 +weight: 208 toc: false --- diff --git a/docs/how-to/github-action.md b/docs/how-to/github-action.md index 4413413..9554995 100644 --- a/docs/how-to/github-action.md +++ b/docs/how-to/github-action.md @@ -4,7 +4,7 @@ description: "How to use terraform-docs with GitHub Actions" menu: docs: parent: "how-to" -weight: 208 +weight: 209 toc: false --- diff --git a/docs/how-to/ignore-resources.md b/docs/how-to/ignore-resources.md new file mode 100644 index 0000000..0367c3d --- /dev/null +++ b/docs/how-to/ignore-resources.md @@ -0,0 +1,74 @@ +--- +title: "Ignore Resources to be Generated" +description: "How to ignore resources from generated output" +menu: + docs: + parent: "how-to" +weight: 204 +toc: false +--- + +Since `v0.18.0` + +Any type of resources can be ignored from the generated output by prepending them +with a comment `terraform-docs-ignore`. Supported type of Terraform resources to +get ignored are: + +- `resource` +- `data` +- `module` +- `variable` +- `output` + +{{< alert type="info" >}} +If a `resource` or `data` is ignored, their corresponding discovered provider +will also get ignored from "Providers" section. +{{< /alert>}} + +Take the following example: + +```hcl +################################################################## +# All of the following will be ignored from the generated output # +################################################################## + +# terraform-docs-ignore +resource "foo_resource" "foo" {} + +# This resource is going to get ignored from generated +# output by using the following known comment. +# +# terraform-docs-ignore +# +# The ignore keyword also doesn't have to be the first, +# last, or the only thing in a leading comment +resource "bar_resource" "bar" {} + +# terraform-docs-ignore +data "foo_data_resource" "foo" {} + +# terraform-docs-ignore +data "bar_data_resource" "bar" {} + +// terraform-docs-ignore +module "foo" { + source = "foo" + version = "x.x.x" +} + +# terraform-docs-ignore +variable "foo" { + default = "foo" +} + +// terraform-docs-ignore +output "foo" { + value = "foo" +} +``` + +{{< alert type="info" >}} +The ignore keyword (i.e. `terraform-docs-ignore`) doesn't have to be the first, +last, or only thing in a leading comment. As long as the keyword is present in +a comment, the following resource will get ignored. +{{< /alert>}} diff --git a/docs/how-to/include-examples.md b/docs/how-to/include-examples.md index fb1965e..557627e 100644 --- a/docs/how-to/include-examples.md +++ b/docs/how-to/include-examples.md @@ -4,7 +4,7 @@ description: "How to include example in terraform-docs generated output" menu: docs: parent: "how-to" -weight: 205 +weight: 206 toc: false --- diff --git a/docs/how-to/insert-output-to-file.md b/docs/how-to/insert-output-to-file.md index 54f11a0..90da9d4 100644 --- a/docs/how-to/insert-output-to-file.md +++ b/docs/how-to/insert-output-to-file.md @@ -4,7 +4,7 @@ description: "How to insert generated terraform-docs output to file" menu: docs: parent: "how-to" -weight: 204 +weight: 205 toc: false --- diff --git a/docs/how-to/pre-commit-hooks.md b/docs/how-to/pre-commit-hooks.md index 91f093c..80f64e7 100644 --- a/docs/how-to/pre-commit-hooks.md +++ b/docs/how-to/pre-commit-hooks.md @@ -4,7 +4,7 @@ description: "How to use pre-commit hooks with terraform-docs" menu: docs: parent: "how-to" -weight: 209 +weight: 210 toc: false --- diff --git a/docs/how-to/recursive-submodules.md b/docs/how-to/recursive-submodules.md index d28ccd0..0751b51 100644 --- a/docs/how-to/recursive-submodules.md +++ b/docs/how-to/recursive-submodules.md @@ -4,7 +4,7 @@ description: "How to generate submodules documentation recursively with terrafor menu: docs: parent: "how-to" -weight: 206 +weight: 207 toc: false --- diff --git a/examples/main.tf b/examples/main.tf index 8b31f04..a800ffa 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -63,8 +63,20 @@ data "aws_caller_identity" "ident" { provider = "aws.ident" } +# terraform-docs-ignore +data "aws_caller_identity" "ignored" { + provider = "aws" +} + resource "null_resource" "foo" {} +# This resource is going to get ignored from generated +# output by using the following known comment. +# terraform-docs-ignore +# And the ignore keyword also doesn't have to be the first, +# last, or only thing in a leading comment +resource "null_resource" "ignored" {} + module "bar" { source = "baz" version = "4.5.6" @@ -84,3 +96,9 @@ module "baz" { module "foobar" { source = "git@github.com:module/path?ref=v7.8.9" } + +// terraform-docs-ignore +module "ignored" { + source = "foobaz" + version = "7.8.9" +} diff --git a/examples/outputs.tf b/examples/outputs.tf index 5bc3fef..e495382 100644 --- a/examples/outputs.tf +++ b/examples/outputs.tf @@ -17,3 +17,8 @@ output "output-0.12" { value = join(",", var.list-3) description = "terraform 0.12 only" } + +// terraform-docs-ignore +output "ignored" { + value = "ignored" +} diff --git a/examples/variables.tf b/examples/variables.tf index b58e27c..bafab8f 100644 --- a/examples/variables.tf +++ b/examples/variables.tf @@ -14,6 +14,11 @@ variable "bool-1" { default = true } +# terraform-docs-ignore +variable "ignored" { + default = "" +} + variable "string-3" { default = "" } diff --git a/go.mod b/go.mod index 823e4d1..ebc3aaf 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 github.com/terraform-docs/terraform-config-inspect v0.0.0-20210728164355-9c1f178932fa + golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d gopkg.in/yaml.v3 v3.0.1 honnef.co/go/tools v0.3.2 mvdan.cc/xurls/v2 v2.5.0 @@ -58,7 +59,6 @@ require ( github.com/zclconf/go-cty v1.14.4 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect diff --git a/terraform/load.go b/terraform/load.go index 348f346..fea188b 100644 --- a/terraform/load.go +++ b/terraform/load.go @@ -189,7 +189,7 @@ func loadInputs(tfmodule *tfconfig.Module, config *print.Config) ([]*Input, []*I for _, input := range tfmodule.Variables { comments := loadComments(input.Pos.Filename, input.Pos.Line) - // Skip over inputs that are marked as being ignored + // skip over inputs that are marked as being ignored if strings.Contains(comments, "terraform-docs-ignore") { continue } @@ -252,13 +252,20 @@ func loadModulecalls(tfmodule *tfconfig.Module, config *print.Config) []*ModuleC var source, version string for _, m := range tfmodule.ModuleCalls { - source, version = formatSource(m.Source, m.Version) + comments := loadComments(m.Pos.Filename, m.Pos.Line) + + // skip over modules that are marked as being ignored + if strings.Contains(comments, "terraform-docs-ignore") { + continue + } description := "" if config.Settings.ReadComments { - description = loadComments(m.Pos.Filename, m.Pos.Line) + description = comments } + source, version = formatSource(m.Source, m.Version) + modules = append(modules, &ModuleCall{ Name: m.Name, Source: source, @@ -284,10 +291,17 @@ func loadOutputs(tfmodule *tfconfig.Module, config *print.Config) ([]*Output, er } } for _, o := range tfmodule.Outputs { + comments := loadComments(o.Pos.Filename, o.Pos.Line) + + // skip over outputs that are marked as being ignored + if strings.Contains(comments, "terraform-docs-ignore") { + continue + } + // convert CRLF to LF early on (https://github.com/terraform-docs/terraform-docs/issues/584) description := strings.ReplaceAll(o.Description, "\r\n", "\n") if description == "" && config.Settings.ReadComments { - description = loadComments(o.Pos.Filename, o.Pos.Line) + description = comments } output := &Output{ @@ -337,7 +351,11 @@ func loadOutputValues(config *print.Config) (map[string]*output, error) { return terraformOutputs, err } -func loadProviders(tfmodule *tfconfig.Module, config *print.Config) []*Provider { +func loadProviders(tfmodule *tfconfig.Module, config *print.Config) []*Provider { //nolint:gocyclo + // NOTE(khos2ow): this function is over our cyclomatic complexity goal. + // Be wary when adding branches, and look for functionality that could + // be reasonably moved into an injected dependency. + type provider struct { Name string `hcl:"name,label"` Version string `hcl:"version"` @@ -367,6 +385,13 @@ func loadProviders(tfmodule *tfconfig.Module, config *print.Config) []*Provider for _, resource := range resources { for _, r := range resource { + comments := loadComments(r.Pos.Filename, r.Pos.Line) + + // skip over resources that are marked as being ignored + if strings.Contains(comments, "terraform-docs-ignore") { + continue + } + var version = "" if l, ok := lock[r.Provider.Name]; ok { version = l.Version @@ -375,6 +400,10 @@ func loadProviders(tfmodule *tfconfig.Module, config *print.Config) []*Provider } key := fmt.Sprintf("%s.%s", r.Provider.Name, r.Provider.Alias) + if _, ok := discovered[key]; ok { + continue + } + discovered[key] = &Provider{ Name: r.Provider.Name, Alias: types.String(r.Provider.Alias), @@ -391,6 +420,7 @@ func loadProviders(tfmodule *tfconfig.Module, config *print.Config) []*Provider for _, provider := range discovered { providers = append(providers, provider) } + return providers } @@ -427,6 +457,13 @@ func loadResources(tfmodule *tfconfig.Module, config *print.Config) []*Resource for _, resource := range allResources { for _, r := range resource { + comments := loadComments(r.Pos.Filename, r.Pos.Line) + + // skip over resources that are marked as being ignored + if strings.Contains(comments, "terraform-docs-ignore") { + continue + } + var version string if rv, ok := tfmodule.RequiredProviders[r.Provider.Name]; ok { version = resourceVersion(rv.VersionConstraints) @@ -444,7 +481,7 @@ func loadResources(tfmodule *tfconfig.Module, config *print.Config) []*Resource description := "" if config.Settings.ReadComments { - description = loadComments(r.Pos.Filename, r.Pos.Line) + description = comments } discovered[key] = &Resource{ diff --git a/terraform/load_test.go b/terraform/load_test.go index 1a5b57c..89a87eb 100644 --- a/terraform/load_test.go +++ b/terraform/load_test.go @@ -11,12 +11,14 @@ the root directory of this source tree. package terraform import ( + "fmt" "os" "path/filepath" "sort" "testing" "github.com/stretchr/testify/assert" + "golang.org/x/exp/slices" "github.com/terraform-docs/terraform-docs/print" ) @@ -38,6 +40,7 @@ func TestLoadModuleWithOptions(t *testing.T) { assert.Equal(true, module.HasModuleCalls()) assert.Equal(true, module.HasProviders()) assert.Equal(true, module.HasRequirements()) + assert.Equal(true, module.HasResources()) config.Sections.Header = false config.Sections.Footer = true @@ -774,6 +777,87 @@ func TestLoadProviders(t *testing.T) { } } +func TestLoadRequirements(t *testing.T) { + type expected struct { + requirements []string + } + tests := []struct { + name string + path string + expected expected + }{ + { + name: "load module requirements from path", + path: "full-example", + expected: expected{ + requirements: []string{"terraform >= 0.12", "aws >= 2.15.0"}, + }, + }, + { + name: "load module requirements from path", + path: "no-requirements", + expected: expected{ + requirements: []string{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + + module, _ := loadModule(filepath.Join("testdata", tt.path)) + requirements := loadRequirements(module) + + assert.Equal(len(tt.expected.requirements), len(requirements)) + + for i, r := range tt.expected.requirements { + assert.Equal(r, fmt.Sprintf("%s %s", requirements[i].Name, requirements[i].Version)) + } + }) + } +} + +func TestLoadResources(t *testing.T) { + type expected struct { + resources []string + } + tests := []struct { + name string + path string + expected expected + }{ + { + name: "load module resources from path", + path: "full-example", + expected: expected{ + resources: []string{"tls_private_key.baz", "aws_caller_identity.current", "null_resource.foo"}, + }, + }, + { + name: "load module resources from path", + path: "no-resources", + expected: expected{ + resources: []string{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + + config := print.NewConfig() + module, _ := loadModule(filepath.Join("testdata", tt.path)) + resources := loadResources(module, config) + + assert.Equal(len(tt.expected.resources), len(resources)) + + for _, r := range resources { + assert.True(slices.Contains(tt.expected.resources, fmt.Sprintf("%s_%s.%s", r.ProviderName, r.Type, r.Name))) + } + }) + } +} + func TestLoadComments(t *testing.T) { tests := []struct { name string diff --git a/terraform/testdata/full-example/main.tf b/terraform/testdata/full-example/main.tf index d9946a7..de5adc3 100644 --- a/terraform/testdata/full-example/main.tf +++ b/terraform/testdata/full-example/main.tf @@ -21,8 +21,16 @@ data "aws_caller_identity" "current" { provider = "aws" } +# terraform-docs-ignore +data "aws_caller_identity" "ignored" { + provider = "aws" +} + resource "null_resource" "foo" {} +# terraform-docs-ignore +resource "null_resource" "ignored" {} + module "foo" { source = "bar" version = "1.2.3" @@ -35,3 +43,9 @@ module "foobar" { locals { arn = provider::aws::arn_parse("arn:aws:iam::444455556666:role/example") } + +// terraform-docs-ignore +module "ignored" { + source = "baz" + version = "1.2.3" +} diff --git a/terraform/testdata/full-example/outputs.tf b/terraform/testdata/full-example/outputs.tf index 012ffb6..211ae2a 100644 --- a/terraform/testdata/full-example/outputs.tf +++ b/terraform/testdata/full-example/outputs.tf @@ -17,3 +17,8 @@ output "B" { output "D" { value = null } + +# terraform-docs-ignore +output "ignored" { + value = "e" +} diff --git a/terraform/testdata/full-example/variables.tf b/terraform/testdata/full-example/variables.tf index ecec599..f395bbd 100644 --- a/terraform/testdata/full-example/variables.tf +++ b/terraform/testdata/full-example/variables.tf @@ -30,7 +30,7 @@ variable "G" { } # terraform-docs-ignore -variable "H" { +variable "ignored" { description = "H description" default = null } diff --git a/terraform/testdata/no-requirements/main.tf b/terraform/testdata/no-requirements/main.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/testdata/no-resources/main.tf b/terraform/testdata/no-resources/main.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/testdata/read-comments/variables.tf b/terraform/testdata/read-comments/variables.tf index be42f66..5ad8d3f 100644 --- a/terraform/testdata/read-comments/variables.tf +++ b/terraform/testdata/read-comments/variables.tf @@ -7,3 +7,8 @@ variable "B" { output "B" { value = "b" } + +// terraform-docs-ignore +output "ignored" { + value = "c" +}