diff --git a/internal/terraform/input.go b/internal/terraform/input.go index 287566b..5f84873 100644 --- a/internal/terraform/input.go +++ b/internal/terraform/input.go @@ -57,26 +57,6 @@ func (i *Input) HasDefault() bool { return i.Default.HasDefault() || !i.Required } -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 -} - type inputsSortedByName []*Input func (a inputsSortedByName) Len() int { return len(a) } @@ -112,3 +92,23 @@ func (a inputsSortedByType) Less(i, j int) bool { } return a[i].Type < a[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 +} diff --git a/internal/terraform/input_test.go b/internal/terraform/input_test.go index 7336707..e3c2313 100644 --- a/internal/terraform/input_test.go +++ b/internal/terraform/input_test.go @@ -211,52 +211,40 @@ func TestInputValue(t *testing.T) { } } -func TestInputsSortedByName(t *testing.T) { - assert := assert.New(t) +func TestInputsSorted(t *testing.T) { inputs := sampleInputs() - - sort.Sort(inputsSortedByName(inputs)) - - expected := []string{"a", "b", "c", "d", "e", "f"} - actual := make([]string, len(inputs)) - - for k, i := range inputs { - actual[k] = i.Name + tests := map[string]struct { + sortType sort.Interface + expected []string + }{ + "ByName": { + sortType: inputsSortedByName(inputs), + expected: []string{"a", "b", "c", "d", "e", "f"}, + }, + "ByRequired": { + sortType: inputsSortedByRequired(inputs), + expected: []string{"b", "d", "a", "c", "e", "f"}, + }, + "ByPosition": { + sortType: inputsSortedByPosition(inputs), + expected: []string{"a", "d", "e", "b", "c", "f"}, + }, } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) - assert.Equal(expected, actual) -} + sort.Sort(tt.sortType) -func TestInputsSortedByRequired(t *testing.T) { - assert := assert.New(t) - inputs := sampleInputs() + actual := make([]string, len(inputs)) - sort.Sort(inputsSortedByRequired(inputs)) + for k, i := range inputs { + actual[k] = i.Name + } - expected := []string{"b", "d", "a", "c", "e", "f"} - actual := make([]string, len(inputs)) - - for k, i := range inputs { - actual[k] = i.Name + assert.Equal(tt.expected, actual) + }) } - - assert.Equal(expected, actual) -} - -func TestInputsSortedByPosition(t *testing.T) { - assert := assert.New(t) - inputs := sampleInputs() - - sort.Sort(inputsSortedByPosition(inputs)) - - expected := []string{"a", "d", "e", "b", "c", "f"} - actual := make([]string, len(inputs)) - - for k, i := range inputs { - actual[k] = i.Name - } - - assert.Equal(expected, actual) } func sampleInputs() []*Input { diff --git a/internal/terraform/module.go b/internal/terraform/module.go index fedcb71..9095046 100644 --- a/internal/terraform/module.go +++ b/internal/terraform/module.go @@ -118,6 +118,7 @@ func LoadWithOptions(options *Options) (*Module, error) { if err != nil { return nil, err } + module, err := loadModuleItems(tfmodule, options) if err != nil { return nil, err @@ -180,6 +181,7 @@ func getFileFormat(filename string) string { } return filename[last:] } + func isFileFormatSupported(filename string, section string) (bool, error) { if section == "" { return false, errors.New("section is missing") @@ -191,7 +193,7 @@ func isFileFormatSupported(filename string, section string) (bool, error) { 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) + return false, fmt.Errorf("only .adoc, .md, .tf, and .txt formats are supported to read %s from", section) } func loadHeader(options *Options) (string, error) { @@ -282,12 +284,14 @@ func loadInputs(tfmodule *tfconfig.Module) ([]*Input, []*Input, []*Input) { } inputs = append(inputs, i) + if i.HasDefault() { optional = append(optional, i) } else { required = append(required, i) } } + return inputs, required, optional } @@ -369,12 +373,14 @@ func loadOutputValues(options *Options) (map[string]*output, error) { func loadProviders(tfmodule *tfconfig.Module) []*Provider { 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 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, @@ -387,6 +393,7 @@ func loadProviders(tfmodule *tfconfig.Module) []*Provider { } } } + providers := make([]*Provider, 0, len(discovered)) for _, provider := range discovered { providers = append(providers, provider) @@ -402,11 +409,14 @@ func loadRequirements(tfmodule *tfconfig.Module) []*Requirement { 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{ diff --git a/internal/terraform/module_test.go b/internal/terraform/module_test.go index 49aefc9..e0c3f34 100644 --- a/internal/terraform/module_test.go +++ b/internal/terraform/module_test.go @@ -169,7 +169,7 @@ func TestIsFileFormatSupported(t *testing.T) { filename: "main.doc", expected: false, wantErr: true, - errText: "only .adoc, .md, .tf and .txt formats are supported to read header from", + errText: "only .adoc, .md, .tf, and .txt formats are supported to read header from", section: "header", }, { @@ -184,7 +184,7 @@ func TestIsFileFormatSupported(t *testing.T) { filename: "main.doc", expected: false, wantErr: true, - errText: "only .adoc, .md, .tf and .txt formats are supported to read footer from", + errText: "only .adoc, .md, .tf, and .txt formats are supported to read footer from", section: "footer", }, { @@ -398,7 +398,7 @@ func TestLoadSections(t *testing.T) { file: "wrong-formate.docx", expected: "", wantErr: true, - errText: "only .adoc, .md, .tf and .txt formats are supported to read footer from", + errText: "only .adoc, .md, .tf, and .txt formats are supported to read footer from", section: "footer", }, { diff --git a/internal/terraform/output.go b/internal/terraform/output.go index a10c3b9..3b3e2e3 100644 --- a/internal/terraform/output.go +++ b/internal/terraform/output.go @@ -128,6 +128,20 @@ type output struct { Value interface{} `json:"value"` } +type outputsSortedByName []*Output + +func (a outputsSortedByName) Len() int { return len(a) } +func (a outputsSortedByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a outputsSortedByName) Less(i, j int) bool { return a[i].Name < a[j].Name } + +type outputsSortedByPosition []*Output + +func (a outputsSortedByPosition) Len() int { return len(a) } +func (a outputsSortedByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a outputsSortedByPosition) Less(i, j int) bool { + return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line +} + type outputs []*Output func (oo outputs) convert() []*terraformsdk.Output { @@ -147,19 +161,3 @@ func (oo outputs) convert() []*terraformsdk.Output { } return list } - -type outputsSortedByName []*Output - -func (a outputsSortedByName) Len() int { return len(a) } -func (a outputsSortedByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a outputsSortedByName) Less(i, j int) bool { - return a[i].Name < a[j].Name -} - -type outputsSortedByPosition []*Output - -func (a outputsSortedByPosition) Len() int { return len(a) } -func (a outputsSortedByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a outputsSortedByPosition) Less(i, j int) bool { - return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line -} diff --git a/internal/terraform/output_test.go b/internal/terraform/output_test.go index ebcf015..d42f614 100644 --- a/internal/terraform/output_test.go +++ b/internal/terraform/output_test.go @@ -460,36 +460,36 @@ func sampleOutputs() []Output { } } -func TestOutputsSortedByName(t *testing.T) { - assert := assert.New(t) +func TestOutputsSort(t *testing.T) { outputs := sampleOutputsForSort() - - sort.Sort(outputsSortedByName(outputs)) - - expected := []string{"a", "b", "c", "d", "e"} - actual := make([]string, len(outputs)) - - for k, o := range outputs { - actual[k] = o.Name + tests := map[string]struct { + sortType sort.Interface + expected []string + }{ + "ByName": { + sortType: outputsSortedByName(outputs), + expected: []string{"a", "b", "c", "d", "e"}, + }, + "ByPosition": { + sortType: outputsSortedByPosition(outputs), + expected: []string{"d", "a", "e", "b", "c"}, + }, } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) - assert.Equal(expected, actual) -} + sort.Sort(tt.sortType) -func TestOutputsSortedByPosition(t *testing.T) { - assert := assert.New(t) - outputs := sampleOutputsForSort() + actual := make([]string, len(outputs)) - sort.Sort(outputsSortedByPosition(outputs)) + for k, o := range outputs { + actual[k] = o.Name + } - expected := []string{"d", "a", "e", "b", "c"} - actual := make([]string, len(outputs)) - - for k, o := range outputs { - actual[k] = o.Name + assert.Equal(tt.expected, actual) + }) } - - assert.Equal(expected, actual) } func sampleOutputsForSort() []*Output { diff --git a/internal/terraform/position.go b/internal/terraform/position.go index c36cc71..e9c9c73 100644 --- a/internal/terraform/position.go +++ b/internal/terraform/position.go @@ -10,7 +10,7 @@ the root directory of this source tree. package terraform -// Position represents position of Terraform input or output in a file. +// 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:"-"` diff --git a/internal/terraform/provider.go b/internal/terraform/provider.go index a87928d..c411267 100644 --- a/internal/terraform/provider.go +++ b/internal/terraform/provider.go @@ -33,6 +33,22 @@ func (p *Provider) FullName() string { return p.Name } +type providersSortedByName []*Provider + +func (a providersSortedByName) Len() int { return len(a) } +func (a providersSortedByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a providersSortedByName) Less(i, j int) bool { + return a[i].Name < a[j].Name || (a[i].Name == a[j].Name && a[i].Alias < a[j].Alias) +} + +type providersSortedByPosition []*Provider + +func (a providersSortedByPosition) Len() int { return len(a) } +func (a providersSortedByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a providersSortedByPosition) Less(i, j int) bool { + return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line +} + type providers []*Provider func (pp providers) convert() []*terraformsdk.Provider { @@ -50,19 +66,3 @@ func (pp providers) convert() []*terraformsdk.Provider { } return list } - -type providersSortedByName []*Provider - -func (a providersSortedByName) Len() int { return len(a) } -func (a providersSortedByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a providersSortedByName) Less(i, j int) bool { - return a[i].Name < a[j].Name || (a[i].Name == a[j].Name && a[i].Alias < a[j].Alias) -} - -type providersSortedByPosition []*Provider - -func (a providersSortedByPosition) Len() int { return len(a) } -func (a providersSortedByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a providersSortedByPosition) Less(i, j int) bool { - return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line -} diff --git a/internal/terraform/provider_test.go b/internal/terraform/provider_test.go index 2ebd21b..50b60ab 100644 --- a/internal/terraform/provider_test.go +++ b/internal/terraform/provider_test.go @@ -19,58 +19,68 @@ import ( "github.com/terraform-docs/terraform-docs/internal/types" ) -func TestProviderNameWithoutAlias(t *testing.T) { - assert := assert.New(t) - provider := Provider{ - Name: "provider", - Alias: types.String(""), - Version: types.String(">= 1.2.3"), - Position: Position{Filename: "foo.tf", Line: 13}, +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()) + }) } - assert.Equal("provider", provider.FullName()) } -func TestProviderNameWithAlias(t *testing.T) { - assert := assert.New(t) - provider := Provider{ - Name: "provider", - Alias: types.String("alias"), - Version: types.String(">= 1.2.3"), - Position: Position{Filename: "foo.tf", Line: 13}, - } - assert.Equal("provider.alias", provider.FullName()) -} - -func TestProvidersSortedByName(t *testing.T) { - assert := assert.New(t) +func TestProvidersSort(t *testing.T) { providers := sampleProviders() - - sort.Sort(providersSortedByName(providers)) - - expected := []string{"a", "b", "c", "d", "d.a", "e", "e.a"} - actual := make([]string, len(providers)) - - for k, p := range providers { - actual[k] = p.FullName() + tests := map[string]struct { + sortType sort.Interface + expected []string + }{ + "ByName": { + sortType: providersSortedByName(providers), + expected: []string{"a", "b", "c", "d", "d.a", "e", "e.a"}, + }, + "ByPosition": { + sortType: providersSortedByPosition(providers), + 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) - assert.Equal(expected, actual) -} + sort.Sort(tt.sortType) -func TestProvidersSortedByPosition(t *testing.T) { - assert := assert.New(t) - providers := sampleProviders() + actual := make([]string, len(providers)) - sort.Sort(providersSortedByPosition(providers)) + for k, p := range providers { + actual[k] = p.FullName() + } - expected := []string{"e.a", "b", "d", "d.a", "a", "e", "c"} - actual := make([]string, len(providers)) - - for k, p := range providers { - actual[k] = p.FullName() + assert.Equal(tt.expected, actual) + }) } - - assert.Equal(expected, actual) } func sampleProviders() []*Provider { diff --git a/internal/terraform/resource_test.go b/internal/terraform/resource_test.go index f4e6e88..835c586 100644 --- a/internal/terraform/resource_test.go +++ b/internal/terraform/resource_test.go @@ -33,13 +33,11 @@ func TestResourceSpec(t *testing.T) { } func TestResourceMode(t *testing.T) { - tests := []struct { - name string + tests := map[string]struct { resource Resource expectValue string }{ - { - name: "resource mode as managed", + "Managed": { resource: Resource{ Type: "private_key", ProviderName: "tls", @@ -49,8 +47,7 @@ func TestResourceMode(t *testing.T) { }, expectValue: "resource", }, - { - name: "resource mode as data source", + "Data Source": { resource: Resource{ Type: "caller_identity", ProviderName: "aws", @@ -60,8 +57,7 @@ func TestResourceMode(t *testing.T) { }, expectValue: "data source", }, - { - name: "resource mode as invalid", + "Invalid": { resource: Resource{ Type: "caller_identity", ProviderName: "aws", @@ -72,8 +68,8 @@ func TestResourceMode(t *testing.T) { expectValue: "invalid", }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for name, tt := range tests { + t.Run(name, func(t *testing.T) { assert := assert.New(t) assert.Equal(tt.expectValue, tt.resource.GetMode()) @@ -82,13 +78,11 @@ func TestResourceMode(t *testing.T) { } func TestResourceURL(t *testing.T) { - tests := []struct { - name string + tests := map[string]struct { resource Resource expectValue string }{ - { - name: "generic URL construction", + "Generic URL construction": { resource: Resource{ Type: "private_key", ProviderName: "tls", @@ -98,8 +92,7 @@ func TestResourceURL(t *testing.T) { }, expectValue: "https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key", }, - { - name: "unable to construct URL", + "Unable to construct URL": { resource: Resource{ Type: "custom", ProviderName: "nih", @@ -110,8 +103,8 @@ func TestResourceURL(t *testing.T) { expectValue: "", }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for name, tt := range tests { + t.Run(name, func(t *testing.T) { assert := assert.New(t) assert.Equal(tt.expectValue, tt.resource.URL()) @@ -159,64 +152,53 @@ func TestResourcesSortedByTypeAndMode(t *testing.T) { } func TestResourceVersion(t *testing.T) { - tests := []struct { - name string + tests := map[string]struct { constraint []string expected string }{ - { - name: "exact version, without operator", + "exact version, without operator": { constraint: []string{"1.2.3"}, expected: "1.2.3", }, - { - name: "exact version, with operator", + "exact version, with operator": { constraint: []string{"= 1.2.3"}, expected: "1.2.3", }, - { - name: "exact version, with operator, without space", + "exact version, with operator, without space": { constraint: []string{"=1.2.3"}, expected: "1.2.3", }, - { - name: "exclude exact version, with space", + "exclude exact version, with space": { constraint: []string{"!= 1.2.3"}, expected: "latest", }, - { - name: "exclude exact version, without space", + "exclude exact version, without space": { constraint: []string{"!=1.2.3"}, expected: "latest", }, - { - name: "comparison version, with space", + "comparison version, with space": { constraint: []string{"> 1.2.3"}, expected: "latest", }, - { - name: "comparison version, without space", + "comparison version, without space": { constraint: []string{">1.2.3"}, expected: "latest", }, - { - name: "range version", + "range version": { constraint: []string{"> 1.2.3, < 2.0.0"}, expected: "latest", }, - { - name: "pessimistic version, with space", + "pessimistic version, with space": { constraint: []string{"~> 1.2.3"}, expected: "latest", }, - { - name: "pessimistic version, without space", + "pessimistic version, without space": { constraint: []string{"~>1.2.3"}, expected: "latest", }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for name, tt := range tests { + t.Run(name, func(t *testing.T) { assert := assert.New(t) assert.Equal(tt.expected, resourceVersion(tt.constraint))