Merge pull request #570 from khos2ow/recursive-override

Add recursive to config and enable partial config override
This commit is contained in:
Khosrow Moossavi
2021-09-30 18:56:24 -04:00
committed by GitHub
12 changed files with 211 additions and 58 deletions

View File

@@ -181,6 +181,10 @@ version: ""
header-from: main.tf
footer-from: ""
recursive:
enabled: false
path: modules
sections:
hide: []
show: []

View File

@@ -60,8 +60,8 @@ func NewCommand() *cobra.Command {
// 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().BoolVar(&config.Recursive.Enabled, "recursive", false, "update submodules recursively (default false)")
cmd.PersistentFlags().StringVar(&config.Recursive.Path, "recursive-path", "modules", "submodules path to recursively update")
cmd.PersistentFlags().StringSliceVar(&config.Sections.Show, "show", []string{}, "show section ["+print.AllSections+"]")
cmd.PersistentFlags().StringSliceVar(&config.Sections.Hide, "hide", []string{}, "hide section ["+print.AllSections+"]")

View File

@@ -11,7 +11,7 @@ toc: false
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
possible to generate documentation for the main and all its submodules in one
execution, with `--recursive` flag.
{{< alert type="warning" >}}
@@ -42,4 +42,41 @@ $ tree .
├── outputs.tf
├── variables.tf
└── versions.tf
$ terraform-docs markdown --recursive --output-file README.md .
```
Alternatively `recursive.enabled` config also can be used instead of CLI flag.
```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
├── ...
└── .terraform-docs.yml
$ cat .terraform-docs.yml
formatter: markdown table
recursive:
enabled: true
output:
file: README.md
mode: inject
$ terraform-docs .
```

View File

@@ -76,6 +76,10 @@ version: ""
header-from: main.tf
footer-from: ""
recursive:
enabled: false
path: modules
sections:
hide: []
show: []

View File

@@ -0,0 +1,52 @@
---
title: "recursive"
description: "recursive configuration"
menu:
docs:
parent: "configuration"
weight: 127
toc: true
---
Since `v0.16.0`
Documentation for main module and its submodules can be generated all in one
execution using `recursive` config. It can be enabled with `recursive.enabled: true`.
Path to find submodules can be configured with `recursive.path` (defaults to
`modules`).
{{< alert type="warning" >}}
Generating documentation recursively is allowed only with `output.file`
set.
{{< /alert >}}
Each submodule can also have their own `.terraform-docs.yml` config file, to
override configuration from root module.
## Options
Available options with their default values.
```yaml
recursive:
enabled: false
path: modules
```
## Examples
Enable recursive mode for submodules folder.
```yaml
recursive:
enabled: true
```
Provide alternative name of submodules folder.
```yaml
recursive:
enabled: true
path: submodules-folder
```

View File

@@ -4,7 +4,7 @@ description: "sections configuration"
menu:
docs:
parent: "configuration"
weight: 127
weight: 128
toc: true
---

View File

@@ -4,7 +4,7 @@ description: "settings configuration"
menu:
docs:
parent: "configuration"
weight: 128
weight: 129
toc: true
---

View File

@@ -4,7 +4,7 @@ description: "sort configuration"
menu:
docs:
parent: "configuration"
weight: 129
weight: 130
toc: true
---

View File

@@ -1,13 +1,21 @@
# # see: https://terraform-docs.io/user-guide/configuration/#version
# # see: https://terraform-docs.io/user-guide/configuration/version
# version: ">= 0.10, < 0.12"
# see: https://terraform-docs.io/user-guide/configuration/#formatters
# see: https://terraform-docs.io/user-guide/configuration/formatter
formatter: markdown table
# see: https://terraform-docs.io/user-guide/configuration/header-from
header-from: doc.txt
# see: https://terraform-docs.io/user-guide/configuration/footer-from
footer-from: footer.md
# see: https://terraform-docs.io/user-guide/configuration/#sections
# see: https://terraform-docs.io/user-guide/configuration/recursive
# recursive:
# enabled: false
# path: modules
# see: https://terraform-docs.io/user-guide/configuration/sections
sections:
show:
- header
@@ -16,7 +24,7 @@ sections:
- modules
- footer
# # see: https://terraform-docs.io/user-guide/configuration/#content
# # see: https://terraform-docs.io/user-guide/configuration/content
# content: |-
# Any arbitrary text can be placed anywhere in the content
#
@@ -38,7 +46,7 @@ sections:
#
# {{ .Inputs }}
# # see: https://terraform-docs.io/user-guide/configuration/#output
# # see: https://terraform-docs.io/user-guide/configuration/output
# output:
# file: README.md
# mode: inject
@@ -53,11 +61,17 @@ sections:
# You can also show something after it!
# <!-- END_TF_DOCS -->
# see: https://terraform-docs.io/user-guide/configuration/#sort
# see: https://terraform-docs.io/user-guide/configuration/sort
sort:
enabled: true
by: required
# # https://terraform-docs.io/user-guide/configuration/output-values/
# output-values:
# enabled: false
# from: ""
# see: https://terraform-docs.io/user-guide/configuration/settings
settings:
indent: 4
escape: false

View File

@@ -71,8 +71,15 @@ func (r *Runtime) PreRunEFunc(cmd *cobra.Command, args []string) error {
return fmt.Errorf("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 {
// read config file and override them with corresponding flags
v := viper.New()
if err := r.readConfig(v, r.config.File, ""); err != nil {
return err
}
// and override them with corresponding flags
if err := r.unmarshalConfig(v, r.config); err != nil {
return err
}
@@ -95,7 +102,7 @@ func (r *Runtime) RunEFunc(cmd *cobra.Command, args []string) error {
// 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 != "" {
if r.config.Recursive.Enabled && r.config.Recursive.Path != "" {
items, err := r.findSubmodules()
if err != nil {
return err
@@ -120,7 +127,7 @@ func (r *Runtime) RunEFunc(cmd *cobra.Command, args []string) error {
return err
}
if r.config.Recursive && cfg.Output.File == "" {
if r.config.Recursive.Enabled && cfg.Output.File == "" {
return fmt.Errorf("value of '--output-file' cannot be empty with '--recursive'")
}
@@ -135,11 +142,9 @@ func (r *Runtime) RunEFunc(cmd *cobra.Command, args []string) error {
// 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 *print.Config, submoduleDir string) error {
v := viper.New()
func (r *Runtime) readConfig(v *viper.Viper, file string, submoduleDir string) error {
if r.isFlagChanged("config") {
v.SetConfigFile(config.File)
v.SetConfigFile(file)
} else {
v.SetConfigName(".terraform-docs")
v.SetConfigType("yml")
@@ -159,7 +164,7 @@ func (r *Runtime) readConfig(config *print.Config, submoduleDir string) error {
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)
return fmt.Errorf("config file %s not found", file)
}
var cerr viper.ConfigFileNotFoundError
@@ -174,6 +179,10 @@ func (r *Runtime) readConfig(config *print.Config, submoduleDir string) error {
}
}
return nil
}
func (r *Runtime) unmarshalConfig(v *viper.Viper, config *print.Config) error {
r.bindFlags(v)
if err := v.Unmarshal(config); err != nil {
@@ -188,7 +197,6 @@ func (r *Runtime) readConfig(config *print.Config, submoduleDir string) error {
config.Formatter = r.formatter
}
// TODO
config.Parse()
return nil
@@ -229,11 +237,27 @@ func (r *Runtime) bindFlags(v *viper.Viper) {
})
}
func (r *Runtime) mergeConfig(v *viper.Viper) (*print.Config, error) {
copy := *r.config
merged := &copy
if v.IsSet("sections.show") || v.IsSet("sections.hide") {
merged.Sections.Show = []string{}
merged.Sections.Hide = []string{}
}
if err := r.unmarshalConfig(v, merged); err != nil {
return nil, err
}
return merged, nil
}
// 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)
dir := filepath.Join(r.rootDir, r.config.Recursive.Path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
return nil, err
@@ -257,9 +281,13 @@ func (r *Runtime) findSubmodules() ([]module, error) {
cfgfile := filepath.Join(path, r.config.File)
if _, err := os.Stat(cfgfile); !os.IsNotExist(err) {
cfg = print.DefaultConfig()
v := viper.New()
if err := r.readConfig(cfg, path); err != nil {
if err = r.readConfig(v, cfgfile, path); err != nil {
return nil, err
}
if cfg, err = r.mergeConfig(v); err != nil {
return nil, err
}
}

View File

@@ -18,19 +18,18 @@ import (
// Config represents all the available config options that can be accessed and
// passed through CLI.
type Config struct {
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"`
File string `mapstructure:"-"`
Formatter string `mapstructure:"formatter"`
Version string `mapstructure:"version"`
HeaderFrom string `mapstructure:"header-from"`
FooterFrom string `mapstructure:"footer-from"`
Recursive recursive `mapstructure:"recursive"`
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
}
@@ -39,6 +38,7 @@ type Config struct {
func NewConfig() *Config {
return &Config{
HeaderFrom: "main.tf",
Recursive: recursive{},
Sections: sections{},
Output: output{},
OutputValues: outputvalues{},
@@ -50,24 +50,42 @@ func NewConfig() *Config {
// DefaultConfig returns new instance of Config with default values set.
func DefaultConfig() *Config {
return &Config{
File: "",
Recursive: false,
RecursivePath: "modules",
Formatter: "",
Version: "",
HeaderFrom: "main.tf",
FooterFrom: "",
Content: "",
Sections: defaultSections(),
Output: defaultOutput(),
OutputValues: defaultOutputValues(),
Sort: defaultSort(),
Settings: defaultSettings(),
File: "",
Formatter: "",
Version: "",
HeaderFrom: "main.tf",
FooterFrom: "",
Recursive: defaultRecursive(),
Content: "",
Sections: defaultSections(),
Output: defaultOutput(),
OutputValues: defaultOutputValues(),
Sort: defaultSort(),
Settings: defaultSettings(),
ModuleRoot: "",
}
}
type recursive struct {
Enabled bool `mapstructure:"enabled"`
Path string `mapstructure:"path"`
}
func defaultRecursive() recursive {
return recursive{
Enabled: false,
Path: "modules",
}
}
func (r *recursive) validate() error {
if r.Enabled && r.Path == "" {
return fmt.Errorf("value of '--recursive-path' can't be empty")
}
return nil
}
const (
sectionAll = "all"
sectionDataSources = "data-sources"
@@ -419,11 +437,6 @@ func (c *Config) Validate() error {
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")
}
// header-from
if c.HeaderFrom == "" {
return fmt.Errorf("value of '--header-from' can't be empty")
@@ -439,6 +452,7 @@ func (c *Config) Validate() error {
}
for _, fn := range [](func() error){
c.Recursive.validate,
c.Sections.validate,
c.Output.validate,
c.OutputValues.validate,

View File

@@ -561,8 +561,8 @@ func TestConfigValidate(t *testing.T) {
},
"RecursivePathEmpty": {
config: func(c *Config) {
c.Recursive = true
c.RecursivePath = ""
c.Recursive.Enabled = true
c.Recursive.Path = ""
},
wantErr: true,
errMsg: "value of '--recursive-path' can't be empty",