mirror of
https://github.com/terraform-docs/terraform-docs.git
synced 2026-03-27 12:58:35 +07:00
Move template package from internal to public
Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>
This commit is contained in:
52
terraform/doc.go
Normal file
52
terraform/doc.go
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// Package terraform is the representation of a Terraform Module.
|
||||
//
|
||||
// It contains:
|
||||
//
|
||||
// • Header: Module header found in shape of multi line '*.tf' comments or an entire file
|
||||
//
|
||||
// • Footer: Module footer found in shape of multi line '*.tf' comments or an entire file
|
||||
//
|
||||
// • Inputs: List of input 'variables' extracted from the Terraform module .tf files
|
||||
//
|
||||
// • ModuleCalls: List of 'modules' extracted from the Terraform module .tf files
|
||||
//
|
||||
// • Outputs: List of 'outputs' extracted from Terraform module .tf files
|
||||
//
|
||||
// • Providers: List of 'providers' extracted from resources used in Terraform module
|
||||
//
|
||||
// • Requirements: List of 'requirements' extracted from the Terraform module .tf files
|
||||
//
|
||||
// • Resources: List of 'resources' extracted from the Terraform module .tf files
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// options := &terraform.Options{
|
||||
// Path: "./examples",
|
||||
// ShowHeader: true,
|
||||
// HeaderFromFile: "main.tf",
|
||||
// ShowFooter: true,
|
||||
// FooterFromFile: "footer.md",
|
||||
// SortBy: &terraform.SortBy{
|
||||
// Name: true,
|
||||
// },
|
||||
// ReadComments: true,
|
||||
// }
|
||||
//
|
||||
// tfmodule, err := terraform.LoadWithOptions(options)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// ...
|
||||
//
|
||||
package terraform
|
||||
112
terraform/input.go
Normal file
112
terraform/input.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
terraformsdk "github.com/terraform-docs/plugin-sdk/terraform"
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
// Input represents a Terraform input.
|
||||
type Input struct {
|
||||
Name string `json:"name" toml:"name" xml:"name" yaml:"name"`
|
||||
Type types.String `json:"type" toml:"type" xml:"type" yaml:"type"`
|
||||
Description types.String `json:"description" toml:"description" xml:"description" yaml:"description"`
|
||||
Default types.Value `json:"default" toml:"default" xml:"default" yaml:"default"`
|
||||
Required bool `json:"required" toml:"required" xml:"required" yaml:"required"`
|
||||
Position Position `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// GetValue returns JSON representation of the 'Default' value, which is an 'interface'.
|
||||
// If 'Default' is a primitive type, the primitive value of 'Default' will be returned
|
||||
// and not the JSON formatted of it.
|
||||
func (i *Input) GetValue() string {
|
||||
var buf bytes.Buffer
|
||||
encoder := json.NewEncoder(&buf)
|
||||
encoder.SetIndent("", " ")
|
||||
encoder.SetEscapeHTML(false)
|
||||
err := encoder.Encode(i.Default)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
value := strings.TrimSpace(buf.String())
|
||||
if value == `null` {
|
||||
if i.Required {
|
||||
return ""
|
||||
}
|
||||
return `null` // explicit 'null' value
|
||||
}
|
||||
return value // everything else
|
||||
}
|
||||
|
||||
// HasDefault indicates if a Terraform variable has a default value set.
|
||||
func (i *Input) HasDefault() bool {
|
||||
return i.Default.HasDefault() || !i.Required
|
||||
}
|
||||
|
||||
func sortInputsByName(x []*Input) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
return x[i].Name < x[j].Name
|
||||
})
|
||||
}
|
||||
|
||||
func sortInputsByRequired(x []*Input) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
if x[i].HasDefault() == x[j].HasDefault() {
|
||||
return x[i].Name < x[j].Name
|
||||
}
|
||||
return !x[i].HasDefault() && x[j].HasDefault()
|
||||
})
|
||||
}
|
||||
|
||||
func sortInputsByPosition(x []*Input) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
if x[i].Position.Filename == x[j].Position.Filename {
|
||||
return x[i].Position.Line < x[j].Position.Line
|
||||
}
|
||||
return x[i].Position.Filename < x[j].Position.Filename
|
||||
})
|
||||
}
|
||||
|
||||
func sortInputsByType(x []*Input) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
if x[i].Type == x[j].Type {
|
||||
return x[i].Name < x[j].Name
|
||||
}
|
||||
return x[i].Type < x[j].Type
|
||||
})
|
||||
}
|
||||
|
||||
type inputs []*Input
|
||||
|
||||
func (ii inputs) convert() []*terraformsdk.Input {
|
||||
list := []*terraformsdk.Input{}
|
||||
for _, i := range ii {
|
||||
list = append(list, &terraformsdk.Input{
|
||||
Name: i.Name,
|
||||
Type: fmt.Sprintf("%v", i.Type.Raw()),
|
||||
Description: fmt.Sprintf("%v", i.Description.Raw()),
|
||||
Default: i.Default.Raw(),
|
||||
Required: i.Required,
|
||||
Position: terraformsdk.Position{
|
||||
Filename: i.Position.Filename,
|
||||
Line: i.Position.Line,
|
||||
},
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
300
terraform/input_test.go
Normal file
300
terraform/input_test.go
Normal file
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
func TestInputValue(t *testing.T) {
|
||||
inputName := "input"
|
||||
inputType := types.String("type")
|
||||
inputDescr := types.String("description")
|
||||
inputPos := Position{Filename: "foo.tf", Line: 13}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input Input
|
||||
expectValue string
|
||||
expectDefault bool
|
||||
expectRequired bool
|
||||
}{
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(nil),
|
||||
Required: true,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "",
|
||||
expectDefault: false,
|
||||
expectRequired: true,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(nil),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "null",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(true),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "true",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(false),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "false",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(""),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "\"\"",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf("foo"),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "\"foo\"",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(42),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "42",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(13.75),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "13.75",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(types.List{"a", "b", "c"}.Underlying()),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "[\n \"a\",\n \"b\",\n \"c\"\n]",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(types.List{}.Underlying()),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "[]",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(types.Map{"a": 1, "b": 2, "c": 3}.Underlying()),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "{\n \"a\": 1,\n \"b\": 2,\n \"c\": 3\n}",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
{
|
||||
name: "input Value and HasDefault",
|
||||
input: Input{
|
||||
Name: inputName,
|
||||
Type: inputType,
|
||||
Description: inputDescr,
|
||||
Default: types.ValueOf(types.Map{}.Underlying()),
|
||||
Required: false,
|
||||
Position: inputPos,
|
||||
},
|
||||
expectValue: "{}",
|
||||
expectDefault: true,
|
||||
expectRequired: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(tt.expectValue, tt.input.GetValue())
|
||||
assert.Equal(tt.expectDefault, tt.input.HasDefault())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputsSorted(t *testing.T) {
|
||||
inputs := sampleInputs()
|
||||
tests := map[string]struct {
|
||||
sortType func([]*Input)
|
||||
expected []string
|
||||
}{
|
||||
"ByName": {
|
||||
sortType: sortInputsByName,
|
||||
expected: []string{"a", "b", "c", "d", "e", "f"},
|
||||
},
|
||||
"ByRequired": {
|
||||
sortType: sortInputsByRequired,
|
||||
expected: []string{"b", "d", "a", "c", "e", "f"},
|
||||
},
|
||||
"ByPosition": {
|
||||
sortType: sortInputsByPosition,
|
||||
expected: []string{"a", "d", "e", "b", "c", "f"},
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tt.sortType(inputs)
|
||||
|
||||
actual := make([]string, len(inputs))
|
||||
|
||||
for k, i := range inputs {
|
||||
actual[k] = i.Name
|
||||
}
|
||||
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sampleInputs() []*Input {
|
||||
return []*Input{
|
||||
{
|
||||
Name: "e",
|
||||
Type: types.String(""),
|
||||
Description: types.String("description of e"),
|
||||
Default: types.ValueOf(true),
|
||||
Required: false,
|
||||
Position: Position{Filename: "foo/variables.tf", Line: 35},
|
||||
},
|
||||
{
|
||||
Name: "a",
|
||||
Type: types.String("string"),
|
||||
Description: types.String(""),
|
||||
Default: types.ValueOf("a"),
|
||||
Required: false,
|
||||
Position: Position{Filename: "foo/variables.tf", Line: 10},
|
||||
},
|
||||
{
|
||||
Name: "d",
|
||||
Type: types.String("string"),
|
||||
Description: types.String("description for d"),
|
||||
Default: types.ValueOf(nil),
|
||||
Required: true,
|
||||
Position: Position{Filename: "foo/variables.tf", Line: 23},
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Type: types.String("number"),
|
||||
Description: types.String("description of b"),
|
||||
Default: types.ValueOf(nil),
|
||||
Required: true,
|
||||
Position: Position{Filename: "foo/variables.tf", Line: 42},
|
||||
},
|
||||
{
|
||||
Name: "c",
|
||||
Type: types.String("list"),
|
||||
Description: types.String("description of c"),
|
||||
Default: types.ValueOf("c"),
|
||||
Required: false,
|
||||
Position: Position{Filename: "foo/variables.tf", Line: 51},
|
||||
},
|
||||
{
|
||||
Name: "f",
|
||||
Type: types.String("string"),
|
||||
Description: types.String("description of f"),
|
||||
Default: types.ValueOf(nil),
|
||||
Required: false,
|
||||
Position: Position{Filename: "foo/variables.tf", Line: 59},
|
||||
},
|
||||
}
|
||||
}
|
||||
540
terraform/load.go
Normal file
540
terraform/load.go
Normal file
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||
|
||||
"github.com/terraform-docs/terraform-config-inspect/tfconfig"
|
||||
"github.com/terraform-docs/terraform-docs/internal/reader"
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
// LoadWithOptions returns new instance of Module with all the inputs and
|
||||
// outputs discovered from provided 'path' containing Terraform config
|
||||
func LoadWithOptions(options *Options) (*Module, error) {
|
||||
tfmodule, err := loadModule(options.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
module, err := loadModuleItems(tfmodule, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sortItems(module, options.SortBy)
|
||||
return module, nil
|
||||
}
|
||||
|
||||
func loadModule(path string) (*tfconfig.Module, error) {
|
||||
module, diag := tfconfig.LoadModule(path)
|
||||
if diag != nil && diag.HasErrors() {
|
||||
return nil, diag
|
||||
}
|
||||
return module, nil
|
||||
}
|
||||
|
||||
func loadModuleItems(tfmodule *tfconfig.Module, options *Options) (*Module, error) {
|
||||
header, err := loadHeader(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
footer, err := loadFooter(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inputs, required, optional := loadInputs(tfmodule, options)
|
||||
modulecalls := loadModulecalls(tfmodule)
|
||||
outputs, err := loadOutputs(tfmodule, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers := loadProviders(tfmodule, options)
|
||||
requirements := loadRequirements(tfmodule)
|
||||
resources := loadResources(tfmodule)
|
||||
|
||||
return &Module{
|
||||
Header: header,
|
||||
Footer: footer,
|
||||
Inputs: inputs,
|
||||
ModuleCalls: modulecalls,
|
||||
Outputs: outputs,
|
||||
Providers: providers,
|
||||
Requirements: requirements,
|
||||
Resources: resources,
|
||||
|
||||
RequiredInputs: required,
|
||||
OptionalInputs: optional,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getFileFormat(filename string) string {
|
||||
if filename == "" {
|
||||
return ""
|
||||
}
|
||||
last := strings.LastIndex(filename, ".")
|
||||
if last == -1 {
|
||||
return ""
|
||||
}
|
||||
return filename[last:]
|
||||
}
|
||||
|
||||
func isFileFormatSupported(filename string, section string) (bool, error) {
|
||||
if section == "" {
|
||||
return false, errors.New("section is missing")
|
||||
}
|
||||
if filename == "" {
|
||||
return false, fmt.Errorf("--%s-from value is missing", section)
|
||||
}
|
||||
switch getFileFormat(filename) {
|
||||
case ".adoc", ".md", ".tf", ".txt":
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("only .adoc, .md, .tf, and .txt formats are supported to read %s from", section)
|
||||
}
|
||||
|
||||
func loadHeader(options *Options) (string, error) {
|
||||
if !options.ShowHeader {
|
||||
return "", nil
|
||||
}
|
||||
return loadSection(options, options.HeaderFromFile, "header")
|
||||
}
|
||||
|
||||
func loadFooter(options *Options) (string, error) {
|
||||
if !options.ShowFooter {
|
||||
return "", nil
|
||||
}
|
||||
return loadSection(options, options.FooterFromFile, "footer")
|
||||
}
|
||||
|
||||
func loadSection(options *Options, file string, section string) (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.
|
||||
|
||||
if section == "" {
|
||||
return "", errors.New("section is missing")
|
||||
}
|
||||
filename := filepath.Join(options.Path, file)
|
||||
if ok, err := isFileFormatSupported(file, section); !ok {
|
||||
return "", err
|
||||
}
|
||||
if info, err := os.Stat(filename); os.IsNotExist(err) || info.IsDir() {
|
||||
if section == "header" && file == "main.tf" {
|
||||
return "", nil // absorb the error to not break workflow for default value of header and missing 'main.tf'
|
||||
}
|
||||
return "", err // user explicitly asked for a file which doesn't exist
|
||||
}
|
||||
if getFileFormat(file) != ".tf" {
|
||||
content, err := ioutil.ReadFile(filepath.Clean(filename))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
lines := reader.Lines{
|
||||
FileName: filename,
|
||||
LineNum: -1,
|
||||
Condition: func(line string) bool {
|
||||
line = strings.TrimSpace(line)
|
||||
return strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*") || strings.HasPrefix(line, "*/")
|
||||
},
|
||||
Parser: func(line string) (string, bool) {
|
||||
tmp := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(tmp, "/*") || strings.HasPrefix(tmp, "*/") {
|
||||
return "", false
|
||||
}
|
||||
if tmp == "*" {
|
||||
return "", true
|
||||
}
|
||||
line = strings.TrimLeft(line, " ")
|
||||
line = strings.TrimRight(line, "\r\n")
|
||||
line = strings.TrimPrefix(line, "* ")
|
||||
return line, true
|
||||
},
|
||||
}
|
||||
sectionText, err := lines.Extract()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Join(sectionText, "\n"), nil
|
||||
}
|
||||
|
||||
func loadInputs(tfmodule *tfconfig.Module, options *Options) ([]*Input, []*Input, []*Input) {
|
||||
var inputs = make([]*Input, 0, len(tfmodule.Variables))
|
||||
var required = make([]*Input, 0, len(tfmodule.Variables))
|
||||
var optional = make([]*Input, 0, len(tfmodule.Variables))
|
||||
|
||||
for _, input := range tfmodule.Variables {
|
||||
// convert CRLF to LF early on (https://github.com/terraform-docs/terraform-docs/issues/305)
|
||||
inputDescription := strings.ReplaceAll(input.Description, "\r\n", "\n")
|
||||
if inputDescription == "" && options.ReadComments {
|
||||
inputDescription = loadComments(input.Pos.Filename, input.Pos.Line)
|
||||
}
|
||||
|
||||
i := &Input{
|
||||
Name: input.Name,
|
||||
Type: types.TypeOf(input.Type, input.Default),
|
||||
Description: types.String(inputDescription),
|
||||
Default: types.ValueOf(input.Default),
|
||||
Required: input.Required,
|
||||
Position: Position{
|
||||
Filename: input.Pos.Filename,
|
||||
Line: input.Pos.Line,
|
||||
},
|
||||
}
|
||||
|
||||
inputs = append(inputs, i)
|
||||
|
||||
if i.HasDefault() {
|
||||
optional = append(optional, i)
|
||||
} else {
|
||||
required = append(required, i)
|
||||
}
|
||||
}
|
||||
|
||||
return inputs, required, optional
|
||||
}
|
||||
|
||||
func formatSource(s, v string) (source, version string) {
|
||||
substr := "?ref="
|
||||
|
||||
if v != "" {
|
||||
return s, v
|
||||
}
|
||||
|
||||
pos := strings.LastIndex(s, substr)
|
||||
if pos == -1 {
|
||||
return s, version
|
||||
}
|
||||
|
||||
adjustedPos := pos + len(substr)
|
||||
if adjustedPos >= len(s) {
|
||||
return s, version
|
||||
}
|
||||
|
||||
source = s[0:pos]
|
||||
version = s[adjustedPos:]
|
||||
|
||||
return source, version
|
||||
}
|
||||
|
||||
func loadModulecalls(tfmodule *tfconfig.Module) []*ModuleCall {
|
||||
var modules = make([]*ModuleCall, 0)
|
||||
var source, version string
|
||||
|
||||
for _, m := range tfmodule.ModuleCalls {
|
||||
source, version = formatSource(m.Source, m.Version)
|
||||
modules = append(modules, &ModuleCall{
|
||||
Name: m.Name,
|
||||
Source: source,
|
||||
Version: version,
|
||||
Position: Position{
|
||||
Filename: m.Pos.Filename,
|
||||
Line: m.Pos.Line,
|
||||
},
|
||||
})
|
||||
}
|
||||
return modules
|
||||
}
|
||||
|
||||
func loadOutputs(tfmodule *tfconfig.Module, options *Options) ([]*Output, error) {
|
||||
outputs := make([]*Output, 0, len(tfmodule.Outputs))
|
||||
values := make(map[string]*output)
|
||||
if options.OutputValues {
|
||||
var err error
|
||||
values, err = loadOutputValues(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, o := range tfmodule.Outputs {
|
||||
description := o.Description
|
||||
if description == "" && options.ReadComments {
|
||||
description = loadComments(o.Pos.Filename, o.Pos.Line)
|
||||
}
|
||||
output := &Output{
|
||||
Name: o.Name,
|
||||
Description: types.String(description),
|
||||
Position: Position{
|
||||
Filename: o.Pos.Filename,
|
||||
Line: o.Pos.Line,
|
||||
},
|
||||
ShowValue: options.OutputValues,
|
||||
}
|
||||
if options.OutputValues {
|
||||
output.Sensitive = values[output.Name].Sensitive
|
||||
if values[output.Name].Sensitive {
|
||||
output.Value = types.ValueOf(`<sensitive>`)
|
||||
} else {
|
||||
output.Value = types.ValueOf(values[output.Name].Value)
|
||||
}
|
||||
}
|
||||
outputs = append(outputs, output)
|
||||
}
|
||||
return outputs, nil
|
||||
}
|
||||
|
||||
func loadOutputValues(options *Options) (map[string]*output, error) {
|
||||
var out []byte
|
||||
var err error
|
||||
if options.OutputValuesPath == "" {
|
||||
cmd := exec.Command("terraform", "output", "-json")
|
||||
cmd.Dir = options.Path
|
||||
if out, err = cmd.Output(); err != nil {
|
||||
return nil, fmt.Errorf("caught error while reading the terraform outputs: %w", err)
|
||||
}
|
||||
} else if out, err = ioutil.ReadFile(options.OutputValuesPath); err != nil {
|
||||
return nil, fmt.Errorf("caught error while reading the terraform outputs file at %s: %w", options.OutputValuesPath, err)
|
||||
}
|
||||
var terraformOutputs map[string]*output
|
||||
err = json.Unmarshal(out, &terraformOutputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return terraformOutputs, err
|
||||
}
|
||||
|
||||
func loadProviders(tfmodule *tfconfig.Module, options *Options) []*Provider {
|
||||
type provider struct {
|
||||
Name string `hcl:"name,label"`
|
||||
Version string `hcl:"version"`
|
||||
Constraints *string `hcl:"constraints"`
|
||||
Hashes []string `hcl:"hashes"`
|
||||
}
|
||||
type lockfile struct {
|
||||
Provider []provider `hcl:"provider,block"`
|
||||
}
|
||||
lock := make(map[string]provider)
|
||||
|
||||
if options.UseLockFile {
|
||||
var lf lockfile
|
||||
|
||||
filename := filepath.Join(options.Path, ".terraform.lock.hcl")
|
||||
if err := hclsimple.DecodeFile(filename, nil, &lf); err == nil {
|
||||
for i := range lf.Provider {
|
||||
segments := strings.Split(lf.Provider[i].Name, "/")
|
||||
name := segments[len(segments)-1]
|
||||
lock[name] = lf.Provider[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resources := []map[string]*tfconfig.Resource{tfmodule.ManagedResources, tfmodule.DataResources}
|
||||
discovered := make(map[string]*Provider)
|
||||
|
||||
for _, resource := range resources {
|
||||
for _, r := range resource {
|
||||
var version = ""
|
||||
if l, ok := lock[r.Provider.Name]; ok {
|
||||
version = l.Version
|
||||
} else if rv, ok := tfmodule.RequiredProviders[r.Provider.Name]; ok && len(rv.VersionConstraints) > 0 {
|
||||
version = strings.Join(rv.VersionConstraints, " ")
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s.%s", r.Provider.Name, r.Provider.Alias)
|
||||
discovered[key] = &Provider{
|
||||
Name: r.Provider.Name,
|
||||
Alias: types.String(r.Provider.Alias),
|
||||
Version: types.String(version),
|
||||
Position: Position{
|
||||
Filename: r.Pos.Filename,
|
||||
Line: r.Pos.Line,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
providers := make([]*Provider, 0, len(discovered))
|
||||
for _, provider := range discovered {
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
func loadRequirements(tfmodule *tfconfig.Module) []*Requirement {
|
||||
var requirements = make([]*Requirement, 0)
|
||||
for _, core := range tfmodule.RequiredCore {
|
||||
requirements = append(requirements, &Requirement{
|
||||
Name: "terraform",
|
||||
Version: types.String(core),
|
||||
})
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(tfmodule.RequiredProviders))
|
||||
for n := range tfmodule.RequiredProviders {
|
||||
names = append(names, n)
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
for _, name := range names {
|
||||
for _, version := range tfmodule.RequiredProviders[name].VersionConstraints {
|
||||
requirements = append(requirements, &Requirement{
|
||||
Name: name,
|
||||
Version: types.String(version),
|
||||
})
|
||||
}
|
||||
}
|
||||
return requirements
|
||||
}
|
||||
|
||||
func loadResources(tfmodule *tfconfig.Module) []*Resource {
|
||||
allResources := []map[string]*tfconfig.Resource{tfmodule.ManagedResources, tfmodule.DataResources}
|
||||
discovered := make(map[string]*Resource)
|
||||
|
||||
for _, resource := range allResources {
|
||||
for _, r := range resource {
|
||||
var version string
|
||||
if rv, ok := tfmodule.RequiredProviders[r.Provider.Name]; ok {
|
||||
version = resourceVersion(rv.VersionConstraints)
|
||||
}
|
||||
|
||||
var source string
|
||||
if len(tfmodule.RequiredProviders[r.Provider.Name].Source) > 0 {
|
||||
source = tfmodule.RequiredProviders[r.Provider.Name].Source
|
||||
} else {
|
||||
source = fmt.Sprintf("%s/%s", "hashicorp", r.Provider.Name)
|
||||
}
|
||||
|
||||
rType := strings.TrimPrefix(r.Type, r.Provider.Name+"_")
|
||||
key := fmt.Sprintf("%s.%s.%s.%s", r.Provider.Name, r.Mode, rType, r.Name)
|
||||
discovered[key] = &Resource{
|
||||
Type: rType,
|
||||
Name: r.Name,
|
||||
Mode: r.Mode.String(),
|
||||
ProviderName: r.Provider.Name,
|
||||
ProviderSource: source,
|
||||
Version: types.String(version),
|
||||
Position: Position{
|
||||
Filename: r.Pos.Filename,
|
||||
Line: r.Pos.Line,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resources := make([]*Resource, 0, len(discovered))
|
||||
for _, resource := range discovered {
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
func resourceVersion(constraints []string) string {
|
||||
if len(constraints) == 0 {
|
||||
return "latest"
|
||||
}
|
||||
versionParts := strings.Split(constraints[len(constraints)-1], " ")
|
||||
switch len(versionParts) {
|
||||
case 1:
|
||||
if _, err := strconv.Atoi(versionParts[0][0:1]); err != nil {
|
||||
if versionParts[0][0:1] == "=" {
|
||||
return versionParts[0][1:]
|
||||
}
|
||||
return "latest"
|
||||
}
|
||||
return versionParts[0]
|
||||
case 2:
|
||||
if versionParts[0] == "=" {
|
||||
return versionParts[1]
|
||||
}
|
||||
}
|
||||
return "latest"
|
||||
}
|
||||
|
||||
func loadComments(filename string, lineNum int) string {
|
||||
lines := reader.Lines{
|
||||
FileName: filename,
|
||||
LineNum: lineNum,
|
||||
Condition: func(line string) bool {
|
||||
return strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//")
|
||||
},
|
||||
Parser: func(line string) (string, bool) {
|
||||
line = strings.TrimSpace(line)
|
||||
line = strings.TrimPrefix(line, "#")
|
||||
line = strings.TrimPrefix(line, "//")
|
||||
line = strings.TrimSpace(line)
|
||||
return line, true
|
||||
},
|
||||
}
|
||||
comment, err := lines.Extract()
|
||||
if err != nil {
|
||||
return "" // absorb the error, we don't need to bubble it up or break the execution
|
||||
}
|
||||
return strings.Join(comment, " ")
|
||||
}
|
||||
|
||||
func sortItems(tfmodule *Module, sortby *SortBy) { //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.
|
||||
|
||||
// inputs
|
||||
switch {
|
||||
case sortby.Type:
|
||||
sortInputsByType(tfmodule.Inputs)
|
||||
sortInputsByType(tfmodule.RequiredInputs)
|
||||
sortInputsByType(tfmodule.OptionalInputs)
|
||||
case sortby.Required:
|
||||
sortInputsByRequired(tfmodule.Inputs)
|
||||
sortInputsByRequired(tfmodule.RequiredInputs)
|
||||
sortInputsByRequired(tfmodule.OptionalInputs)
|
||||
case sortby.Name:
|
||||
sortInputsByName(tfmodule.Inputs)
|
||||
sortInputsByName(tfmodule.RequiredInputs)
|
||||
sortInputsByName(tfmodule.OptionalInputs)
|
||||
default:
|
||||
sortInputsByPosition(tfmodule.Inputs)
|
||||
sortInputsByPosition(tfmodule.RequiredInputs)
|
||||
sortInputsByPosition(tfmodule.OptionalInputs)
|
||||
}
|
||||
|
||||
// outputs
|
||||
if sortby.Name || sortby.Required || sortby.Type {
|
||||
sortOutputsByName(tfmodule.Outputs)
|
||||
} else {
|
||||
sortOutputsByPosition(tfmodule.Outputs)
|
||||
}
|
||||
|
||||
// providers
|
||||
if sortby.Name || sortby.Required || sortby.Type {
|
||||
sortProvidersByName(tfmodule.Providers)
|
||||
} else {
|
||||
sortProvidersByPosition(tfmodule.Providers)
|
||||
}
|
||||
|
||||
// resources (always sorted)
|
||||
sortResourcesByType(tfmodule.Resources)
|
||||
|
||||
// modules
|
||||
switch {
|
||||
case sortby.Name || sortby.Required:
|
||||
sortModulecallsByName(tfmodule.ModuleCalls)
|
||||
case sortby.Type:
|
||||
sortModulecallsBySource(tfmodule.ModuleCalls)
|
||||
default:
|
||||
sortModulecallsByPosition(tfmodule.ModuleCalls)
|
||||
}
|
||||
}
|
||||
971
terraform/load_test.go
Normal file
971
terraform/load_test.go
Normal file
@@ -0,0 +1,971 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadModuleWithOptions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
options, _ := NewOptions().With(&Options{
|
||||
Path: filepath.Join("testdata", "full-example"),
|
||||
})
|
||||
module, err := LoadWithOptions(options)
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(true, module.HasHeader())
|
||||
assert.Equal(false, module.HasFooter())
|
||||
assert.Equal(true, module.HasInputs())
|
||||
assert.Equal(true, module.HasOutputs())
|
||||
assert.Equal(true, module.HasModuleCalls())
|
||||
assert.Equal(true, module.HasProviders())
|
||||
assert.Equal(true, module.HasRequirements())
|
||||
|
||||
options, _ = options.With(&Options{
|
||||
FooterFromFile: "doc.tf",
|
||||
ShowFooter: true,
|
||||
})
|
||||
// options.With and .WithOverwrite will not overwrite true with false
|
||||
options.ShowHeader = false
|
||||
module, err = LoadWithOptions(options)
|
||||
assert.Nil(err)
|
||||
assert.Equal(true, module.HasFooter())
|
||||
assert.Equal(false, module.HasHeader())
|
||||
}
|
||||
|
||||
func TestLoadModule(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "load module from path",
|
||||
path: "full-example",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "load module from path",
|
||||
path: "non-exist",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
_, err := loadModule(filepath.Join("testdata", tt.path))
|
||||
if tt.wantErr {
|
||||
assert.NotNil(err)
|
||||
} else {
|
||||
assert.Nil(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFileFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "get file format",
|
||||
filename: "main.tf",
|
||||
expected: ".tf",
|
||||
},
|
||||
{
|
||||
name: "get file format",
|
||||
filename: "main.file.tf",
|
||||
expected: ".tf",
|
||||
},
|
||||
{
|
||||
name: "get file format",
|
||||
filename: "main_file.tf",
|
||||
expected: ".tf",
|
||||
},
|
||||
{
|
||||
name: "get file format",
|
||||
filename: "main.file_tf",
|
||||
expected: ".file_tf",
|
||||
},
|
||||
{
|
||||
name: "get file format",
|
||||
filename: "main_file_tf",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "get file format",
|
||||
filename: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual := getFileFormat(tt.filename)
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFileFormatSupported(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
expected bool
|
||||
wantErr bool
|
||||
errText string
|
||||
section string
|
||||
}{
|
||||
{
|
||||
name: "is file format supported",
|
||||
filename: "main.adoc",
|
||||
expected: true,
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "is file format supported",
|
||||
filename: "main.md",
|
||||
expected: true,
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "is file format supported",
|
||||
filename: "main.tf",
|
||||
expected: true,
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "is file format supported",
|
||||
filename: "main.txt",
|
||||
expected: true,
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "is file format supported",
|
||||
filename: "main.doc",
|
||||
expected: false,
|
||||
wantErr: true,
|
||||
errText: "only .adoc, .md, .tf, and .txt formats are supported to read header from",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "is file format supported",
|
||||
filename: "",
|
||||
expected: false,
|
||||
wantErr: true,
|
||||
errText: "--header-from value is missing",
|
||||
section: "header",
|
||||
}, {
|
||||
name: "err message changes for footer",
|
||||
filename: "main.doc",
|
||||
expected: false,
|
||||
wantErr: true,
|
||||
errText: "only .adoc, .md, .tf, and .txt formats are supported to read footer from",
|
||||
section: "footer",
|
||||
},
|
||||
{
|
||||
name: "err message changes for footer",
|
||||
filename: "",
|
||||
expected: false,
|
||||
wantErr: true,
|
||||
errText: "--footer-from value is missing",
|
||||
section: "footer",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual, err := isFileFormatSupported(tt.filename, tt.section)
|
||||
if tt.wantErr {
|
||||
assert.NotNil(err)
|
||||
assert.Equal(tt.errText, err.Error())
|
||||
} else {
|
||||
assert.Nil(err)
|
||||
assert.Equal(tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
testData string
|
||||
showHeader bool
|
||||
expectedData func() (string, error)
|
||||
}{
|
||||
{
|
||||
name: "loadHeader should return a string from file",
|
||||
testData: "full-example",
|
||||
showHeader: true,
|
||||
expectedData: func() (string, error) {
|
||||
path := filepath.Join("testdata", "expected", "full-example-mainTf-Header.golden")
|
||||
data, err := ioutil.ReadFile(path)
|
||||
return string(data), err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "loadHeader should return an empty string if not shown",
|
||||
testData: "",
|
||||
showHeader: false,
|
||||
expectedData: func() (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options, err := NewOptions().With(&Options{
|
||||
Path: filepath.Join("testdata", tt.testData),
|
||||
ShowHeader: tt.showHeader,
|
||||
})
|
||||
assert.Nil(err)
|
||||
expected, err := tt.expectedData()
|
||||
assert.Nil(err)
|
||||
header, err := loadHeader(options)
|
||||
assert.Nil(err)
|
||||
assert.Equal(expected, header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFooter(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
testData string
|
||||
footerFile string
|
||||
showFooter bool
|
||||
expectedData func() (string, error)
|
||||
}{
|
||||
{
|
||||
name: "loadFooter should return a string from file",
|
||||
testData: "full-example",
|
||||
footerFile: "main.tf",
|
||||
showFooter: true,
|
||||
expectedData: func() (string, error) {
|
||||
path := filepath.Join("testdata", "expected", "full-example-mainTf-Header.golden")
|
||||
data, err := ioutil.ReadFile(path)
|
||||
return string(data), err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "loadHeader should return an empty string if not shown",
|
||||
testData: "",
|
||||
footerFile: "",
|
||||
showFooter: false,
|
||||
expectedData: func() (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options, err := NewOptions().With(&Options{
|
||||
Path: filepath.Join("testdata", tt.testData),
|
||||
FooterFromFile: tt.footerFile,
|
||||
ShowFooter: tt.showFooter,
|
||||
})
|
||||
assert.Nil(err)
|
||||
expected, err := tt.expectedData()
|
||||
assert.Nil(err)
|
||||
header, err := loadFooter(options)
|
||||
assert.Nil(err)
|
||||
assert.Equal(expected, header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSections(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
file string
|
||||
expected string
|
||||
wantErr bool
|
||||
errText string
|
||||
section string
|
||||
}{
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "full-example",
|
||||
file: "main.tf",
|
||||
expected: "Example of 'foo_bar' module in `foo_bar.tf`.\n\n- list item 1\n- list item 2\n\nEven inline **formatting** in _here_ is possible.\nand some [link](https://domain.com/)",
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "full-example",
|
||||
file: "doc.tf",
|
||||
expected: "Custom Header:\n\nExample of 'foo_bar' module in `foo_bar.tf`.\n\n- list item 1\n- list item 2",
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "full-example",
|
||||
file: "doc.md",
|
||||
expected: "# Custom Header\n\nExample of 'foo_bar' module in `foo_bar.tf`.\n\n- list item 1\n- list item 2\n",
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "full-example",
|
||||
file: "doc.adoc",
|
||||
expected: "= Custom Header\n\nExample of 'foo_bar' module in `foo_bar.tf`.\n\n- list item 1\n- list item 2\n",
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "full-example",
|
||||
file: "doc.txt",
|
||||
expected: "# Custom Header\n\nExample of 'foo_bar' module in `foo_bar.tf`.\n\n- list item 1\n- list item 2\n",
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "no-inputs",
|
||||
file: "main.tf",
|
||||
expected: "",
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "full-example",
|
||||
file: "non-existent.tf",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errText: "stat testdata/full-example/non-existent.tf: no such file or directory",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "no error if header file is missing and is default 'main.tf'",
|
||||
path: "inputs-lf",
|
||||
file: "main.tf",
|
||||
expected: "",
|
||||
wantErr: false,
|
||||
errText: "",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "error if footer file is missing even if 'main.tf'",
|
||||
path: "inputs-lf",
|
||||
file: "main.tf",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errText: "stat testdata/inputs-lf/main.tf: no such file or directory",
|
||||
section: "footer",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "full-example",
|
||||
file: "wrong-formate.docx",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errText: "only .adoc, .md, .tf, and .txt formats are supported to read footer from",
|
||||
section: "footer",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "full-example",
|
||||
file: "",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errText: "--header-from value is missing",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "load module header from path",
|
||||
path: "empty-header",
|
||||
file: "",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errText: "--header-from value is missing",
|
||||
section: "header",
|
||||
},
|
||||
{
|
||||
name: "load module footer from path",
|
||||
path: "non-exist",
|
||||
file: "",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errText: "--footer-from value is missing",
|
||||
section: "footer",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options := &Options{Path: filepath.Join("testdata", tt.path)}
|
||||
actual, err := loadSection(options, tt.file, tt.section)
|
||||
if tt.wantErr {
|
||||
assert.NotNil(err)
|
||||
assert.Equal(tt.errText, err.Error())
|
||||
} else {
|
||||
assert.Nil(err)
|
||||
assert.Equal(tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadInputs(t *testing.T) {
|
||||
type expected struct {
|
||||
inputs int
|
||||
requireds int
|
||||
optionals int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "load module inputs from path",
|
||||
path: "full-example",
|
||||
expected: expected{
|
||||
inputs: 7,
|
||||
requireds: 2,
|
||||
optionals: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "load module inputs from path",
|
||||
path: "no-required-inputs",
|
||||
expected: expected{
|
||||
inputs: 6,
|
||||
requireds: 0,
|
||||
optionals: 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "load module inputs from path",
|
||||
path: "no-optional-inputs",
|
||||
expected: expected{
|
||||
inputs: 6,
|
||||
requireds: 6,
|
||||
optionals: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "load module inputs from path",
|
||||
path: "no-inputs",
|
||||
expected: expected{
|
||||
inputs: 0,
|
||||
requireds: 0,
|
||||
optionals: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
module, _ := loadModule(filepath.Join("testdata", tt.path))
|
||||
options := NewOptions()
|
||||
inputs, requireds, optionals := loadInputs(module, options)
|
||||
|
||||
assert.Equal(tt.expected.inputs, len(inputs))
|
||||
assert.Equal(tt.expected.requireds, len(requireds))
|
||||
assert.Equal(tt.expected.optionals, len(optionals))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadModulecalls(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "load modulecalls from path",
|
||||
path: "full-example",
|
||||
expected: 2,
|
||||
},
|
||||
{
|
||||
name: "load modulecalls from path",
|
||||
path: "no-modulecalls",
|
||||
expected: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
module, _ := loadModule(filepath.Join("testdata", tt.path))
|
||||
modulecalls := loadModulecalls(module)
|
||||
|
||||
assert.Equal(tt.expected, len(modulecalls))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadInputsLineEnding(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "load module inputs from file with lf line ending",
|
||||
path: "inputs-lf",
|
||||
expected: "The quick brown fox jumps\nover the lazy dog\n",
|
||||
},
|
||||
{
|
||||
name: "load module inputs from file with crlf line ending",
|
||||
path: "inputs-crlf",
|
||||
expected: "The quick brown fox jumps\nover the lazy dog\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
module, _ := loadModule(filepath.Join("testdata", tt.path))
|
||||
options := NewOptions()
|
||||
inputs, _, _ := loadInputs(module, options)
|
||||
|
||||
assert.Equal(1, len(inputs))
|
||||
assert.Equal(tt.expected, string(inputs[0].Description))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOutputs(t *testing.T) {
|
||||
type expected struct {
|
||||
outputs int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "load module outputs from path",
|
||||
path: "full-example",
|
||||
expected: expected{
|
||||
outputs: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "load module outputs from path",
|
||||
path: "no-outputs",
|
||||
expected: expected{
|
||||
outputs: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options := NewOptions()
|
||||
module, _ := loadModule(filepath.Join("testdata", tt.path))
|
||||
outputs, err := loadOutputs(module, options)
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(tt.expected.outputs, len(outputs))
|
||||
|
||||
for _, v := range outputs {
|
||||
assert.Equal(false, v.ShowValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOutputsValues(t *testing.T) {
|
||||
type expected struct {
|
||||
outputs int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
outputPath string
|
||||
expected expected
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "load module outputs with values from path",
|
||||
path: "full-example",
|
||||
outputPath: "output-values.json",
|
||||
expected: expected{
|
||||
outputs: 3,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "load module outputs with values from path",
|
||||
path: "full-example",
|
||||
outputPath: "no-file.json",
|
||||
expected: expected{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "load module outputs with values from path",
|
||||
path: "no-outputs",
|
||||
outputPath: "no-file.json",
|
||||
expected: expected{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options, _ := NewOptions().With(&Options{
|
||||
OutputValues: true,
|
||||
OutputValuesPath: filepath.Join("testdata", tt.path, tt.outputPath),
|
||||
})
|
||||
module, _ := loadModule(filepath.Join("testdata", tt.path))
|
||||
outputs, err := loadOutputs(module, options)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.NotNil(err)
|
||||
} else {
|
||||
assert.Nil(err)
|
||||
assert.Equal(tt.expected.outputs, len(outputs))
|
||||
|
||||
for _, v := range outputs {
|
||||
assert.Equal(true, v.ShowValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadProviders(t *testing.T) {
|
||||
type expected struct {
|
||||
providers []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
lockfile bool
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "load module providers from path",
|
||||
path: "full-example",
|
||||
lockfile: false,
|
||||
expected: expected{
|
||||
providers: []string{"aws->= 2.15.0", "null-", "tls-"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "load module providers from path",
|
||||
path: "with-lock-file",
|
||||
lockfile: true,
|
||||
expected: expected{
|
||||
providers: []string{"aws-3.42.0", "null-3.1.0", "tls-3.1.0"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "load module providers from path",
|
||||
path: "with-lock-file",
|
||||
lockfile: false,
|
||||
expected: expected{
|
||||
providers: []string{"aws->= 2.15.0", "null-", "tls-"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "load module providers from path",
|
||||
path: "no-providers",
|
||||
expected: expected{
|
||||
providers: []string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options, _ := NewOptions().With(&Options{
|
||||
Path: filepath.Join("testdata", tt.path),
|
||||
})
|
||||
options.UseLockFile = tt.lockfile
|
||||
module, _ := loadModule(filepath.Join("testdata", tt.path))
|
||||
providers := loadProviders(module, options)
|
||||
|
||||
actual := []string{}
|
||||
|
||||
for _, p := range providers {
|
||||
actual = append(actual, p.FullName()+"-"+string(p.Version))
|
||||
}
|
||||
sort.Strings(actual)
|
||||
|
||||
assert.Equal(tt.expected.providers, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadComments(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
fileName string
|
||||
lineNumber int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "load resource comment from file",
|
||||
path: "full-example",
|
||||
fileName: "variables.tf",
|
||||
lineNumber: 2,
|
||||
expected: "D description",
|
||||
},
|
||||
{
|
||||
name: "load resource comment from file",
|
||||
path: "full-example",
|
||||
fileName: "variables.tf",
|
||||
lineNumber: 16,
|
||||
expected: "A Description in multiple lines",
|
||||
},
|
||||
{
|
||||
name: "load resource comment from file with wrong line number",
|
||||
path: "full-example",
|
||||
fileName: "variables.tf",
|
||||
lineNumber: 100,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "load resource comment from non existing file",
|
||||
path: "full-example",
|
||||
fileName: "non-exist.tf",
|
||||
lineNumber: 5,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "load resource comment from non existing file",
|
||||
path: "non-exist",
|
||||
fileName: "variables.tf",
|
||||
lineNumber: 5,
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual := loadComments(filepath.Join("testdata", tt.path, tt.fileName), tt.lineNumber)
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadComments(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
fileName string
|
||||
readComments bool
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Validate description when 'ReadComments' is false",
|
||||
path: "read-comments",
|
||||
fileName: "variables.tf",
|
||||
readComments: false,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Validate description when 'ReadComments' is true",
|
||||
path: "read-comments",
|
||||
fileName: "variables.tf",
|
||||
readComments: true,
|
||||
expected: "B description",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options := NewOptions()
|
||||
options.ReadComments = tt.readComments
|
||||
module, err := loadModule(filepath.Join("testdata", tt.path))
|
||||
|
||||
assert.Nil(err)
|
||||
|
||||
inputs, _, _ := loadInputs(module, options)
|
||||
assert.Equal(1, len(inputs))
|
||||
assert.Equal(tt.expected, string(inputs[0].Description))
|
||||
|
||||
outputs, _ := loadOutputs(module, options)
|
||||
assert.Equal(1, len(outputs))
|
||||
assert.Equal(tt.expected, string(outputs[0].Description))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortItems(t *testing.T) {
|
||||
type expected struct {
|
||||
inputs []string
|
||||
required []string
|
||||
optional []string
|
||||
outputs []string
|
||||
providers []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
sort *SortBy
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "sort module items",
|
||||
path: "full-example",
|
||||
sort: &SortBy{Name: false, Required: false, Type: false},
|
||||
expected: expected{
|
||||
inputs: []string{"D", "B", "E", "A", "C", "F", "G"},
|
||||
required: []string{"A", "F"},
|
||||
optional: []string{"D", "B", "E", "C", "G"},
|
||||
outputs: []string{"C", "A", "B"},
|
||||
providers: []string{"tls", "aws", "null"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort module items",
|
||||
path: "full-example",
|
||||
sort: &SortBy{Name: true, Required: false, Type: false},
|
||||
expected: expected{
|
||||
inputs: []string{"A", "B", "C", "D", "E", "F", "G"},
|
||||
required: []string{"A", "F"},
|
||||
optional: []string{"B", "C", "D", "E", "G"},
|
||||
outputs: []string{"A", "B", "C"},
|
||||
providers: []string{"aws", "null", "tls"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort module items",
|
||||
path: "full-example",
|
||||
sort: &SortBy{Name: false, Required: true, Type: false},
|
||||
expected: expected{
|
||||
inputs: []string{"A", "F", "B", "C", "D", "E", "G"},
|
||||
required: []string{"A", "F"},
|
||||
optional: []string{"B", "C", "D", "E", "G"},
|
||||
outputs: []string{"A", "B", "C"},
|
||||
providers: []string{"aws", "null", "tls"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort module items",
|
||||
path: "full-example",
|
||||
sort: &SortBy{Name: false, Required: false, Type: true},
|
||||
expected: expected{
|
||||
inputs: []string{"A", "F", "G", "B", "C", "D", "E"},
|
||||
required: []string{"A", "F"},
|
||||
optional: []string{"G", "B", "C", "D", "E"},
|
||||
outputs: []string{"A", "B", "C"},
|
||||
providers: []string{"aws", "null", "tls"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort module items",
|
||||
path: "full-example",
|
||||
sort: &SortBy{Name: true, Required: true, Type: false},
|
||||
expected: expected{
|
||||
inputs: []string{"A", "F", "B", "C", "D", "E", "G"},
|
||||
required: []string{"A", "F"},
|
||||
optional: []string{"B", "C", "D", "E", "G"},
|
||||
outputs: []string{"A", "B", "C"},
|
||||
providers: []string{"aws", "null", "tls"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort module items",
|
||||
path: "full-example",
|
||||
sort: &SortBy{Name: true, Required: false, Type: true},
|
||||
expected: expected{
|
||||
inputs: []string{"A", "F", "G", "B", "C", "D", "E"},
|
||||
required: []string{"A", "F"},
|
||||
optional: []string{"G", "B", "C", "D", "E"},
|
||||
outputs: []string{"A", "B", "C"},
|
||||
providers: []string{"aws", "null", "tls"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort module items",
|
||||
path: "full-example",
|
||||
sort: &SortBy{Name: false, Required: true, Type: true},
|
||||
expected: expected{
|
||||
inputs: []string{"A", "F", "G", "B", "C", "D", "E"},
|
||||
required: []string{"A", "F"},
|
||||
optional: []string{"G", "B", "C", "D", "E"},
|
||||
outputs: []string{"A", "B", "C"},
|
||||
providers: []string{"aws", "null", "tls"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort module items",
|
||||
path: "full-example",
|
||||
sort: &SortBy{Name: true, Required: true, Type: true},
|
||||
expected: expected{
|
||||
inputs: []string{"A", "F", "G", "B", "C", "D", "E"},
|
||||
required: []string{"A", "F"},
|
||||
optional: []string{"G", "B", "C", "D", "E"},
|
||||
outputs: []string{"A", "B", "C"},
|
||||
providers: []string{"aws", "null", "tls"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
path := filepath.Join("testdata", tt.path)
|
||||
options, _ := NewOptions().With(&Options{
|
||||
Path: path,
|
||||
SortBy: tt.sort,
|
||||
})
|
||||
tfmodule, _ := loadModule(path)
|
||||
module, err := loadModuleItems(tfmodule, options)
|
||||
|
||||
assert.Nil(err)
|
||||
sortItems(module, tt.sort)
|
||||
|
||||
for i, v := range module.Inputs {
|
||||
assert.Equal(tt.expected.inputs[i], v.Name)
|
||||
}
|
||||
for i, v := range module.RequiredInputs {
|
||||
assert.Equal(tt.expected.required[i], v.Name)
|
||||
}
|
||||
for i, v := range module.OptionalInputs {
|
||||
assert.Equal(tt.expected.optional[i], v.Name)
|
||||
}
|
||||
for i, v := range module.Outputs {
|
||||
assert.Equal(tt.expected.outputs[i], v.Name)
|
||||
}
|
||||
for i, v := range module.Providers {
|
||||
assert.Equal(tt.expected.providers[i], v.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
90
terraform/module.go
Normal file
90
terraform/module.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
terraformsdk "github.com/terraform-docs/plugin-sdk/terraform"
|
||||
)
|
||||
|
||||
// Module represents a Terraform module. It consists of
|
||||
type Module struct {
|
||||
XMLName xml.Name `json:"-" toml:"-" xml:"module" yaml:"-"`
|
||||
|
||||
Header string `json:"header" toml:"header" xml:"header" yaml:"header"`
|
||||
Footer string `json:"footer" toml:"footer" xml:"footer" yaml:"footer"`
|
||||
Inputs []*Input `json:"inputs" toml:"inputs" xml:"inputs>input" yaml:"inputs"`
|
||||
ModuleCalls []*ModuleCall `json:"modules" toml:"modules" xml:"modules>module" yaml:"modules"`
|
||||
Outputs []*Output `json:"outputs" toml:"outputs" xml:"outputs>output" yaml:"outputs"`
|
||||
Providers []*Provider `json:"providers" toml:"providers" xml:"providers>provider" yaml:"providers"`
|
||||
Requirements []*Requirement `json:"requirements" toml:"requirements" xml:"requirements>requirement" yaml:"requirements"`
|
||||
Resources []*Resource `json:"resources" toml:"resources" xml:"resources>resource" yaml:"resources"`
|
||||
|
||||
RequiredInputs []*Input `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
OptionalInputs []*Input `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// HasHeader indicates if the module has header.
|
||||
func (m *Module) HasHeader() bool {
|
||||
return len(m.Header) > 0
|
||||
}
|
||||
|
||||
// HasFooter indicates if the module has footer.
|
||||
func (m *Module) HasFooter() bool {
|
||||
return len(m.Footer) > 0
|
||||
}
|
||||
|
||||
// HasInputs indicates if the module has inputs.
|
||||
func (m *Module) HasInputs() bool {
|
||||
return len(m.Inputs) > 0
|
||||
}
|
||||
|
||||
// HasModuleCalls indicates if the module has modulecalls.
|
||||
func (m *Module) HasModuleCalls() bool {
|
||||
return len(m.ModuleCalls) > 0
|
||||
}
|
||||
|
||||
// HasOutputs indicates if the module has outputs.
|
||||
func (m *Module) HasOutputs() bool {
|
||||
return len(m.Outputs) > 0
|
||||
}
|
||||
|
||||
// HasProviders indicates if the module has providers.
|
||||
func (m *Module) HasProviders() bool {
|
||||
return len(m.Providers) > 0
|
||||
}
|
||||
|
||||
// HasRequirements indicates if the module has requirements.
|
||||
func (m *Module) HasRequirements() bool {
|
||||
return len(m.Requirements) > 0
|
||||
}
|
||||
|
||||
// HasResources indicates if the module has resources.
|
||||
func (m *Module) HasResources() bool {
|
||||
return len(m.Resources) > 0
|
||||
}
|
||||
|
||||
// Convert internal Module to its equivalent in plugin-sdk
|
||||
func (m *Module) Convert() terraformsdk.Module {
|
||||
return terraformsdk.NewModule(
|
||||
terraformsdk.WithHeader(m.Header),
|
||||
terraformsdk.WithFooter(m.Footer),
|
||||
terraformsdk.WithInputs(inputs(m.Inputs).convert()),
|
||||
terraformsdk.WithModuleCalls(modulecalls(m.ModuleCalls).convert()),
|
||||
terraformsdk.WithOutputs(outputs(m.Outputs).convert()),
|
||||
terraformsdk.WithProviders(providers(m.Providers).convert()),
|
||||
terraformsdk.WithRequirements(requirements(m.Requirements).convert()),
|
||||
terraformsdk.WithResources(resources(m.Resources).convert()),
|
||||
terraformsdk.WithRequiredInputs(inputs(m.RequiredInputs).convert()),
|
||||
terraformsdk.WithOptionalInputs(inputs(m.OptionalInputs).convert()),
|
||||
)
|
||||
}
|
||||
73
terraform/modulecall.go
Normal file
73
terraform/modulecall.go
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
terraformsdk "github.com/terraform-docs/plugin-sdk/terraform"
|
||||
)
|
||||
|
||||
// ModuleCall represents a submodule called by Terraform module.
|
||||
type ModuleCall struct {
|
||||
Name string `json:"name" toml:"name" xml:"name" yaml:"name"`
|
||||
Source string `json:"source" toml:"source" xml:"source" yaml:"source"`
|
||||
Version string `json:"version" toml:"version" xml:"version" yaml:"version"`
|
||||
Position Position `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// FullName returns full name of the modulecall, with version if available
|
||||
func (mc *ModuleCall) FullName() string {
|
||||
if mc.Version != "" {
|
||||
return fmt.Sprintf("%s,%s", mc.Source, mc.Version)
|
||||
}
|
||||
return mc.Source
|
||||
}
|
||||
|
||||
func sortModulecallsByName(x []*ModuleCall) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
return x[i].Name < x[j].Name
|
||||
})
|
||||
}
|
||||
|
||||
func sortModulecallsBySource(x []*ModuleCall) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
if x[i].Source == x[j].Source {
|
||||
return x[i].Name < x[j].Name
|
||||
}
|
||||
return x[i].Source < x[j].Source
|
||||
})
|
||||
}
|
||||
|
||||
func sortModulecallsByPosition(x []*ModuleCall) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
return x[i].Position.Filename < x[j].Position.Filename || x[i].Position.Line < x[j].Position.Line
|
||||
})
|
||||
}
|
||||
|
||||
type modulecalls []*ModuleCall
|
||||
|
||||
func (mm modulecalls) convert() []*terraformsdk.ModuleCall {
|
||||
list := []*terraformsdk.ModuleCall{}
|
||||
for _, m := range mm {
|
||||
list = append(list, &terraformsdk.ModuleCall{
|
||||
Name: m.Name,
|
||||
Source: m.Source,
|
||||
Version: m.Version,
|
||||
Position: terraformsdk.Position{
|
||||
Filename: m.Position.Filename,
|
||||
Line: m.Position.Line,
|
||||
},
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
123
terraform/modulecall_test.go
Normal file
123
terraform/modulecall_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestModulecallName(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
module ModuleCall
|
||||
expected string
|
||||
}{
|
||||
"WithoutVersion": {
|
||||
module: ModuleCall{
|
||||
Name: "provider",
|
||||
Source: "bar",
|
||||
},
|
||||
expected: "bar",
|
||||
},
|
||||
"WithVersion": {
|
||||
module: ModuleCall{
|
||||
Name: "provider",
|
||||
Source: "bar",
|
||||
Version: "1.2.3",
|
||||
},
|
||||
expected: "bar,1.2.3",
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.Equal(tt.expected, tt.module.FullName())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestModulecallSort(t *testing.T) {
|
||||
modules := sampleModulecalls()
|
||||
tests := map[string]struct {
|
||||
sortType func([]*ModuleCall)
|
||||
expected []string
|
||||
}{
|
||||
"ByName": {
|
||||
sortType: sortModulecallsByName,
|
||||
expected: []string{"a", "b", "c", "d", "e", "f"},
|
||||
},
|
||||
"BySource": {
|
||||
sortType: sortModulecallsBySource,
|
||||
expected: []string{"f", "d", "c", "e", "a", "b"},
|
||||
},
|
||||
"ByPosition": {
|
||||
sortType: sortModulecallsByPosition,
|
||||
expected: []string{"b", "c", "a", "e", "d", "f"},
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tt.sortType(modules)
|
||||
|
||||
actual := make([]string, len(modules))
|
||||
|
||||
for k, i := range modules {
|
||||
actual[k] = i.Name
|
||||
}
|
||||
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sampleModulecalls() []*ModuleCall {
|
||||
return []*ModuleCall{
|
||||
{
|
||||
Name: "a",
|
||||
Source: "z",
|
||||
Version: "1.2.3",
|
||||
Position: Position{Filename: "foo/main.tf", Line: 35},
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Source: "z",
|
||||
Version: "1.2.3",
|
||||
Position: Position{Filename: "foo/main.tf", Line: 10},
|
||||
},
|
||||
{
|
||||
Name: "c",
|
||||
Source: "m",
|
||||
Version: "1.2.3",
|
||||
Position: Position{Filename: "foo/main.tf", Line: 23},
|
||||
},
|
||||
{
|
||||
Name: "e",
|
||||
Source: "x",
|
||||
Version: "1.2.3",
|
||||
Position: Position{Filename: "foo/main.tf", Line: 42},
|
||||
},
|
||||
{
|
||||
Name: "d",
|
||||
Source: "l",
|
||||
Version: "1.2.3",
|
||||
Position: Position{Filename: "foo/main.tf", Line: 51},
|
||||
},
|
||||
{
|
||||
Name: "f",
|
||||
Source: "a",
|
||||
Version: "1.2.3",
|
||||
Position: Position{Filename: "foo/main.tf", Line: 59},
|
||||
},
|
||||
}
|
||||
}
|
||||
78
terraform/options.go
Normal file
78
terraform/options.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
// SortBy contains different sort criteria corresponding
|
||||
// to available flags (e.g. name, required, etc)
|
||||
type SortBy struct {
|
||||
Name bool
|
||||
Required bool
|
||||
Type bool
|
||||
}
|
||||
|
||||
// Options contains required options to load a Module from path
|
||||
type Options struct {
|
||||
Path string
|
||||
ShowHeader bool
|
||||
HeaderFromFile string
|
||||
ShowFooter bool
|
||||
FooterFromFile string
|
||||
UseLockFile bool
|
||||
SortBy *SortBy
|
||||
OutputValues bool
|
||||
OutputValuesPath string
|
||||
ReadComments bool
|
||||
}
|
||||
|
||||
// NewOptions returns new instance of Options
|
||||
func NewOptions() *Options {
|
||||
return &Options{
|
||||
Path: "",
|
||||
ShowHeader: true,
|
||||
HeaderFromFile: "main.tf",
|
||||
ShowFooter: false,
|
||||
FooterFromFile: "",
|
||||
UseLockFile: true,
|
||||
SortBy: &SortBy{Name: false, Required: false, Type: false},
|
||||
OutputValues: false,
|
||||
OutputValuesPath: "",
|
||||
ReadComments: true,
|
||||
}
|
||||
}
|
||||
|
||||
// With override options with existing Options
|
||||
func (o *Options) With(override *Options) (*Options, error) {
|
||||
if override == nil {
|
||||
return nil, errors.New("cannot use nil as override value")
|
||||
}
|
||||
if err := mergo.Merge(o, *override); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// WithOverwrite override options with existing Options and overwrites non-empty
|
||||
// items in destination
|
||||
func (o *Options) WithOverwrite(override *Options) (*Options, error) {
|
||||
if override == nil {
|
||||
return nil, errors.New("cannot use nil as override value")
|
||||
}
|
||||
if err := mergo.MergeWithOverwrite(o, *override); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
117
terraform/options_test.go
Normal file
117
terraform/options_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptionsWith(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
options := NewOptions()
|
||||
|
||||
assert.Equal(options.Path, "")
|
||||
assert.Equal(options.OutputValues, false)
|
||||
assert.Equal(options.OutputValuesPath, "")
|
||||
|
||||
_, err1 := options.With(&Options{
|
||||
Path: "/path/to/foo",
|
||||
})
|
||||
assert.Nil(err1)
|
||||
|
||||
assert.Equal(options.Path, "/path/to/foo")
|
||||
assert.Equal(options.OutputValues, false)
|
||||
assert.Equal(options.OutputValuesPath, "")
|
||||
|
||||
_, err2 := options.With(&Options{
|
||||
OutputValues: true,
|
||||
OutputValuesPath: "/path/to/output/values",
|
||||
})
|
||||
assert.Nil(err2)
|
||||
|
||||
assert.Equal(options.Path, "/path/to/foo")
|
||||
assert.Equal(options.OutputValues, true)
|
||||
assert.Equal(options.OutputValuesPath, "/path/to/output/values")
|
||||
|
||||
_, err3 := options.With(&Options{
|
||||
Path: "",
|
||||
OutputValues: false,
|
||||
})
|
||||
assert.Nil(err3)
|
||||
|
||||
assert.NotEqual(options.Path, "")
|
||||
assert.NotEqual(options.OutputValues, false)
|
||||
}
|
||||
|
||||
func TestOptionsWithNil(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options := NewOptions()
|
||||
|
||||
_, err := options.With(nil)
|
||||
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
func TestOptionsWithOverwrite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
options := NewOptions()
|
||||
|
||||
assert.Equal(options.Path, "")
|
||||
assert.Equal(options.HeaderFromFile, "main.tf")
|
||||
assert.Equal(options.OutputValues, false)
|
||||
assert.Equal(options.OutputValuesPath, "")
|
||||
|
||||
_, err1 := options.With(&Options{
|
||||
Path: "/path/to/foo",
|
||||
})
|
||||
assert.Nil(err1)
|
||||
|
||||
assert.Equal(options.Path, "/path/to/foo")
|
||||
assert.Equal(options.HeaderFromFile, "main.tf")
|
||||
assert.Equal(options.OutputValues, false)
|
||||
assert.Equal(options.OutputValuesPath, "")
|
||||
|
||||
_, err2 := options.WithOverwrite(&Options{
|
||||
HeaderFromFile: "doc.tf",
|
||||
OutputValues: true,
|
||||
OutputValuesPath: "/path/to/output/values",
|
||||
})
|
||||
assert.Nil(err2)
|
||||
|
||||
assert.Equal(options.Path, "/path/to/foo")
|
||||
assert.Equal(options.HeaderFromFile, "doc.tf")
|
||||
assert.Equal(options.OutputValues, true)
|
||||
assert.Equal(options.OutputValuesPath, "/path/to/output/values")
|
||||
|
||||
_, err3 := options.WithOverwrite(&Options{
|
||||
Path: "",
|
||||
OutputValues: false,
|
||||
})
|
||||
assert.Nil(err3)
|
||||
|
||||
assert.NotEqual(options.Path, "")
|
||||
assert.Equal(options.HeaderFromFile, "doc.tf")
|
||||
assert.NotEqual(options.OutputValues, false)
|
||||
assert.Equal(options.OutputValuesPath, "/path/to/output/values")
|
||||
}
|
||||
|
||||
func TestOptionsWithNilOverwrite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
options := NewOptions()
|
||||
|
||||
_, err := options.WithOverwrite(nil)
|
||||
|
||||
assert.NotNil(err)
|
||||
}
|
||||
165
terraform/output.go
Normal file
165
terraform/output.go
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
terraformsdk "github.com/terraform-docs/plugin-sdk/terraform"
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
// Output represents a Terraform output.
|
||||
type Output struct {
|
||||
Name string `json:"name" toml:"name" xml:"name" yaml:"name"`
|
||||
Description types.String `json:"description" toml:"description" xml:"description" yaml:"description"`
|
||||
Value types.Value `json:"value,omitempty" toml:"value,omitempty" xml:"value,omitempty" yaml:"value,omitempty"`
|
||||
Sensitive bool `json:"sensitive,omitempty" toml:"sensitive,omitempty" xml:"sensitive,omitempty" yaml:"sensitive,omitempty"`
|
||||
Position Position `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
ShowValue bool `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
type withvalue struct {
|
||||
Name string `json:"name" toml:"name" xml:"name" yaml:"name"`
|
||||
Description types.String `json:"description" toml:"description" xml:"description" yaml:"description"`
|
||||
Value types.Value `json:"value" toml:"value" xml:"value" yaml:"value"`
|
||||
Sensitive bool `json:"sensitive" toml:"sensitive" xml:"sensitive" yaml:"sensitive"`
|
||||
Position Position `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
ShowValue bool `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// GetValue returns JSON representation of the 'Value', which is an 'interface'.
|
||||
// If 'Value' is a primitive type, the primitive value of 'Value' will be returned
|
||||
// and not the JSON formatted of it.
|
||||
func (o *Output) GetValue() string {
|
||||
if !o.ShowValue || o.Value == nil {
|
||||
return ""
|
||||
}
|
||||
marshaled, err := json.MarshalIndent(o.Value, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
value := string(marshaled)
|
||||
if value == `null` {
|
||||
return "" // types.Nil
|
||||
}
|
||||
return value // everything else
|
||||
}
|
||||
|
||||
// HasDefault indicates if a Terraform output has a default value set.
|
||||
func (o *Output) HasDefault() bool {
|
||||
if !o.ShowValue || o.Value == nil {
|
||||
return false
|
||||
}
|
||||
return o.Value.HasDefault()
|
||||
}
|
||||
|
||||
// MarshalJSON custom yaml marshal function to take '--output-values' flag into
|
||||
// consideration. It means if the flag is not set Value and Sensitive fields are
|
||||
// set to 'omitempty', otherwise if output values are being shown 'omitempty' gets
|
||||
// explicitly removed to show even empty and false values.
|
||||
func (o *Output) MarshalJSON() ([]byte, error) {
|
||||
fn := func(oo interface{}) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(oo); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
if o.ShowValue {
|
||||
return fn(withvalue(*o))
|
||||
}
|
||||
o.Value = nil // explicitly make empty
|
||||
o.Sensitive = false // explicitly make empty
|
||||
return fn(*o)
|
||||
}
|
||||
|
||||
// MarshalXML custom xml marshal function to take '--output-values' flag into
|
||||
// consideration. It means if the flag is not set Value and Sensitive fields
|
||||
// are set to 'omitempty', otherwise if output values are being shown 'omitempty'
|
||||
// gets explicitly removed to show even empty and false values.
|
||||
func (o *Output) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
fn := func(v interface{}, name string) error {
|
||||
return e.EncodeElement(v, xml.StartElement{Name: xml.Name{Local: name}})
|
||||
}
|
||||
err := e.EncodeToken(start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fn(o.Name, "name") //nolint:errcheck,gosec
|
||||
fn(o.Description, "description") //nolint:errcheck,gosec
|
||||
if o.ShowValue {
|
||||
fn(o.Value, "value") //nolint:errcheck,gosec
|
||||
fn(o.Sensitive, "sensitive") //nolint:errcheck,gosec
|
||||
}
|
||||
return e.EncodeToken(start.End())
|
||||
}
|
||||
|
||||
// MarshalYAML custom yaml marshal function to take '--output-values' flag into
|
||||
// consideration. It means if the flag is not set Value and Sensitive fields are
|
||||
// set to 'omitempty', otherwise if output values are being shown 'omitempty' gets
|
||||
// explicitly removed to show even empty and false values.
|
||||
func (o *Output) MarshalYAML() (interface{}, error) {
|
||||
if o.ShowValue {
|
||||
return withvalue(*o), nil
|
||||
}
|
||||
o.Value = nil // explicitly make empty
|
||||
o.Sensitive = false // explicitly make empty
|
||||
return *o, nil
|
||||
}
|
||||
|
||||
// output is used for unmarshalling `terraform outputs --json` into
|
||||
type output struct {
|
||||
Sensitive bool `json:"sensitive"`
|
||||
Type interface{} `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func sortOutputsByName(x []*Output) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
return x[i].Name < x[j].Name
|
||||
})
|
||||
}
|
||||
|
||||
func sortOutputsByPosition(x []*Output) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
if x[i].Position.Filename == x[j].Position.Filename {
|
||||
return x[i].Position.Line < x[j].Position.Line
|
||||
}
|
||||
return x[i].Position.Filename < x[j].Position.Filename
|
||||
})
|
||||
}
|
||||
|
||||
type outputs []*Output
|
||||
|
||||
func (oo outputs) convert() []*terraformsdk.Output {
|
||||
list := []*terraformsdk.Output{}
|
||||
for _, o := range oo {
|
||||
list = append(list, &terraformsdk.Output{
|
||||
Name: o.Name,
|
||||
Description: fmt.Sprintf("%v", o.Description.Raw()),
|
||||
Value: nil,
|
||||
Sensitive: o.Sensitive,
|
||||
Position: terraformsdk.Position{
|
||||
Filename: o.Position.Filename,
|
||||
Line: o.Position.Line,
|
||||
},
|
||||
ShowValue: o.ShowValue,
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
527
terraform/output_test.go
Normal file
527
terraform/output_test.go
Normal file
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
func TestOutputValue(t *testing.T) {
|
||||
outputs := sampleOutputs()
|
||||
tests := []struct {
|
||||
name string
|
||||
output Output
|
||||
expectValue string
|
||||
expectDefault bool
|
||||
}{
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[0],
|
||||
expectValue: "",
|
||||
expectDefault: false,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[1],
|
||||
expectValue: "",
|
||||
expectDefault: false,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[2],
|
||||
expectValue: "false",
|
||||
expectDefault: true,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[3],
|
||||
expectValue: "\"\"",
|
||||
expectDefault: true,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[4],
|
||||
expectValue: "\"foo\"",
|
||||
expectDefault: true,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[5],
|
||||
expectValue: "",
|
||||
expectDefault: false,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[6],
|
||||
expectValue: "\"\\u003csensitive\\u003e\"",
|
||||
expectDefault: true,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[7],
|
||||
expectValue: "[\n \"a\",\n \"b\",\n \"c\"\n]",
|
||||
expectDefault: true,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[8],
|
||||
expectValue: "[]",
|
||||
expectDefault: true,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[9],
|
||||
expectValue: "{\n \"a\": 1,\n \"b\": 2,\n \"c\": 3\n}",
|
||||
expectDefault: true,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[10],
|
||||
expectValue: "{}",
|
||||
expectDefault: true,
|
||||
},
|
||||
{
|
||||
name: "output Value and HasDefault",
|
||||
output: outputs[11],
|
||||
expectValue: "",
|
||||
expectDefault: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(tt.expectValue, tt.output.GetValue())
|
||||
assert.Equal(tt.expectDefault, tt.output.HasDefault())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputMarshalJSON(t *testing.T) {
|
||||
outputs := sampleOutputs()
|
||||
tests := []struct {
|
||||
name string
|
||||
output Output
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[0],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":null,\"sensitive\":false}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[1],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\"}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[2],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":false,\"sensitive\":false}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[3],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":\"\",\"sensitive\":false}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[4],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":\"foo\",\"sensitive\":false}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[5],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\"}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[6],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":\"<sensitive>\",\"sensitive\":true}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[7],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":[\"a\",\"b\",\"c\"],\"sensitive\":false}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[8],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":[],\"sensitive\":false}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[9],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":{\"a\":1,\"b\":2,\"c\":3},\"sensitive\":false}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[10],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":{},\"sensitive\":false}\n",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[11],
|
||||
expected: "{\"name\":\"output\",\"description\":\"description\",\"value\":null,\"sensitive\":false}\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual, err := tt.output.MarshalJSON()
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(tt.expected, string(actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputMarshalXML(t *testing.T) {
|
||||
outputs := sampleOutputs()
|
||||
tests := []struct {
|
||||
name string
|
||||
output Output
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[0],
|
||||
expected: "<output><name>output</name><description>description</description><value xsi:nil=\"true\"></value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[1],
|
||||
expected: "<output><name>output</name><description>description</description></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[2],
|
||||
expected: "<output><name>output</name><description>description</description><value>false</value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[3],
|
||||
expected: "<output><name>output</name><description>description</description><value></value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[4],
|
||||
expected: "<output><name>output</name><description>description</description><value>foo</value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[5],
|
||||
expected: "<output><name>output</name><description>description</description></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[6],
|
||||
expected: "<output><name>output</name><description>description</description><value><sensitive></value><sensitive>true</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[7],
|
||||
expected: "<output><name>output</name><description>description</description><value><item>a</item><item>b</item><item>c</item></value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[8],
|
||||
expected: "<output><name>output</name><description>description</description><value></value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[9],
|
||||
expected: "<output><name>output</name><description>description</description><value><a>1</a><b>2</b><c>3</c></value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[10],
|
||||
expected: "<output><name>output</name><description>description</description><value></value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
{
|
||||
name: "output marshal XML",
|
||||
output: outputs[11],
|
||||
expected: "<output><name>output</name><description>description</description><value xsi:nil=\"true\"></value><sensitive>false</sensitive></output>",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
var b bytes.Buffer
|
||||
encoder := xml.NewEncoder(&b)
|
||||
start := xml.StartElement{Name: xml.Name{Local: "output"}}
|
||||
|
||||
err := tt.output.MarshalXML(encoder, start)
|
||||
assert.Nil(err)
|
||||
|
||||
err = encoder.Flush()
|
||||
assert.Nil(err)
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(tt.expected, b.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputMarshalYAML(t *testing.T) {
|
||||
outputs := sampleOutputs()
|
||||
tests := []struct {
|
||||
name string
|
||||
output Output
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[0],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[1],
|
||||
expected: "terraform.Output",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[2],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[3],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[4],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[5],
|
||||
expected: "terraform.Output",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[6],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[7],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[8],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[9],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[10],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
{
|
||||
name: "output marshal JSON",
|
||||
output: outputs[11],
|
||||
expected: "terraform.withvalue",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual, err := tt.output.MarshalYAML()
|
||||
|
||||
assert.Nil(err)
|
||||
assert.Equal(tt.expected, reflect.TypeOf(actual).String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sampleOutputs() []Output {
|
||||
name := "output"
|
||||
description := types.String("description")
|
||||
position := Position{Filename: "foo.tf", Line: 13}
|
||||
return []Output{
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf(nil),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Position: position,
|
||||
ShowValue: false,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf(false),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf(""),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf("foo"),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf("this should be hidden"),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: false,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf("<sensitive>"),
|
||||
Sensitive: true,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf(types.List{"a", "b", "c"}.Underlying()),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf(types.List{}.Underlying()),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf(types.Map{"a": 1, "b": 2, "c": 3}.Underlying()),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf(types.Map{}.Underlying()),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Value: types.ValueOf(nil),
|
||||
Sensitive: false,
|
||||
Position: position,
|
||||
ShowValue: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputsSort(t *testing.T) {
|
||||
outputs := sampleOutputsForSort()
|
||||
tests := map[string]struct {
|
||||
sortType func([]*Output)
|
||||
expected []string
|
||||
}{
|
||||
"ByName": {
|
||||
sortType: sortOutputsByName,
|
||||
expected: []string{"a", "b", "c", "d", "e"},
|
||||
},
|
||||
"ByPosition": {
|
||||
sortType: sortOutputsByPosition,
|
||||
expected: []string{"d", "a", "e", "b", "c"},
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tt.sortType(outputs)
|
||||
|
||||
actual := make([]string, len(outputs))
|
||||
|
||||
for k, o := range outputs {
|
||||
actual[k] = o.Name
|
||||
}
|
||||
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sampleOutputsForSort() []*Output {
|
||||
return []*Output{
|
||||
{
|
||||
Name: "a",
|
||||
Description: types.String("description of a"),
|
||||
Value: nil,
|
||||
Position: Position{Filename: "foo/outputs.tf", Line: 25},
|
||||
},
|
||||
{
|
||||
Name: "d",
|
||||
Description: types.String("description of d"),
|
||||
Value: nil,
|
||||
Position: Position{Filename: "foo/outputs.tf", Line: 10},
|
||||
},
|
||||
{
|
||||
Name: "e",
|
||||
Description: types.String("description of e"),
|
||||
Value: nil,
|
||||
Position: Position{Filename: "foo/outputs.tf", Line: 33},
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Description: types.String("description of b"),
|
||||
Value: nil,
|
||||
Position: Position{Filename: "foo/outputs.tf", Line: 39},
|
||||
},
|
||||
{
|
||||
Name: "c",
|
||||
Description: types.String("description of c"),
|
||||
Value: nil,
|
||||
Position: Position{Filename: "foo/outputs.tf", Line: 42},
|
||||
},
|
||||
}
|
||||
}
|
||||
17
terraform/position.go
Normal file
17
terraform/position.go
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
// Position represents position of Terraform item (input, output, provider, etc) in a file.
|
||||
type Position struct {
|
||||
Filename string `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
Line int `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
71
terraform/provider.go
Normal file
71
terraform/provider.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
terraformsdk "github.com/terraform-docs/plugin-sdk/terraform"
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
// Provider represents a Terraform output.
|
||||
type Provider struct {
|
||||
Name string `json:"name" toml:"name" xml:"name" yaml:"name"`
|
||||
Alias types.String `json:"alias" toml:"alias" xml:"alias" yaml:"alias"`
|
||||
Version types.String `json:"version" toml:"version" xml:"version" yaml:"version"`
|
||||
Position Position `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// FullName returns full name of the provider, with alias if available
|
||||
func (p *Provider) FullName() string {
|
||||
if p.Alias != "" {
|
||||
return fmt.Sprintf("%s.%s", p.Name, p.Alias)
|
||||
}
|
||||
return p.Name
|
||||
}
|
||||
|
||||
func sortProvidersByName(x []*Provider) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
if x[i].Name == x[j].Name {
|
||||
return x[i].Name == x[j].Name && x[i].Alias < x[j].Alias
|
||||
}
|
||||
return x[i].Name < x[j].Name
|
||||
})
|
||||
}
|
||||
|
||||
func sortProvidersByPosition(x []*Provider) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
if x[i].Position.Filename == x[j].Position.Filename {
|
||||
return x[i].Position.Line < x[j].Position.Line
|
||||
}
|
||||
return x[i].Position.Filename < x[j].Position.Filename
|
||||
})
|
||||
}
|
||||
|
||||
type providers []*Provider
|
||||
|
||||
func (pp providers) convert() []*terraformsdk.Provider {
|
||||
list := []*terraformsdk.Provider{}
|
||||
for _, p := range pp {
|
||||
list = append(list, &terraformsdk.Provider{
|
||||
Name: p.Name,
|
||||
Alias: fmt.Sprintf("%v", p.Alias.Raw()),
|
||||
Version: fmt.Sprintf("%v", p.Version.Raw()),
|
||||
Position: terraformsdk.Position{
|
||||
Filename: p.Position.Filename,
|
||||
Line: p.Position.Line,
|
||||
},
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
130
terraform/provider_test.go
Normal file
130
terraform/provider_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
func TestProviderName(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
provider Provider
|
||||
expected string
|
||||
}{
|
||||
"WithoutAlias": {
|
||||
provider: Provider{
|
||||
Name: "provider",
|
||||
Alias: types.String(""),
|
||||
Version: types.String(">= 1.2.3"),
|
||||
Position: Position{Filename: "foo.tf", Line: 13},
|
||||
},
|
||||
expected: "provider",
|
||||
},
|
||||
"WithAlias": {
|
||||
provider: Provider{
|
||||
Name: "provider",
|
||||
Alias: types.String("alias"),
|
||||
Version: types.String(">= 1.2.3"),
|
||||
Position: Position{Filename: "foo.tf", Line: 13},
|
||||
},
|
||||
expected: "provider.alias",
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.Equal(tt.expected, tt.provider.FullName())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvidersSort(t *testing.T) {
|
||||
providers := sampleProviders()
|
||||
tests := map[string]struct {
|
||||
sortType func([]*Provider)
|
||||
expected []string
|
||||
}{
|
||||
"ByName": {
|
||||
sortType: sortProvidersByName,
|
||||
expected: []string{"a", "b", "c", "d", "d.a", "e", "e.a"},
|
||||
},
|
||||
"ByPosition": {
|
||||
sortType: sortProvidersByPosition,
|
||||
expected: []string{"e.a", "b", "d", "d.a", "a", "e", "c"},
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tt.sortType(providers)
|
||||
|
||||
actual := make([]string, len(providers))
|
||||
|
||||
for k, p := range providers {
|
||||
actual[k] = p.FullName()
|
||||
}
|
||||
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sampleProviders() []*Provider {
|
||||
return []*Provider{
|
||||
{
|
||||
Name: "d",
|
||||
Alias: types.String(""),
|
||||
Version: types.String("1.3.2"),
|
||||
Position: Position{Filename: "foo/main.tf", Line: 21},
|
||||
},
|
||||
{
|
||||
Name: "d",
|
||||
Alias: types.String("a"),
|
||||
Version: types.String("> 1.x"),
|
||||
Position: Position{Filename: "foo/main.tf", Line: 25},
|
||||
},
|
||||
{
|
||||
Name: "b",
|
||||
Alias: types.String(""),
|
||||
Version: types.String("= 2.1.0"),
|
||||
Position: Position{Filename: "foo/main.tf", Line: 13},
|
||||
},
|
||||
{
|
||||
Name: "a",
|
||||
Alias: types.String(""),
|
||||
Version: types.String(""),
|
||||
Position: Position{Filename: "foo/main.tf", Line: 39},
|
||||
},
|
||||
{
|
||||
Name: "c",
|
||||
Alias: types.String(""),
|
||||
Version: types.String("~> 0.5.0"),
|
||||
Position: Position{Filename: "foo/main.tf", Line: 53},
|
||||
},
|
||||
{
|
||||
Name: "e",
|
||||
Alias: types.String(""),
|
||||
Version: types.String(""),
|
||||
Position: Position{Filename: "foo/main.tf", Line: 47},
|
||||
},
|
||||
{
|
||||
Name: "e",
|
||||
Alias: types.String("a"),
|
||||
Version: types.String("> 1.0"),
|
||||
Position: Position{Filename: "foo/main.tf", Line: 5},
|
||||
},
|
||||
}
|
||||
}
|
||||
37
terraform/requirement.go
Normal file
37
terraform/requirement.go
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
terraformsdk "github.com/terraform-docs/plugin-sdk/terraform"
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
// Requirement represents a requirement for Terraform module.
|
||||
type Requirement struct {
|
||||
Name string `json:"name" toml:"name" xml:"name" yaml:"name"`
|
||||
Version types.String `json:"version" toml:"version" xml:"version" yaml:"version"`
|
||||
}
|
||||
|
||||
type requirements []*Requirement
|
||||
|
||||
func (rr requirements) convert() []*terraformsdk.Requirement {
|
||||
list := []*terraformsdk.Requirement{}
|
||||
for _, r := range rr {
|
||||
list = append(list, &terraformsdk.Requirement{
|
||||
Name: r.Name,
|
||||
Version: fmt.Sprintf("%v", r.Version.Raw()),
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
103
terraform/resource.go
Normal file
103
terraform/resource.go
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
terraformsdk "github.com/terraform-docs/plugin-sdk/terraform"
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
// Resource represents a managed or data type that is created by the module
|
||||
type Resource struct {
|
||||
Type string `json:"type" toml:"type" xml:"type" yaml:"type"`
|
||||
Name string `json:"name" toml:"name" xml:"name" yaml:"name"`
|
||||
ProviderName string `json:"provider" toml:"provider" xml:"provider" yaml:"provider"`
|
||||
ProviderSource string `json:"source" toml:"source" xml:"source" yaml:"source"`
|
||||
Mode string `json:"mode" toml:"mode" xml:"mode" yaml:"mode"`
|
||||
Version types.String `json:"version" toml:"version" xml:"version" yaml:"version"`
|
||||
Position Position `json:"-" toml:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// Spec returns the resource spec addresses a specific resource in the config.
|
||||
// It takes the form: resource_type.resource_name[resource index]
|
||||
// For more details, see:
|
||||
// https://www.terraform.io/docs/cli/state/resource-addressing.html#resource-spec
|
||||
func (r *Resource) Spec() string {
|
||||
return r.ProviderName + "_" + r.Type + "." + r.Name
|
||||
}
|
||||
|
||||
// GetMode returns normalized resource type as "resource" or "data source"
|
||||
func (r *Resource) GetMode() string {
|
||||
switch r.Mode {
|
||||
case "managed":
|
||||
return "resource"
|
||||
case "data":
|
||||
return "data source"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// URL returns a best guess at the URL for resource documentation
|
||||
func (r *Resource) URL() string {
|
||||
kind := ""
|
||||
switch r.Mode {
|
||||
case "managed":
|
||||
kind = "resources"
|
||||
case "data":
|
||||
kind = "data-sources"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
if strings.Count(r.ProviderSource, "/") > 1 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("https://registry.terraform.io/providers/%s/%s/docs/%s/%s", r.ProviderSource, r.Version, kind, r.Type)
|
||||
}
|
||||
|
||||
func sortResourcesByType(x []*Resource) {
|
||||
sort.Slice(x, func(i, j int) bool {
|
||||
if x[i].Mode == x[j].Mode {
|
||||
if x[i].Spec() == x[j].Spec() {
|
||||
return x[i].Name <= x[j].Name
|
||||
}
|
||||
return x[i].Spec() < x[j].Spec()
|
||||
}
|
||||
return x[i].Mode > x[j].Mode
|
||||
})
|
||||
}
|
||||
|
||||
type resources []*Resource
|
||||
|
||||
func (rr resources) convert() []*terraformsdk.Resource {
|
||||
list := []*terraformsdk.Resource{}
|
||||
for _, r := range rr {
|
||||
list = append(list, &terraformsdk.Resource{
|
||||
Type: r.Type,
|
||||
Name: r.Name,
|
||||
ProviderName: r.ProviderName,
|
||||
ProviderSource: r.ProviderSource,
|
||||
Mode: r.Mode,
|
||||
Version: fmt.Sprintf("%v", r.Version.Raw()),
|
||||
Position: terraformsdk.Position{
|
||||
Filename: r.Position.Filename,
|
||||
Line: r.Position.Line,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
341
terraform/resource_test.go
Normal file
341
terraform/resource_test.go
Normal file
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
Copyright 2021 The terraform-docs Authors.
|
||||
|
||||
Licensed under the MIT license (the "License"); you may not
|
||||
use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/terraform-docs/terraform-docs/internal/types"
|
||||
)
|
||||
|
||||
func TestResourceSpec(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
resource := Resource{
|
||||
Type: "private_key",
|
||||
Name: "baz",
|
||||
ProviderName: "tls",
|
||||
ProviderSource: "hashicorp/tls",
|
||||
Mode: "managed",
|
||||
Version: types.String("latest"),
|
||||
}
|
||||
assert.Equal("tls_private_key.baz", resource.Spec())
|
||||
}
|
||||
func TestPluginSdkConversion(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
resource := Resource{
|
||||
Type: "private_key",
|
||||
Name: "baz",
|
||||
ProviderName: "tls",
|
||||
ProviderSource: "hashicorp/tls",
|
||||
Mode: "managed",
|
||||
Version: types.String("latest"),
|
||||
}
|
||||
sdkResource := resources{&resource}.convert()[0]
|
||||
assert.Equal(resource.Type, sdkResource.Type)
|
||||
assert.Equal(resource.Name, sdkResource.Name)
|
||||
assert.Equal(resource.ProviderName, sdkResource.ProviderName)
|
||||
assert.Equal(resource.ProviderSource, sdkResource.ProviderSource)
|
||||
assert.Equal(resource.Mode, sdkResource.Mode)
|
||||
assert.Equal(resource.Version, types.String(sdkResource.Version))
|
||||
}
|
||||
|
||||
func TestResourceMode(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
resource Resource
|
||||
expectValue string
|
||||
}{
|
||||
"Managed": {
|
||||
resource: Resource{
|
||||
Type: "private_key",
|
||||
ProviderName: "tls",
|
||||
ProviderSource: "hashicorp/tls",
|
||||
Mode: "managed",
|
||||
Version: types.String("latest"),
|
||||
},
|
||||
expectValue: "resource",
|
||||
},
|
||||
"Data Source": {
|
||||
resource: Resource{
|
||||
Type: "caller_identity",
|
||||
ProviderName: "aws",
|
||||
ProviderSource: "hashicorp/aws",
|
||||
Mode: "data",
|
||||
Version: types.String("latest"),
|
||||
},
|
||||
expectValue: "data source",
|
||||
},
|
||||
"Invalid": {
|
||||
resource: Resource{
|
||||
Type: "caller_identity",
|
||||
ProviderName: "aws",
|
||||
ProviderSource: "hashicorp/aws",
|
||||
Mode: "",
|
||||
Version: types.String("latest"),
|
||||
},
|
||||
expectValue: "invalid",
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(tt.expectValue, tt.resource.GetMode())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceURL(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
resource Resource
|
||||
expectValue string
|
||||
}{
|
||||
"Generic URL construction": {
|
||||
resource: Resource{
|
||||
Type: "private_key",
|
||||
ProviderName: "tls",
|
||||
ProviderSource: "hashicorp/tls",
|
||||
Mode: "managed",
|
||||
Version: types.String("latest"),
|
||||
},
|
||||
expectValue: "https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key",
|
||||
},
|
||||
"Unable to construct URL": {
|
||||
resource: Resource{
|
||||
Type: "custom",
|
||||
ProviderName: "nih",
|
||||
ProviderSource: "http://nih.tld/some/path/to/provider/source",
|
||||
Mode: "managed",
|
||||
Version: types.String("latest"),
|
||||
},
|
||||
expectValue: "",
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(tt.expectValue, tt.resource.URL())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourcesSortedByType(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
resources := sampleResources()
|
||||
|
||||
sortResourcesByType(resources)
|
||||
|
||||
expected := []string{"a_a.a", "a_f.f", "b_b.b", "b_d.d", "c_c.c", "c_e.c", "c_e.d", "c_e_x.c", "c_e_x.d", "z_z.z", "a_a.a", "z_z.z", "a_a.a", "z_z.z"}
|
||||
actual := make([]string, len(resources))
|
||||
|
||||
for k, i := range resources {
|
||||
actual[k] = i.Spec()
|
||||
}
|
||||
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestResourcesSortedByTypeAndMode(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
resources := sampleResources()
|
||||
|
||||
sortResourcesByType(resources)
|
||||
|
||||
expected := []string{"a_a.a (r)", "a_f.f (r)", "b_b.b (r)", "b_d.d (r)", "c_c.c (r)", "c_e.c (r)", "c_e.d (r)", "c_e_x.c (r)", "c_e_x.d (r)", "z_z.z (r)", "a_a.a (d)", "z_z.z (d)", "a_a.a", "z_z.z"}
|
||||
actual := make([]string, len(resources))
|
||||
|
||||
for k, i := range resources {
|
||||
mode := ""
|
||||
switch i.Mode {
|
||||
case "managed":
|
||||
mode = " (r)"
|
||||
case "data":
|
||||
mode = " (d)"
|
||||
}
|
||||
actual[k] = i.Spec() + mode
|
||||
}
|
||||
|
||||
assert.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func TestResourceVersion(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
constraint []string
|
||||
expected string
|
||||
}{
|
||||
"exact version, without operator": {
|
||||
constraint: []string{"1.2.3"},
|
||||
expected: "1.2.3",
|
||||
},
|
||||
"exact version, with operator": {
|
||||
constraint: []string{"= 1.2.3"},
|
||||
expected: "1.2.3",
|
||||
},
|
||||
"exact version, with operator, without space": {
|
||||
constraint: []string{"=1.2.3"},
|
||||
expected: "1.2.3",
|
||||
},
|
||||
"exclude exact version, with space": {
|
||||
constraint: []string{"!= 1.2.3"},
|
||||
expected: "latest",
|
||||
},
|
||||
"exclude exact version, without space": {
|
||||
constraint: []string{"!=1.2.3"},
|
||||
expected: "latest",
|
||||
},
|
||||
"comparison version, with space": {
|
||||
constraint: []string{"> 1.2.3"},
|
||||
expected: "latest",
|
||||
},
|
||||
"comparison version, without space": {
|
||||
constraint: []string{">1.2.3"},
|
||||
expected: "latest",
|
||||
},
|
||||
"range version": {
|
||||
constraint: []string{"> 1.2.3, < 2.0.0"},
|
||||
expected: "latest",
|
||||
},
|
||||
"pessimistic version, with space": {
|
||||
constraint: []string{"~> 1.2.3"},
|
||||
expected: "latest",
|
||||
},
|
||||
"pessimistic version, without space": {
|
||||
constraint: []string{"~>1.2.3"},
|
||||
expected: "latest",
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(tt.expected, resourceVersion(tt.constraint))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sampleResources() []*Resource {
|
||||
return []*Resource{
|
||||
{
|
||||
Type: "e",
|
||||
Name: "d",
|
||||
ProviderName: "c",
|
||||
ProviderSource: "hashicorp/e",
|
||||
Mode: "managed",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
Type: "e",
|
||||
Name: "c",
|
||||
ProviderName: "c",
|
||||
ProviderSource: "hashicorp/e",
|
||||
Mode: "managed",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
Type: "e_x",
|
||||
Name: "d",
|
||||
ProviderName: "c",
|
||||
ProviderSource: "hashicorp/e",
|
||||
Mode: "managed",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
Type: "e_x",
|
||||
Name: "c",
|
||||
ProviderName: "c",
|
||||
ProviderSource: "hashicorp/e",
|
||||
Mode: "managed",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
Type: "a",
|
||||
Name: "a",
|
||||
ProviderName: "a",
|
||||
ProviderSource: "hashicorp/a",
|
||||
Mode: "managed",
|
||||
Version: "1.1.0",
|
||||
},
|
||||
{
|
||||
Type: "d",
|
||||
Name: "d",
|
||||
ProviderName: "b",
|
||||
ProviderSource: "hashicorp/d",
|
||||
Mode: "managed",
|
||||
Version: "1.4.0",
|
||||
},
|
||||
{
|
||||
Type: "b",
|
||||
Name: "b",
|
||||
ProviderName: "b",
|
||||
ProviderSource: "hashicorp/b",
|
||||
Mode: "managed",
|
||||
Version: "1.2.0",
|
||||
},
|
||||
{
|
||||
Type: "c",
|
||||
Name: "c",
|
||||
ProviderName: "c",
|
||||
ProviderSource: "hashicorp/c",
|
||||
Mode: "managed",
|
||||
Version: "1.3.0",
|
||||
},
|
||||
{
|
||||
Type: "f",
|
||||
Name: "f",
|
||||
ProviderName: "a",
|
||||
ProviderSource: "hashicorp/f",
|
||||
Mode: "managed",
|
||||
Version: "1.6.0",
|
||||
},
|
||||
{
|
||||
ProviderName: "z",
|
||||
Type: "z",
|
||||
Name: "z",
|
||||
ProviderSource: "hashicorp/a",
|
||||
Mode: "managed",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
ProviderName: "z",
|
||||
Type: "z",
|
||||
Name: "z",
|
||||
ProviderSource: "hashicorp/a",
|
||||
Mode: "data",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
ProviderName: "a",
|
||||
Type: "a",
|
||||
Name: "a",
|
||||
ProviderSource: "hashicorp/a",
|
||||
Mode: "data",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
ProviderName: "z",
|
||||
Type: "z",
|
||||
Name: "z",
|
||||
ProviderSource: "hashicorp/a",
|
||||
Mode: "",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
ProviderName: "a",
|
||||
Type: "a",
|
||||
Name: "a",
|
||||
ProviderSource: "hashicorp/a",
|
||||
Mode: "",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
}
|
||||
}
|
||||
7
terraform/testdata/empty-header/main.tf
vendored
Normal file
7
terraform/testdata/empty-header/main.tf
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
resource "tls_private_key" "baz" {}
|
||||
|
||||
data "aws_caller_identity" "current" {
|
||||
provider = "aws"
|
||||
}
|
||||
|
||||
resource "null_resource" "foo" {}
|
||||
7
terraform/testdata/expected/full-example-mainTf-Header.golden
vendored
Normal file
7
terraform/testdata/expected/full-example-mainTf-Header.golden
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
Example of 'foo_bar' module in `foo_bar.tf`.
|
||||
|
||||
- list item 1
|
||||
- list item 2
|
||||
|
||||
Even inline **formatting** in _here_ is possible.
|
||||
and some [link](https://domain.com/)
|
||||
6
terraform/testdata/full-example/doc.adoc
vendored
Normal file
6
terraform/testdata/full-example/doc.adoc
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
= Custom Header
|
||||
|
||||
Example of 'foo_bar' module in `foo_bar.tf`.
|
||||
|
||||
- list item 1
|
||||
- list item 2
|
||||
6
terraform/testdata/full-example/doc.md
vendored
Normal file
6
terraform/testdata/full-example/doc.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Custom Header
|
||||
|
||||
Example of 'foo_bar' module in `foo_bar.tf`.
|
||||
|
||||
- list item 1
|
||||
- list item 2
|
||||
8
terraform/testdata/full-example/doc.tf
vendored
Normal file
8
terraform/testdata/full-example/doc.tf
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Custom Header:
|
||||
*
|
||||
* Example of 'foo_bar' module in `foo_bar.tf`.
|
||||
*
|
||||
* - list item 1
|
||||
* - list item 2
|
||||
*/
|
||||
6
terraform/testdata/full-example/doc.txt
vendored
Normal file
6
terraform/testdata/full-example/doc.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Custom Header
|
||||
|
||||
Example of 'foo_bar' module in `foo_bar.tf`.
|
||||
|
||||
- list item 1
|
||||
- list item 2
|
||||
33
terraform/testdata/full-example/main.tf
vendored
Normal file
33
terraform/testdata/full-example/main.tf
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Example of 'foo_bar' module in `foo_bar.tf`.
|
||||
*
|
||||
* - list item 1
|
||||
* - list item 2
|
||||
*
|
||||
* Even inline **formatting** in _here_ is possible.
|
||||
* and some [link](https://domain.com/)
|
||||
*/
|
||||
|
||||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
required_providers {
|
||||
aws = ">= 2.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
resource "tls_private_key" "baz" {}
|
||||
|
||||
data "aws_caller_identity" "current" {
|
||||
provider = "aws"
|
||||
}
|
||||
|
||||
resource "null_resource" "foo" {}
|
||||
|
||||
module "foo" {
|
||||
source = "bar"
|
||||
version = "1.2.3"
|
||||
}
|
||||
|
||||
module "foobar" {
|
||||
source = "git@github.com:module/path?ref=v7.8.9"
|
||||
}
|
||||
17
terraform/testdata/full-example/output-values.json
vendored
Normal file
17
terraform/testdata/full-example/output-values.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"C": {
|
||||
"sensitive": true,
|
||||
"type": "string",
|
||||
"value": "sensitive-c"
|
||||
},
|
||||
"A": {
|
||||
"sensitive": false,
|
||||
"type": "string",
|
||||
"value": "a value"
|
||||
},
|
||||
"B": {
|
||||
"sensitive": false,
|
||||
"type": "string",
|
||||
"value": "b value"
|
||||
}
|
||||
}
|
||||
14
terraform/testdata/full-example/outputs.tf
vendored
Normal file
14
terraform/testdata/full-example/outputs.tf
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
output C {
|
||||
description = "It's unquoted output."
|
||||
value = "c"
|
||||
}
|
||||
|
||||
output "A" {
|
||||
description = "A description"
|
||||
value = "a"
|
||||
}
|
||||
|
||||
// B description
|
||||
output "B" {
|
||||
value = "b"
|
||||
}
|
||||
30
terraform/testdata/full-example/variables.tf
vendored
Normal file
30
terraform/testdata/full-example/variables.tf
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// D description
|
||||
variable "D" {
|
||||
default = "d"
|
||||
}
|
||||
|
||||
variable "B" {
|
||||
default = "b"
|
||||
}
|
||||
|
||||
variable "E" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
# A Description
|
||||
# in multiple lines
|
||||
variable A {}
|
||||
|
||||
variable "C" {
|
||||
description = "C description"
|
||||
default = "c"
|
||||
}
|
||||
|
||||
variable "F" {
|
||||
description = "F description"
|
||||
}
|
||||
|
||||
variable "G" {
|
||||
description = "G description"
|
||||
default = null
|
||||
}
|
||||
7
terraform/testdata/inputs-crlf/variables.tf
vendored
Normal file
7
terraform/testdata/inputs-crlf/variables.tf
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
variable "multi-line-lf" {
|
||||
type = string
|
||||
description = <<-EOT
|
||||
The quick brown fox jumps
|
||||
over the lazy dog
|
||||
EOT
|
||||
}
|
||||
7
terraform/testdata/inputs-lf/variables.tf
vendored
Normal file
7
terraform/testdata/inputs-lf/variables.tf
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
variable "multi-line-crlf" {
|
||||
type = string
|
||||
description = <<-EOT
|
||||
The quick brown fox jumps
|
||||
over the lazy dog
|
||||
EOT
|
||||
}
|
||||
0
terraform/testdata/no-inputs/variables.tf
vendored
Normal file
0
terraform/testdata/no-inputs/variables.tf
vendored
Normal file
1
terraform/testdata/no-modulecalls/main.tf
vendored
Normal file
1
terraform/testdata/no-modulecalls/main.tf
vendored
Normal file
@@ -0,0 +1 @@
|
||||
resource "tls_private_key" "baz" {}
|
||||
18
terraform/testdata/no-optional-inputs/variables.tf
vendored
Normal file
18
terraform/testdata/no-optional-inputs/variables.tf
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// D description
|
||||
variable "D" {}
|
||||
|
||||
variable "B" {}
|
||||
|
||||
variable "E" {}
|
||||
|
||||
# A Description
|
||||
# in multiple lines
|
||||
variable A {}
|
||||
|
||||
variable "C" {
|
||||
description = "C description"
|
||||
}
|
||||
|
||||
variable "F" {
|
||||
description = "F description"
|
||||
}
|
||||
0
terraform/testdata/no-outputs/outputs.tf
vendored
Normal file
0
terraform/testdata/no-outputs/outputs.tf
vendored
Normal file
0
terraform/testdata/no-providers/main.tf
vendored
Normal file
0
terraform/testdata/no-providers/main.tf
vendored
Normal file
28
terraform/testdata/no-required-inputs/variables.tf
vendored
Normal file
28
terraform/testdata/no-required-inputs/variables.tf
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// D description
|
||||
variable "D" {
|
||||
default = "d"
|
||||
}
|
||||
|
||||
variable "B" {
|
||||
default = "b"
|
||||
}
|
||||
|
||||
variable "E" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
# A Description
|
||||
# in multiple lines
|
||||
variable A {
|
||||
default = "a"
|
||||
}
|
||||
|
||||
variable "C" {
|
||||
description = "C description"
|
||||
default = "c"
|
||||
}
|
||||
|
||||
variable "F" {
|
||||
description = "F description"
|
||||
default = "f"
|
||||
}
|
||||
9
terraform/testdata/read-comments/variables.tf
vendored
Normal file
9
terraform/testdata/read-comments/variables.tf
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// B description
|
||||
variable "B" {
|
||||
default = "b"
|
||||
}
|
||||
|
||||
// B description
|
||||
output "B" {
|
||||
value = "b"
|
||||
}
|
||||
57
terraform/testdata/with-lock-file/.terraform.lock.hcl
generated
vendored
Normal file
57
terraform/testdata/with-lock-file/.terraform.lock.hcl
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "3.42.0"
|
||||
constraints = ">= 2.15.0"
|
||||
hashes = [
|
||||
"h1:quV6hK7ewiHWBznGWCb/gJ6JAPm6UtouBUrhAjv6oRY=",
|
||||
"zh:126c856a6eedddd8571f161a826a407ba5655a37a6241393560a96b8c4beca1a",
|
||||
"zh:1a4868e6ac734b5fc2e79a4a889d176286b66664aad709435aa6acee5871d5b0",
|
||||
"zh:40fed7637ab8ddeb93bef06aded35d970f0628025b97459ae805463e8aa0a58a",
|
||||
"zh:68def3c0a5a1aac1db6372c51daef858b707f03052626d3427ac24cba6f2014d",
|
||||
"zh:6db7ec9c8d1803a0b6f40a664aa892e0f8894562de83061fa7ac1bc51ff5e7e5",
|
||||
"zh:7058abaad595930b3f97dc04e45c112b2dbf37d098372a849081f7081da2fb52",
|
||||
"zh:8c25adb15a19da301c478aa1f4a4d8647cabdf8e5dae8331d4490f80ea718c26",
|
||||
"zh:8e129b847401e39fcbc54817726dab877f36b7f00ff5ed76f7b43470abe99ff9",
|
||||
"zh:d268bb267a2d6b39df7ddee8efa7c1ef7a15cf335dfa5f2e64c9dae9b623a1b8",
|
||||
"zh:d6eeb3614a0ab50f8e9ab5666ae5754ea668ce327310e5b21b7f04a18d7611a8",
|
||||
"zh:f5d3c58055dff6e38562b75d3edc908cb2f1e45c6914f6b00f4773359ce49324",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/null" {
|
||||
version = "3.1.0"
|
||||
hashes = [
|
||||
"h1:vpC6bgUQoJ0znqIKVFevOdq+YQw42bRq0u+H3nto8nA=",
|
||||
"zh:02a1675fd8de126a00460942aaae242e65ca3380b5bb192e8773ef3da9073fd2",
|
||||
"zh:53e30545ff8926a8e30ad30648991ca8b93b6fa496272cd23b26763c8ee84515",
|
||||
"zh:5f9200bf708913621d0f6514179d89700e9aa3097c77dac730e8ba6e5901d521",
|
||||
"zh:9ebf4d9704faba06b3ec7242c773c0fbfe12d62db7d00356d4f55385fc69bfb2",
|
||||
"zh:a6576c81adc70326e4e1c999c04ad9ca37113a6e925aefab4765e5a5198efa7e",
|
||||
"zh:a8a42d13346347aff6c63a37cda9b2c6aa5cc384a55b2fe6d6adfa390e609c53",
|
||||
"zh:c797744d08a5307d50210e0454f91ca4d1c7621c68740441cf4579390452321d",
|
||||
"zh:cecb6a304046df34c11229f20a80b24b1603960b794d68361a67c5efe58e62b8",
|
||||
"zh:e1371aa1e502000d9974cfaff5be4cfa02f47b17400005a16f14d2ef30dc2a70",
|
||||
"zh:fc39cc1fe71234a0b0369d5c5c7f876c71b956d23d7d6f518289737a001ba69b",
|
||||
"zh:fea4227271ebf7d9e2b61b89ce2328c7262acd9fd190e1fd6d15a591abfa848e",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/tls" {
|
||||
version = "3.1.0"
|
||||
hashes = [
|
||||
"h1:fUJX8Zxx38e2kBln+zWr1Tl41X+OuiE++REjrEyiOM4=",
|
||||
"zh:3d46616b41fea215566f4a957b6d3a1aa43f1f75c26776d72a98bdba79439db6",
|
||||
"zh:623a203817a6dafa86f1b4141b645159e07ec418c82fe40acd4d2a27543cbaa2",
|
||||
"zh:668217e78b210a6572e7b0ecb4134a6781cc4d738f4f5d09eb756085b082592e",
|
||||
"zh:95354df03710691773c8f50a32e31fca25f124b7f3d6078265fdf3c4e1384dca",
|
||||
"zh:9f97ab190380430d57392303e3f36f4f7835c74ea83276baa98d6b9a997c3698",
|
||||
"zh:a16f0bab665f8d933e95ca055b9c8d5707f1a0dd8c8ecca6c13091f40dc1e99d",
|
||||
"zh:be274d5008c24dc0d6540c19e22dbb31ee6bfdd0b2cddd4d97f3cd8a8d657841",
|
||||
"zh:d5faa9dce0a5fc9d26b2463cea5be35f8586ab75030e7fa4d4920cd73ee26989",
|
||||
"zh:e9b672210b7fb410780e7b429975adcc76dd557738ecc7c890ea18942eb321a5",
|
||||
"zh:eb1f8368573d2370605d6dbf60f9aaa5b64e55741d96b5fb026dbfe91de67c0d",
|
||||
"zh:fc1e12b713837b85daf6c3bb703d7795eaf1c5177aebae1afcf811dd7009f4b0",
|
||||
]
|
||||
}
|
||||
33
terraform/testdata/with-lock-file/main.tf
vendored
Normal file
33
terraform/testdata/with-lock-file/main.tf
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Example of 'foo_bar' module in `foo_bar.tf`.
|
||||
*
|
||||
* - list item 1
|
||||
* - list item 2
|
||||
*
|
||||
* Even inline **formatting** in _here_ is possible.
|
||||
* and some [link](https://domain.com/)
|
||||
*/
|
||||
|
||||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
required_providers {
|
||||
aws = ">= 2.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
resource "tls_private_key" "baz" {}
|
||||
|
||||
data "aws_caller_identity" "current" {
|
||||
provider = "aws"
|
||||
}
|
||||
|
||||
resource "null_resource" "foo" {}
|
||||
|
||||
module "foo" {
|
||||
source = "bar"
|
||||
version = "1.2.3"
|
||||
}
|
||||
|
||||
module "foobar" {
|
||||
source = "git@github.com:module/path?ref=v7.8.9"
|
||||
}
|
||||
14
terraform/testdata/with-lock-file/outputs.tf
vendored
Normal file
14
terraform/testdata/with-lock-file/outputs.tf
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
output C {
|
||||
description = "It's unquoted output."
|
||||
value = "c"
|
||||
}
|
||||
|
||||
output "A" {
|
||||
description = "A description"
|
||||
value = "a"
|
||||
}
|
||||
|
||||
// B description
|
||||
output "B" {
|
||||
value = "b"
|
||||
}
|
||||
30
terraform/testdata/with-lock-file/variables.tf
vendored
Normal file
30
terraform/testdata/with-lock-file/variables.tf
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// D description
|
||||
variable "D" {
|
||||
default = "d"
|
||||
}
|
||||
|
||||
variable "B" {
|
||||
default = "b"
|
||||
}
|
||||
|
||||
variable "E" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
# A Description
|
||||
# in multiple lines
|
||||
variable A {}
|
||||
|
||||
variable "C" {
|
||||
description = "C description"
|
||||
default = "c"
|
||||
}
|
||||
|
||||
variable "F" {
|
||||
description = "F description"
|
||||
}
|
||||
|
||||
variable "G" {
|
||||
description = "G description"
|
||||
default = null
|
||||
}
|
||||
Reference in New Issue
Block a user