Generate submodules documents with '--resurcive' flag

Considering the file strucutre below of main module and its submodules,
now it is possible to generate documentation for them main and all its
submodules in one execution, with `--recursive` flag.

Note that generating documentation recursively is allowed only with
`--output-file` set.

Path to find submodules can be configured with `--recursive-path`
(defaults to `modules`).

Each submodule can also have their own `.terraform-docs.yml` confi file,
to override configuration from root module.

```
.
├── README.md
├── main.tf
├── modules
│   └── my-sub-module
│       ├── README.md
│       ├── main.tf
│       ├── variables.tf
│       └── versions.tf
├── outputs.tf
├── variables.tf
└── versions.tf
```

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>
This commit is contained in:
Khosrow Moossavi
2021-06-03 13:53:40 -04:00
parent 45bf4f6713
commit 1450ee9a10
35 changed files with 401 additions and 197 deletions

View File

@@ -19,15 +19,15 @@ import (
)
// NewCommand returns a new cobra.Command for 'asciidoc' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "asciidoc [PATH]",
Aliases: []string{"adoc"},
Short: "Generate AsciiDoc of inputs and outputs",
Annotations: cli.Annotations("asciidoc"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
// flags
@@ -39,8 +39,8 @@ func NewCommand(config *cli.Config) *cobra.Command {
cmd.PersistentFlags().BoolVar(&config.Settings.Type, "type", true, "show Type column or section")
// subcommands
cmd.AddCommand(document.NewCommand(config))
cmd.AddCommand(table.NewCommand(config))
cmd.AddCommand(document.NewCommand(runtime, config))
cmd.AddCommand(table.NewCommand(runtime, config))
return cmd
}

View File

@@ -17,15 +17,15 @@ import (
)
// NewCommand returns a new cobra.Command for 'asciidoc document' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "document [PATH]",
Aliases: []string{"doc"},
Short: "Generate AsciiDoc document of inputs and outputs",
Annotations: cli.Annotations("asciidoc document"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
return cmd
}

View File

@@ -17,15 +17,15 @@ import (
)
// NewCommand returns a new cobra.Command for 'asciidoc table' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "table [PATH]",
Aliases: []string{"tbl"},
Short: "Generate AsciiDoc tables of inputs and outputs",
Annotations: cli.Annotations("asciidoc table"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
return cmd
}

View File

@@ -17,14 +17,14 @@ import (
)
// NewCommand returns a new cobra.Command for 'json' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "json [PATH]",
Short: "Generate JSON of inputs and outputs",
Annotations: cli.Annotations("json"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
// flags

View File

@@ -17,15 +17,15 @@ import (
)
// NewCommand returns a new cobra.Command for 'markdown document' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "document [PATH]",
Aliases: []string{"doc"},
Short: "Generate Markdown document of inputs and outputs",
Annotations: cli.Annotations("markdown document"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
return cmd
}

View File

@@ -19,15 +19,15 @@ import (
)
// NewCommand returns a new cobra.Command for 'markdown' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "markdown [PATH]",
Aliases: []string{"md"},
Short: "Generate Markdown of inputs and outputs",
Annotations: cli.Annotations("markdown"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
// flags
@@ -41,8 +41,8 @@ func NewCommand(config *cli.Config) *cobra.Command {
cmd.PersistentFlags().BoolVar(&config.Settings.Type, "type", true, "show Type column or section")
// subcommands
cmd.AddCommand(document.NewCommand(config))
cmd.AddCommand(table.NewCommand(config))
cmd.AddCommand(document.NewCommand(runtime, config))
cmd.AddCommand(table.NewCommand(runtime, config))
return cmd
}

View File

@@ -17,15 +17,15 @@ import (
)
// NewCommand returns a new cobra.Command for 'markdown table' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "table [PATH]",
Aliases: []string{"tbl"},
Short: "Generate Markdown tables of inputs and outputs",
Annotations: cli.Annotations("markdown table"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
return cmd
}

View File

@@ -17,14 +17,14 @@ import (
)
// NewCommand returns a new cobra.Command for pretty formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "pretty [PATH]",
Short: "Generate colorized pretty of inputs and outputs",
Annotations: cli.Annotations("pretty"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
// flags

View File

@@ -43,6 +43,7 @@ func Execute() error {
// NewCommand returns a new cobra.Command for 'root' command
func NewCommand() *cobra.Command {
config := cli.DefaultConfig()
runtime := cli.NewRuntime(config)
cmd := &cobra.Command{
Args: cobra.MaximumNArgs(1),
Use: "terraform-docs [PATH]",
@@ -52,12 +53,14 @@ func NewCommand() *cobra.Command {
SilenceUsage: true,
SilenceErrors: true,
Annotations: cli.Annotations("root"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
// flags
cmd.PersistentFlags().StringVarP(&config.File, "config", "c", ".terraform-docs.yml", "config file name")
cmd.PersistentFlags().BoolVar(&config.Recursive, "recursive", false, "update submodules recursively (default false)")
cmd.PersistentFlags().StringVar(&config.RecursivePath, "recursive-path", "modules", "submodules path to recursively update")
cmd.PersistentFlags().StringSliceVar(&config.Sections.Show, "show", []string{}, "show section ["+cli.AllSections+"]")
cmd.PersistentFlags().StringSliceVar(&config.Sections.Hide, "hide", []string{}, "hide section ["+cli.AllSections+"]")
@@ -79,14 +82,14 @@ func NewCommand() *cobra.Command {
cmd.PersistentFlags().StringVar(&config.OutputValues.From, "output-values-from", "", "inject output values from file into outputs (default \"\")")
// formatter subcommands
cmd.AddCommand(asciidoc.NewCommand(config))
cmd.AddCommand(json.NewCommand(config))
cmd.AddCommand(markdown.NewCommand(config))
cmd.AddCommand(pretty.NewCommand(config))
cmd.AddCommand(tfvars.NewCommand(config))
cmd.AddCommand(toml.NewCommand(config))
cmd.AddCommand(xml.NewCommand(config))
cmd.AddCommand(yaml.NewCommand(config))
cmd.AddCommand(asciidoc.NewCommand(runtime, config))
cmd.AddCommand(json.NewCommand(runtime, config))
cmd.AddCommand(markdown.NewCommand(runtime, config))
cmd.AddCommand(pretty.NewCommand(runtime, config))
cmd.AddCommand(tfvars.NewCommand(runtime, config))
cmd.AddCommand(toml.NewCommand(runtime, config))
cmd.AddCommand(xml.NewCommand(runtime, config))
cmd.AddCommand(yaml.NewCommand(runtime, config))
// other subcommands
cmd.AddCommand(completion.NewCommand())

View File

@@ -17,14 +17,14 @@ import (
)
// NewCommand returns a new cobra.Command for 'tfvars hcl' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "hcl [PATH]",
Short: "Generate HCL format of terraform.tfvars of inputs",
Annotations: cli.Annotations("tfvars hcl"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
cmd.PersistentFlags().BoolVar(&config.Settings.Description, "description", false, "show Descriptions on variables")
return cmd

View File

@@ -17,14 +17,14 @@ import (
)
// NewCommand returns a new cobra.Command for 'tfvars json' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "json [PATH]",
Short: "Generate JSON format of terraform.tfvars of inputs",
Annotations: cli.Annotations("tfvars json"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
return cmd
}

View File

@@ -19,7 +19,7 @@ import (
)
// NewCommand returns a new cobra.Command for 'tfvars' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "tfvars [PATH]",
@@ -28,8 +28,8 @@ func NewCommand(config *cli.Config) *cobra.Command {
}
// subcommands
cmd.AddCommand(hcl.NewCommand(config))
cmd.AddCommand(json.NewCommand(config))
cmd.AddCommand(hcl.NewCommand(runtime, config))
cmd.AddCommand(json.NewCommand(runtime, config))
return cmd
}

View File

@@ -17,14 +17,14 @@ import (
)
// NewCommand returns a new cobra.Command for 'toml' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "toml [PATH]",
Short: "Generate TOML of inputs and outputs",
Annotations: cli.Annotations("toml"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
return cmd
}

View File

@@ -17,14 +17,14 @@ import (
)
// NewCommand returns a new cobra.Command for 'xml' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "xml [PATH]",
Short: "Generate XML of inputs and outputs",
Annotations: cli.Annotations("xml"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
return cmd
}

View File

@@ -17,14 +17,14 @@ import (
)
// NewCommand returns a new cobra.Command for 'yaml' formatter
func NewCommand(config *cli.Config) *cobra.Command {
func NewCommand(runtime *cli.Runtime, config *cli.Config) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.ExactArgs(1),
Use: "yaml [PATH]",
Short: "Generate YAML of inputs and outputs",
Annotations: cli.Annotations("yaml"),
PreRunE: cli.PreRunEFunc(config),
RunE: cli.RunEFunc(config),
PreRunE: runtime.PreRunEFunc,
RunE: runtime.RunEFunc,
}
return cmd
}

View File

@@ -39,6 +39,8 @@ terraform-docs asciidoc document [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--required show Required column or section (default true)
--sensitive show Sensitive column or section (default true)
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]

View File

@@ -39,6 +39,8 @@ terraform-docs asciidoc table [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--required show Required column or section (default true)
--sensitive show Sensitive column or section (default true)
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]

View File

@@ -42,6 +42,8 @@ terraform-docs asciidoc [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -37,6 +37,8 @@ terraform-docs json [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -41,6 +41,8 @@ terraform-docs markdown document [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--required show Required column or section (default true)
--sensitive show Sensitive column or section (default true)
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]

View File

@@ -41,6 +41,8 @@ terraform-docs markdown table [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--required show Required column or section (default true)
--sensitive show Sensitive column or section (default true)
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]

View File

@@ -44,6 +44,8 @@ terraform-docs markdown [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -37,6 +37,8 @@ terraform-docs pretty [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -31,6 +31,8 @@ terraform-docs [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -37,6 +37,8 @@ terraform-docs tfvars hcl [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -36,6 +36,8 @@ terraform-docs tfvars json [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -32,6 +32,8 @@ Generate terraform.tfvars of inputs.
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -36,6 +36,8 @@ terraform-docs toml [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -36,6 +36,8 @@ terraform-docs xml [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -36,6 +36,8 @@ terraform-docs yaml [PATH] [flags]
--output-template string output template (default "<!-- BEGIN_TF_DOCS -->\n{{ .Content }}\n<!-- END_TF_DOCS -->")
--output-values inject output values into outputs (default false)
--output-values-from string inject output values from file into outputs (default "")
--recursive update submodules recursively (default false)
--recursive-path string submodules path to recursively update (default "modules")
--show strings show section [all, data-sources, footer, header, inputs, modules, outputs, providers, requirements, resources]
--sort sort items (default true)
--sort-by string sort items by criteria [name, required, type] (default "name")

View File

@@ -223,6 +223,42 @@ $ terraform-docs markdown table --output-file ./docs/README.md .
$ terraform-docs markdown table --output-file /path/to/module/docs/README.md .
```
## Recursive Submodules
Since `v0.15.0`
Considering the file strucutre below of main module and its submodules, it is
possible to generate documentation for them main and all its submodules in one
execution, with `--recursive` flag.
Note that generating documentation recursively is allowed only with `--output-file`
set.
Path to find submodules can be configured with `--recursive-path` (defaults to
`modules`).
Each submodule can also have their own `.terraform-docs.yml` confi file, to
override configuration from root module.
```bash
$ pwd
/path/to/module
$ tree .
.
├── README.md
├── main.tf
├── modules
│   └── my-sub-module
│   ├── README.md
│   ├── main.tf
│   ├── variables.tf
│   └── versions.tf
├── outputs.tf
├── variables.tf
└── versions.tf
```
## Generate terraform.tfvars
Since `v0.9.0`

View File

@@ -51,17 +51,19 @@ var flagMappings = map[string]string{
// Config represents all the available config options that can be accessed and passed through CLI
type Config struct {
File string `mapstructure:"-"`
Formatter string `mapstructure:"formatter"`
Version string `mapstructure:"version"`
HeaderFrom string `mapstructure:"header-from"`
FooterFrom string `mapstructure:"footer-from"`
Content string `mapstructure:"content"`
Sections sections `mapstructure:"sections"`
Output output `mapstructure:"output"`
OutputValues outputvalues `mapstructure:"output-values"`
Sort sort `mapstructure:"sort"`
Settings settings `mapstructure:"settings"`
File string `mapstructure:"-"`
Recursive bool `mapstructure:"-"`
RecursivePath string `mapstructure:"-"`
Formatter string `mapstructure:"formatter"`
Version string `mapstructure:"version"`
HeaderFrom string `mapstructure:"header-from"`
FooterFrom string `mapstructure:"footer-from"`
Content string `mapstructure:"content"`
Sections sections `mapstructure:"sections"`
Output output `mapstructure:"output"`
OutputValues outputvalues `mapstructure:"output-values"`
Sort sort `mapstructure:"sort"`
Settings settings `mapstructure:"settings"`
moduleRoot string
isFlagChanged func(string) bool
@@ -70,17 +72,19 @@ type Config struct {
// DefaultConfig returns new instance of Config with default values set
func DefaultConfig() *Config {
return &Config{
File: "",
Formatter: "",
Version: "",
HeaderFrom: "main.tf",
FooterFrom: "",
Content: "",
Sections: defaultSections(),
Output: defaultOutput(),
OutputValues: defaultOutputValues(),
Sort: defaultSort(),
Settings: defaultSettings(),
File: "",
Recursive: false,
RecursivePath: "modules",
Formatter: "",
Version: "",
HeaderFrom: "main.tf",
FooterFrom: "",
Content: "",
Sections: defaultSections(),
Output: defaultOutput(),
OutputValues: defaultOutputValues(),
Sort: defaultSort(),
Settings: defaultSettings(),
moduleRoot: "",
isFlagChanged: func(name string) bool { return false },
@@ -414,6 +418,11 @@ func (c *Config) process() error { //nolint:gocyclo
return fmt.Errorf("value of 'formatter' can't be empty")
}
// recursive
if c.Recursive && c.RecursivePath == "" {
return fmt.Errorf("value of '--recursive-path' can't be empty")
}
// sections
c.Sections.dataSources = c.Sections.visibility("data-sources")
c.Sections.header = c.Sections.visibility("header")

View File

@@ -547,9 +547,7 @@ func TestConfigProcess(t *testing.T) {
errMsg string
}{
"OK": {
config: func(c *Config) {
c.Formatter = "foo"
},
config: func(c *Config) {},
wantErr: false,
errMsg: "",
},
@@ -560,9 +558,16 @@ func TestConfigProcess(t *testing.T) {
wantErr: true,
errMsg: "value of 'formatter' can't be empty",
},
"RecursivePathEmpty": {
config: func(c *Config) {
c.Recursive = true
c.RecursivePath = ""
},
wantErr: true,
errMsg: "value of '--recursive-path' can't be empty",
},
"HeaderFromEmpty": {
config: func(c *Config) {
c.Formatter = "foo"
c.HeaderFrom = ""
},
wantErr: true,
@@ -570,7 +575,6 @@ func TestConfigProcess(t *testing.T) {
},
"FooterFrom": {
config: func(c *Config) {
c.Formatter = "foo"
c.FooterFrom = ""
c.Sections.footer = true
c.isFlagChanged = func(s string) bool { return true }
@@ -593,6 +597,7 @@ func TestConfigProcess(t *testing.T) {
assert := assert.New(t)
config := DefaultConfig()
config.Formatter = "foo"
tt.config(config)
err := config.process()

View File

@@ -15,6 +15,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
goversion "github.com/hashicorp/go-version"
"github.com/spf13/cobra"
@@ -28,144 +29,170 @@ import (
"github.com/terraform-docs/terraform-docs/internal/version"
)
// PreRunEFunc returns actual 'cobra.Command#PreRunE' function for 'formatter'
// commands. This functions reads and normalizes flags and arguments passed
// Runtime represents the execution runtime for CLI.
type Runtime struct {
rootDir string
formatter string
config *Config
cmd *cobra.Command
}
// NewRuntime retruns new instance of Runtime. If `config` is not provided
// default config will be used.
func NewRuntime(config *Config) *Runtime {
if config == nil {
config = DefaultConfig()
}
return &Runtime{config: config}
}
// PreRunEFunc is the 'cobra.Command#PreRunE' function for 'formatter'
// commands. This function reads and normalizes flags and arguments passed
// through CLI execution.
func PreRunEFunc(config *Config) func(*cobra.Command, []string) error { //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.
func (r *Runtime) PreRunEFunc(cmd *cobra.Command, args []string) error {
r.formatter = cmd.Annotations["command"]
return func(cmd *cobra.Command, args []string) error {
config.isFlagChanged = cmd.Flags().Changed
// root command must have an argument, otherwise we're going to show help
if r.formatter == "root" && len(args) == 0 {
cmd.Help() //nolint:errcheck,gosec
os.Exit(0)
}
formatter := cmd.Annotations["command"]
r.config.isFlagChanged = cmd.Flags().Changed
r.rootDir = args[0]
r.cmd = cmd
// root command must have an argument, otherwise we're going to show help
if formatter == "root" && len(args) == 0 {
cmd.Help() //nolint:errcheck,gosec
os.Exit(0)
}
// this can only happen in one way: terraform-docs -c "" /path/to/module
if r.config.File == "" {
return fmt.Errorf("value of '--config' can't be empty")
}
// this can only happen in one way: terraform-docs -c "" /path/to/module
if config.File == "" {
return errors.New("value of '--config' can't be empty")
}
// attempt to read config file and override them with corresponding flags
if err := r.readConfig(r.config, ""); err != nil {
return err
}
v := viper.New()
return checkConstraint(r.config.Version, version.Core())
}
if config.isFlagChanged("config") {
v.SetConfigFile(config.File)
} else {
v.SetConfigName(".terraform-docs")
v.SetConfigType("yml")
}
type module struct {
rootDir string
config *Config
}
v.AddConfigPath(args[0]) // first look at module root
v.AddConfigPath(args[0] + "/.config") // then .config/ folder at module root
v.AddConfigPath(".") // then current directory
v.AddConfigPath(".config") // then .config/ folder at current directory
v.AddConfigPath("$HOME/.tfdocs.d") // and finally $HOME/.tfdocs.d/
// RunEFunc is the 'cobra.Command#RunE' function for 'formatter' commands. It attempts
// to discover submodules, on `--recursive` flag, and generates the content for them
// as well as the root module.
func (r *Runtime) RunEFunc(cmd *cobra.Command, args []string) error {
modules := []module{
{rootDir: r.rootDir, config: r.config},
}
if err := v.ReadInConfig(); err != nil {
var perr *os.PathError
if errors.As(err, &perr) {
return fmt.Errorf("config file %s not found", config.File)
}
var cerr viper.ConfigFileNotFoundError
if !errors.As(err, &cerr) {
return err
}
// config is not provided, only show error for root command
if formatter == "root" {
cmd.Help() //nolint:errcheck,gosec
os.Exit(0)
}
}
// bind flags to viper
bindFlags(cmd, v)
if err := v.Unmarshal(config); err != nil {
return fmt.Errorf("unable to decode config, %w", err)
}
if err := checkConstraint(config.Version, version.Core()); err != nil {
// Generating content recursively is only allowed when `config.Output.File`
// is set. Otherwise it would be impossible to distinguish where output of
// one module ends and the other begin, if content is outpput to stdout.
if r.config.Recursive && r.config.RecursivePath != "" {
items, err := r.findSubmodules()
if err != nil {
return err
}
// explicitly setting formatter to Config for non-root commands this
// will effectively override formattter properties from config file
// if 1) config file exists and 2) formatter is set and 3) explicitly
// a subcommand was executed in the terminal
if formatter != "root" {
config.Formatter = formatter
modules = append(modules, items...)
}
for _, module := range modules {
cfg := r.config
// If submodules contains its own configuration file, use that instead
if module.config != nil {
cfg = module.config
}
// set the module root directory
config.moduleRoot = args[0]
cfg.moduleRoot = module.rootDir
// process and validate configuration
return config.process()
if err := cfg.process(); err != nil {
return err
}
if r.config.Recursive && cfg.Output.File == "" {
return fmt.Errorf("value of '--output-file' cannot be empty with '--recursive'")
}
if err := generateContent(cfg); err != nil {
return err
}
}
return nil
}
// RunEFunc returns actual 'cobra.Command#RunE' function for 'formatter' commands.
// This functions extract print.Settings and terraform.Options from generated and
// normalized Config and initializes required print.Format instance and executes it.
func RunEFunc(config *Config) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, _ []string) error {
settings, options := config.extract()
options.Path = config.moduleRoot
// readConfig attempts to read config file, either default `.terraform-docs.yml`
// or provided file with `-c, --config` flag. It will then attempt to override
// them with corresponding flags (if set).
func (r *Runtime) readConfig(config *Config, submoduleDir string) error {
v := viper.New()
module, err := terraform.LoadWithOptions(options)
if err != nil {
return err
}
formatter, err := format.Factory(config.Formatter, settings)
if err != nil {
plugins, perr := plugin.Discover()
if perr != nil {
return fmt.Errorf("formatter '%s' not found", config.Formatter)
}
client, found := plugins.Get(config.Formatter)
if !found {
return fmt.Errorf("formatter '%s' not found", config.Formatter)
}
content, cerr := client.Execute(pluginsdk.ExecuteArgs{
Module: module.Convert(),
Settings: settings.Convert(),
})
if cerr != nil {
return cerr
}
return writeContent(config, content)
}
generator, err := formatter.Generate(module)
if err != nil {
return err
}
generator.Path(config.moduleRoot)
content, err := generator.ExecuteTemplate(config.Content)
if err != nil {
return err
}
return writeContent(config, content)
if config.isFlagChanged("config") {
v.SetConfigFile(config.File)
} else {
v.SetConfigName(".terraform-docs")
v.SetConfigType("yml")
}
if submoduleDir != "" {
v.AddConfigPath(submoduleDir) // first look at submodule root
v.AddConfigPath(submoduleDir + "/.config") // then .config/ folder at submodule root
}
v.AddConfigPath(r.rootDir) // first look at module root
v.AddConfigPath(r.rootDir + "/.config") // then .config/ folder at module root
v.AddConfigPath(".") // then current directory
v.AddConfigPath(".config") // then .config/ folder at current directory
v.AddConfigPath("$HOME/.tfdocs.d") // and finally $HOME/.tfdocs.d/
if err := v.ReadInConfig(); err != nil {
var perr *os.PathError
if errors.As(err, &perr) {
return fmt.Errorf("config file %s not found", config.File)
}
var cerr viper.ConfigFileNotFoundError
if !errors.As(err, &cerr) {
return err
}
// config is not provided, only show error for root command
if r.formatter == "root" {
r.cmd.Help() //nolint:errcheck,gosec
os.Exit(0)
}
}
r.bindFlags(v)
if err := v.Unmarshal(config); err != nil {
return fmt.Errorf("unable to decode config, %w", err)
}
// explicitly setting formatter to Config for non-root commands this
// will effectively override formattter properties from config file
// if 1) config file exists and 2) formatter is set and 3) explicitly
// a subcommand was executed in the terminal
if r.formatter != "root" {
config.Formatter = r.formatter
}
return nil
}
// bindFlags binds current command's changed flags to viper
func bindFlags(cmd *cobra.Command, v *viper.Viper) {
// bindFlags binds current command's changed flags to viper.
func (r *Runtime) bindFlags(v *viper.Viper) {
sectionsCleared := false
fs := cmd.Flags()
fs := r.cmd.Flags()
fs.VisitAll(func(f *pflag.Flag) {
if !f.Changed {
return
@@ -197,6 +224,47 @@ func bindFlags(cmd *cobra.Command, v *viper.Viper) {
})
}
// findSubmodules generates list of submodules in `rootDir/RecursivePath` if
// `--recursive` flag is set. This keeps track of `.terraform-docs.yml` in any
// of the submodules (if exists) to override the root configuration.
func (r *Runtime) findSubmodules() ([]module, error) {
dir := filepath.Join(r.rootDir, r.config.RecursivePath)
if _, err := os.Stat(dir); os.IsNotExist(err) {
return nil, err
}
info, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
modules := []module{}
for _, file := range info {
if !file.IsDir() {
continue
}
var cfg *Config
path := filepath.Join(dir, file.Name())
cfgfile := filepath.Join(path, r.config.File)
if _, err := os.Stat(cfgfile); !os.IsNotExist(err) {
cfg = DefaultConfig()
if err := r.readConfig(cfg, path); err != nil {
return nil, err
}
}
modules = append(modules, module{rootDir: path, config: cfg})
}
return modules, nil
}
// checkConstraint validates if current version of terraform-docs being executed
// is valid against 'version' string provided in config file, and fail if the
// constraints is violated.
@@ -218,6 +286,59 @@ func checkConstraint(versionRange string, currentVersion string) error {
return nil
}
// generateContent extracts print.Settings and terraform.Options from normalized
// Config and generates the output content for the module (and submodules if avialble)
// and write the result to the output (either stdout or a file).
func generateContent(config *Config) error {
settings, options := config.extract()
options.Path = config.moduleRoot
module, err := terraform.LoadWithOptions(options)
if err != nil {
return err
}
formatter, err := format.Factory(config.Formatter, settings)
// formatter is unkowns, this might mean that the intended formatter is
// coming from a plugin. We are going to attempt to find a plugin with
// that name and generate the content with it or error out if not found.
if err != nil {
plugins, perr := plugin.Discover()
if perr != nil {
return fmt.Errorf("formatter '%s' not found", config.Formatter)
}
client, found := plugins.Get(config.Formatter)
if !found {
return fmt.Errorf("formatter '%s' not found", config.Formatter)
}
content, cerr := client.Execute(pluginsdk.ExecuteArgs{
Module: module.Convert(),
Settings: settings.Convert(),
})
if cerr != nil {
return cerr
}
return writeContent(config, content)
}
generator, err := formatter.Generate(module)
if err != nil {
return err
}
generator.Path(options.Path)
content, err := generator.ExecuteTemplate(config.Content)
if err != nil {
return err
}
return writeContent(config, content)
}
// writeContent to a Writer. This can either be os.Stdout or specific
// file (e.g. README.md) if '--output-file' is provided.
func writeContent(config *Config, content string) error {

View File

@@ -154,7 +154,7 @@ func (fw *fileWriter) inject(filename string, content string, generated string)
return fw.write(filename, []byte(generated))
}
// wrtie the content to io.Writer. If no io.Writer is available,
// write the content to io.Writer. If no io.Writer is available,
// it will be written to 'filename'.
func (fw *fileWriter) write(filename string, p []byte) (int, error) {
// if run in check mode return exit 1