From 713f451182741e9881021ce2d0037105f811a8f3 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Fri, 1 Mar 2024 17:57:27 -0500 Subject: [PATCH 01/29] Initial validate implementation --- cmd/tfplugindocs/main_test.go | 8 + ...amework_provider_success_legacy_docs.txtar | 162 ++++++ ...ework_provider_success_registry_docs.txtar | 153 ++++++ go.mod | 7 +- go.sum | 2 + internal/check/directory.go | 163 ++++++ internal/check/directory_test.go | 57 +++ internal/check/file.go | 41 ++ internal/check/file_extension.go | 61 +++ internal/check/file_extension_test.go | 50 ++ internal/check/file_mismatch.go | 277 ++++++++++ internal/check/file_mismatch_test.go | 350 +++++++++++++ internal/check/file_test.go | 90 ++++ internal/check/frontmatter.go | 82 +++ internal/check/frontmatter_test.go | 161 ++++++ internal/check/provider_file.go | 80 +++ internal/cmd/validate.go | 19 +- internal/provider/logger.go | 23 + internal/provider/schema.go | 133 +++++ internal/provider/template.go | 4 +- .../templates/template-description.gotmpl | 3 + .../docs/r/invalid/thing.html.markdown | 27 + .../data_source_invalid_extension.txt | 27 + ...a_source_invalid_frontmatter.html.markdown | 27 + ..._source_with_sidebar_current.html.markdown | 28 ++ .../data_source_without_layout.html.markdown | 26 + .../guide_invalid_extension.txt | 11 + .../guide_invalid_frontmatter.html.markdown | 11 + .../guide_with_sidebar_current.html.markdown | 12 + .../guide_without_layout.html.markdown | 10 + .../index_invalid_extension.txt | 10 + .../index_invalid_frontmatter.html.markdown | 10 + .../index_with_sidebar_current.html.markdown | 11 + .../index_with_subcategory.html.markdown | 10 + .../index_without_layout.html.markdown | 9 + .../resource_invalid_extension.txt | 27 + ...resource_invalid_frontmatter.html.markdown | 27 + ...esource_with_sidebar_current.html.markdown | 28 ++ .../resource_without_layout.html.markdown | 26 + .../docs/resources/thing.md | 27 + .../website/docs/r/thing.html.markdown | 0 .../docs/resources/invalid/thing.md | 26 + .../data_source_invalid_extension.markdown | 26 + .../data_source_invalid_frontmatter.md | 26 + .../data_source_with_layout.md | 27 + .../data_source_with_sidebar_current.md | 27 + .../guide_invalid_extension.markdown | 10 + .../guide_invalid_frontmatter.md | 10 + .../guide_with_layout.md | 11 + .../guide_with_sidebar_current.md | 11 + .../index_invalid_extension.markdown | 9 + .../index_invalid_frontmatter.md | 9 + .../index_with_layout.md | 10 + .../index_with_sidebar_current.md | 10 + .../index_with_subcategory.md | 10 + .../resource_invalid_extension.markdown | 26 + .../resource_invalid_frontmatter.md | 26 + .../resource_with_layout.md | 27 + .../resource_with_sidebar_current.md | 27 + .../cdktf/typescript/d/thing.html.markdown | 37 ++ .../cdktf/typescript/r/thing.html.markdown | 37 ++ .../website/docs/d/thing.html.markdown | 27 + .../website/docs/r/thing.html.markdown | 27 + .../website/docs/d/thing.html.markdown | 27 + .../website/docs/r/thing.html.markdown | 27 + .../2.0-guide.html.markdown | 11 + .../data_source.html.markdown | 27 + .../valid-legacy-files/guide.html.markdown | 11 + .../valid-legacy-files/index.html.markdown | 10 + .../valid-legacy-files/resource.html.markdown | 27 + .../docs/CONTRIBUTING.md | 3 + .../docs/nonregistrydocs/valid.md | 3 + .../website/docs/r/thing.html.markdown | 27 + .../docs/CONTRIBUTING.md | 3 + .../docs/cdktf/typescript/CONTRIBUTING.md | 3 + .../cdktf/typescript/data-sources/thing.md | 36 ++ .../docs/cdktf/typescript/index.md | 9 + .../cdktf/typescript/nonregistrydocs/valid.md | 3 + .../docs/cdktf/typescript/resources/thing.md | 36 ++ .../docs/data-sources/thing.md | 26 + .../docs/index.md | 9 + .../docs/nonregistrydocs/valid.md | 3 + .../docs/resources/thing.md | 26 + .../docs/CONTRIBUTING.md | 3 + .../docs/data-sources/thing.md | 26 + .../valid-registry-directories/docs/index.md | 9 + .../docs/nonregistrydocs/valid.md | 3 + .../docs/resources/thing.md | 26 + .../valid-registry-files/2.0-guide.md | 10 + .../valid-registry-files/data_source.md | 26 + .../testdata/valid-registry-files/guide.md | 10 + .../testdata/valid-registry-files/index.md | 9 + .../testdata/valid-registry-files/resource.md | 26 + internal/provider/validate.go | 472 ++++++++++-------- internal/provider/validate_test.go | 132 +++++ 95 files changed, 3608 insertions(+), 217 deletions(-) create mode 100644 cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar create mode 100644 cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar create mode 100644 internal/check/directory.go create mode 100644 internal/check/directory_test.go create mode 100644 internal/check/file.go create mode 100644 internal/check/file_extension.go create mode 100644 internal/check/file_extension_test.go create mode 100644 internal/check/file_mismatch.go create mode 100644 internal/check/file_mismatch_test.go create mode 100644 internal/check/file_test.go create mode 100644 internal/check/frontmatter.go create mode 100644 internal/check/frontmatter_test.go create mode 100644 internal/check/provider_file.go create mode 100644 internal/provider/logger.go create mode 100644 internal/provider/schema.go create mode 100644 internal/provider/templates/template-description.gotmpl create mode 100644 internal/provider/testdata/invalid-legacy-directories/website/docs/r/invalid/thing.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/data_source_invalid_extension.txt create mode 100644 internal/provider/testdata/invalid-legacy-files/data_source_invalid_frontmatter.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/data_source_with_sidebar_current.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/data_source_without_layout.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/guide_invalid_extension.txt create mode 100644 internal/provider/testdata/invalid-legacy-files/guide_invalid_frontmatter.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/guide_with_sidebar_current.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/guide_without_layout.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/index_invalid_extension.txt create mode 100644 internal/provider/testdata/invalid-legacy-files/index_invalid_frontmatter.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/index_with_sidebar_current.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/index_with_subcategory.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/index_without_layout.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/resource_invalid_extension.txt create mode 100644 internal/provider/testdata/invalid-legacy-files/resource_invalid_frontmatter.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/resource_with_sidebar_current.html.markdown create mode 100644 internal/provider/testdata/invalid-legacy-files/resource_without_layout.html.markdown create mode 100644 internal/provider/testdata/invalid-mixed-directories/docs/resources/thing.md create mode 100644 internal/provider/testdata/invalid-mixed-directories/website/docs/r/thing.html.markdown create mode 100644 internal/provider/testdata/invalid-registry-directories/docs/resources/invalid/thing.md create mode 100644 internal/provider/testdata/invalid-registry-files/data_source_invalid_extension.markdown create mode 100644 internal/provider/testdata/invalid-registry-files/data_source_invalid_frontmatter.md create mode 100644 internal/provider/testdata/invalid-registry-files/data_source_with_layout.md create mode 100644 internal/provider/testdata/invalid-registry-files/data_source_with_sidebar_current.md create mode 100644 internal/provider/testdata/invalid-registry-files/guide_invalid_extension.markdown create mode 100644 internal/provider/testdata/invalid-registry-files/guide_invalid_frontmatter.md create mode 100644 internal/provider/testdata/invalid-registry-files/guide_with_layout.md create mode 100644 internal/provider/testdata/invalid-registry-files/guide_with_sidebar_current.md create mode 100644 internal/provider/testdata/invalid-registry-files/index_invalid_extension.markdown create mode 100644 internal/provider/testdata/invalid-registry-files/index_invalid_frontmatter.md create mode 100644 internal/provider/testdata/invalid-registry-files/index_with_layout.md create mode 100644 internal/provider/testdata/invalid-registry-files/index_with_sidebar_current.md create mode 100644 internal/provider/testdata/invalid-registry-files/index_with_subcategory.md create mode 100644 internal/provider/testdata/invalid-registry-files/resource_invalid_extension.markdown create mode 100644 internal/provider/testdata/invalid-registry-files/resource_invalid_frontmatter.md create mode 100644 internal/provider/testdata/invalid-registry-files/resource_with_layout.md create mode 100644 internal/provider/testdata/invalid-registry-files/resource_with_sidebar_current.md create mode 100644 internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/cdktf/typescript/d/thing.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/cdktf/typescript/r/thing.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/d/thing.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/r/thing.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-directories/website/docs/d/thing.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-directories/website/docs/r/thing.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-files/2.0-guide.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-files/data_source.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-files/guide.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-files/index.html.markdown create mode 100644 internal/provider/testdata/valid-legacy-files/resource.html.markdown create mode 100644 internal/provider/testdata/valid-mixed-directories/docs/CONTRIBUTING.md create mode 100644 internal/provider/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md create mode 100644 internal/provider/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/CONTRIBUTING.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/CONTRIBUTING.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/data-sources/thing.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/index.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/nonregistrydocs/valid.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/resources/thing.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/data-sources/thing.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/index.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/nonregistrydocs/valid.md create mode 100644 internal/provider/testdata/valid-registry-directories-with-cdktf/docs/resources/thing.md create mode 100644 internal/provider/testdata/valid-registry-directories/docs/CONTRIBUTING.md create mode 100644 internal/provider/testdata/valid-registry-directories/docs/data-sources/thing.md create mode 100644 internal/provider/testdata/valid-registry-directories/docs/index.md create mode 100644 internal/provider/testdata/valid-registry-directories/docs/nonregistrydocs/valid.md create mode 100644 internal/provider/testdata/valid-registry-directories/docs/resources/thing.md create mode 100644 internal/provider/testdata/valid-registry-files/2.0-guide.md create mode 100644 internal/provider/testdata/valid-registry-files/data_source.md create mode 100644 internal/provider/testdata/valid-registry-files/guide.md create mode 100644 internal/provider/testdata/valid-registry-files/index.md create mode 100644 internal/provider/testdata/valid-registry-files/resource.md create mode 100644 internal/provider/validate_test.go diff --git a/cmd/tfplugindocs/main_test.go b/cmd/tfplugindocs/main_test.go index 703dd4cb..f3f2b27c 100644 --- a/cmd/tfplugindocs/main_test.go +++ b/cmd/tfplugindocs/main_test.go @@ -54,3 +54,11 @@ func Test_SchemaJson_MigrateAcceptanceTests(t *testing.T) { Dir: "testdata/scripts/schema-json/migrate", }) } + +func Test_SchemaJson_ValidateAcceptanceTests(t *testing.T) { + t.Parallel() + + testscript.Run(t, testscript.Params{ + Dir: "testdata/scripts/schema-json/validate", + }) +} diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar new file mode 100644 index 00000000..5ece1598 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar @@ -0,0 +1,162 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs validate command on a Framework provider with docs in the legacy directory structure (i.e. r/.md.tmpl) +[!unix] skip +exec tfplugindocs validate --provider-name=terraform-provider-scaffolding --providers-schema=schema.json +cmp stdout expected-output.txt + +-- expected-output.txt -- +exporting schema from JSON file +getting provider schema +detected legacy website directory, running checks +running invalid directories check on website/docs/d +running file checks on website/docs/d/example.html.md +running invalid directories check on website/docs/functions +running file checks on website/docs/functions/example.html.md +running file checks on website/docs/index.html.md +running invalid directories check on website/docs/r +running file checks on website/docs/r/example.html.md +running file mismatch check +-- website/docs/r/example.html.md -- +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- website/docs/d/example.html.md -- +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- website/docs/functions/example.html.md -- +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- website/docs/index.html.md -- +--- +layout: "example" +page_title: "Example Provider" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- schema.json -- +{ + "format_version": "1.0", + "provider_schemas": { + "registry.terraform.io/hashicorp/scaffolding": { + "provider": { + "version": 0, + "block": { + "attributes": { + "endpoint": { + "type": "string", + "description": "Example provider attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description": "Example provider", + "description_kind": "markdown" + } + }, + "resource_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "defaulted": { + "type": "string", + "description": "Example configurable attribute with default value", + "description_kind": "markdown", + "optional": true, + "computed": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example resource", + "description_kind": "markdown" + } + } + }, + "data_source_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example data source", + "description_kind": "markdown" + } + } + }, + "functions": { + "example": { + "description": "Given a string value, returns the same value.", + "summary": "Echo a string", + "return_type": "string", + "parameters": [ + { + "name": "input", + "description": "Value to echo.", + "type": "string" + } + ], + "variadic_parameter": { + "name": "variadicInput", + "description": "Variadic input to echo.", + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar new file mode 100644 index 00000000..f64988b0 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar @@ -0,0 +1,153 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs validate command on a Framework provider with docs in the registry directory structure (i.e. resource/.md.tmpl) +[!unix] skip +exec tfplugindocs validate --provider-name=terraform-provider-scaffolding --providers-schema=schema.json +cmpenv stdout expected-output.txt + +-- expected-output.txt -- +exporting schema from JSON file +getting provider schema +detected static docs directory, running checks +running file checks on docs/data-sources/example.md +running file checks on docs/resources/example.md +running file mismatch check +-- docs/resources/example.md -- +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- docs/data-sources/example.md -- +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- docs/functions/example.md -- +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- docs/index.md -- +--- +page_title: "Example Provider" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- schema.json -- +{ + "format_version": "1.0", + "provider_schemas": { + "registry.terraform.io/hashicorp/scaffolding": { + "provider": { + "version": 0, + "block": { + "attributes": { + "endpoint": { + "type": "string", + "description": "Example provider attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description": "Example provider", + "description_kind": "markdown" + } + }, + "resource_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "defaulted": { + "type": "string", + "description": "Example configurable attribute with default value", + "description_kind": "markdown", + "optional": true, + "computed": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example resource", + "description_kind": "markdown" + } + } + }, + "data_source_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example data source", + "description_kind": "markdown" + } + } + }, + "functions": { + "example": { + "description": "Given a string value, returns the same value.", + "summary": "Echo a string", + "return_type": "string", + "parameters": [ + { + "name": "input", + "description": "Value to echo.", + "type": "string" + } + ], + "variadic_parameter": { + "name": "variadicInput", + "description": "Variadic input to echo.", + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 50a01989..feea3d90 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,13 @@ module github.com/hashicorp/terraform-plugin-docs -go 1.19 +go 1.20 require ( github.com/Kunde21/markdownfmt/v3 v3.1.0 + github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/google/go-cmp v0.6.0 github.com/hashicorp/cli v1.1.6 + github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hc-install v0.6.2 github.com/hashicorp/terraform-exec v0.20.0 @@ -17,6 +19,7 @@ require ( github.com/zclconf/go-cty v1.14.2 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df golang.org/x/text v0.14.0 + gopkg.in/yaml.v2 v2.3.0 ) require ( @@ -33,7 +36,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.15 // indirect @@ -48,5 +50,4 @@ require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/tools v0.13.0 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index dac50614..f1494452 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= diff --git a/internal/check/directory.go b/internal/check/directory.go new file mode 100644 index 00000000..fcbb3719 --- /dev/null +++ b/internal/check/directory.go @@ -0,0 +1,163 @@ +package check + +import ( + "fmt" + "log" +) + +const ( + CdktfIndexDirectory = `cdktf` + + LegacyIndexDirectory = `website/docs` + LegacyDataSourcesDirectory = `d` + LegacyGuidesDirectory = `guides` + LegacyResourcesDirectory = `r` + LegacyFunctionsDirectory = `functions` + + RegistryIndexDirectory = `docs` + RegistryDataSourcesDirectory = `data-sources` + RegistryGuidesDirectory = `guides` + RegistryResourcesDirectory = `resources` + RegistryFunctionsDirectory = `functions` + + // Terraform Registry Storage Limits + // https://www.terraform.io/docs/registry/providers/docs.html#storage-limits + RegistryMaximumNumberOfFiles = 2000 + RegistryMaximumSizeOfFile = 500000 // 500KB + +) + +var ValidLegacyDirectories = []string{ + LegacyIndexDirectory, + LegacyIndexDirectory + "/" + LegacyDataSourcesDirectory, + LegacyIndexDirectory + "/" + LegacyGuidesDirectory, + LegacyIndexDirectory + "/" + LegacyResourcesDirectory, + LegacyIndexDirectory + "/" + LegacyFunctionsDirectory, +} + +var ValidRegistryDirectories = []string{ + RegistryIndexDirectory, + RegistryIndexDirectory + "/" + RegistryDataSourcesDirectory, + RegistryIndexDirectory + "/" + RegistryGuidesDirectory, + RegistryIndexDirectory + "/" + RegistryResourcesDirectory, + RegistryIndexDirectory + "/" + RegistryFunctionsDirectory, +} + +var ValidCdktfLanguages = []string{ + "csharp", + "go", + "java", + "python", + "typescript", +} + +var ValidLegacySubdirectories = []string{ + LegacyIndexDirectory, + LegacyDataSourcesDirectory, + LegacyGuidesDirectory, + LegacyResourcesDirectory, +} + +var ValidRegistrySubdirectories = []string{ + RegistryIndexDirectory, + RegistryDataSourcesDirectory, + RegistryGuidesDirectory, + RegistryResourcesDirectory, +} + +func InvalidDirectoriesCheck(dirPath string) error { + if IsValidRegistryDirectory(dirPath) { + return nil + } + + if IsValidLegacyDirectory(dirPath) { + return nil + } + + if IsValidCdktfDirectory(dirPath) { + return nil + } + + return fmt.Errorf("invalid Terraform Provider documentation directory found: %s", dirPath) + +} + +// NumberOfFilesCheck verifies that documentation is below the Terraform Registry storage limit. +// This check presumes that all provided directories are valid, e.g. that directory checking +// for invalid or mixed directory structures was previously completed. +func NumberOfFilesCheck(directories map[string][]string) error { + var numberOfFiles int + + for directory, files := range directories { + // Ignore CDKTF files. The file limit is per-language and presumably there is one CDKTF file per source HCL file. + if IsValidCdktfDirectory(directory) { + continue + } + + directoryNumberOfFiles := len(files) + log.Printf("[TRACE] Found %d documentation files in directory: %s", directoryNumberOfFiles, directory) + numberOfFiles = numberOfFiles + directoryNumberOfFiles + } + + log.Printf("[DEBUG] Found %d documentation files with limit of %d", numberOfFiles, RegistryMaximumNumberOfFiles) + if numberOfFiles >= RegistryMaximumNumberOfFiles { + return fmt.Errorf("exceeded maximum (%d) number of documentation files for Terraform Registry: %d", RegistryMaximumNumberOfFiles, numberOfFiles) + } + + return nil +} + +func IsValidLegacyDirectory(directory string) bool { + for _, validLegacyDirectory := range ValidLegacyDirectories { + if directory == validLegacyDirectory { + return true + } + } + + return false +} + +func IsValidRegistryDirectory(directory string) bool { + for _, validRegistryDirectory := range ValidRegistryDirectories { + if directory == validRegistryDirectory { + return true + } + } + + return false +} + +func IsValidCdktfDirectory(directory string) bool { + if directory == fmt.Sprintf("%s/%s", LegacyIndexDirectory, CdktfIndexDirectory) { + return true + } + + if directory == fmt.Sprintf("%s/%s", RegistryIndexDirectory, CdktfIndexDirectory) { + return true + } + + for _, validCdktfLanguage := range ValidCdktfLanguages { + + if directory == fmt.Sprintf("%s/%s/%s", LegacyIndexDirectory, CdktfIndexDirectory, validCdktfLanguage) { + return true + } + + if directory == fmt.Sprintf("%s/%s/%s", RegistryIndexDirectory, CdktfIndexDirectory, validCdktfLanguage) { + return true + } + + for _, validLegacySubdirectory := range ValidLegacySubdirectories { + if directory == fmt.Sprintf("%s/%s/%s/%s", LegacyIndexDirectory, CdktfIndexDirectory, validCdktfLanguage, validLegacySubdirectory) { + return true + } + } + + for _, validRegistrySubdirectory := range ValidRegistrySubdirectories { + if directory == fmt.Sprintf("%s/%s/%s/%s", RegistryIndexDirectory, CdktfIndexDirectory, validCdktfLanguage, validRegistrySubdirectory) { + return true + } + } + } + + return false +} diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go new file mode 100644 index 00000000..c6cc9cfc --- /dev/null +++ b/internal/check/directory_test.go @@ -0,0 +1,57 @@ +package check + +import ( + "fmt" + "testing" +) + +func TestNumberOfFilesCheck(t *testing.T) { + testCases := []struct { + Name string + Directories map[string][]string + ExpectError bool + }{ + { + Name: "under limit", + Directories: testGenerateDirectories(RegistryMaximumNumberOfFiles - 1), + }, + { + Name: "at limit", + Directories: testGenerateDirectories(RegistryMaximumNumberOfFiles), + ExpectError: true, + }, + { + Name: "over limit", + Directories: testGenerateDirectories(RegistryMaximumNumberOfFiles + 1), + ExpectError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := NumberOfFilesCheck(testCase.Directories) + + if got == nil && testCase.ExpectError { + t.Errorf("expected error, got no error") + } + + if got != nil && !testCase.ExpectError { + t.Errorf("expected no error, got error: %s", got) + } + }) + } +} + +func testGenerateDirectories(numberOfFiles int) map[string][]string { + files := make([]string, numberOfFiles) + + for i := 0; i < numberOfFiles; i++ { + files[i] = fmt.Sprintf("thing%d.md", i) + } + + directories := map[string][]string{ + "docs/resources": files, + } + + return directories +} diff --git a/internal/check/file.go b/internal/check/file.go new file mode 100644 index 00000000..bfc06111 --- /dev/null +++ b/internal/check/file.go @@ -0,0 +1,41 @@ +package check + +import ( + "fmt" + "log" + "os" + "path/filepath" +) + +type FileCheck interface { + Run(string) error + RunAll([]string) error +} + +type FileOptions struct { + BasePath string +} + +func (opts *FileOptions) FullPath(path string) string { + if opts.BasePath != "" { + return filepath.Join(opts.BasePath, path) + } + + return path +} + +// FileSizeCheck verifies that documentation file is below the Terraform Registry storage limit. +func FileSizeCheck(fullpath string) error { + fi, err := os.Stat(fullpath) + + if err != nil { + return err + } + + log.Printf("[DEBUG] File %s size: %d (limit: %d)", fullpath, fi.Size(), RegistryMaximumSizeOfFile) + if fi.Size() >= int64(RegistryMaximumSizeOfFile) { + return fmt.Errorf("exceeded maximum (%d) size of documentation file for Terraform Registry: %d", RegistryMaximumSizeOfFile, fi.Size()) + } + + return nil +} diff --git a/internal/check/file_extension.go b/internal/check/file_extension.go new file mode 100644 index 00000000..1eea3db0 --- /dev/null +++ b/internal/check/file_extension.go @@ -0,0 +1,61 @@ +package check + +import ( + "fmt" + "path/filepath" + "strings" +) + +const ( + FileExtensionHtmlMarkdown = `.html.markdown` + FileExtensionHtmlMd = `.html.md` + FileExtensionMarkdown = `.markdown` + FileExtensionMd = `.md` +) + +var ValidLegacyFileExtensions = []string{ + FileExtensionHtmlMarkdown, + FileExtensionHtmlMd, + FileExtensionMarkdown, + FileExtensionMd, +} + +var ValidRegistryFileExtensions = []string{ + FileExtensionMd, +} + +// FileExtensionCheck checks if the file extension of the given path is valid. +func FileExtensionCheck(path string, validExtensions []string) error { + if !FilePathEndsWithExtensionFrom(path, validExtensions) { + return fmt.Errorf("file does not end with a valid extension, valid extensions: %v", ValidLegacyFileExtensions) + } + + return nil +} + +func FilePathEndsWithExtensionFrom(path string, validExtensions []string) bool { + for _, validExtension := range validExtensions { + if strings.HasSuffix(path, validExtension) { + return true + } + } + + return false +} + +// TrimFileExtension removes file extensions including those with multiple periods. +func TrimFileExtension(path string) string { + filename := filepath.Base(path) + + if filename == "." { + return "" + } + + dotIndex := strings.IndexByte(filename, '.') + + if dotIndex > 0 { + return filename[:dotIndex] + } + + return filename +} diff --git a/internal/check/file_extension_test.go b/internal/check/file_extension_test.go new file mode 100644 index 00000000..7857d35a --- /dev/null +++ b/internal/check/file_extension_test.go @@ -0,0 +1,50 @@ +package check + +import ( + "testing" +) + +func TestTrimFileExtension(t *testing.T) { + testCases := []struct { + Name string + Path string + Expect string + }{ + { + Name: "empty path", + Path: "", + Expect: "", + }, + { + Name: "filename with single extension", + Path: "file.md", + Expect: "file", + }, + { + Name: "filename with multiple extensions", + Path: "file.html.markdown", + Expect: "file", + }, + { + Name: "full path with single extensions", + Path: "docs/resource/thing.md", + Expect: "thing", + }, + { + Name: "full path with multiple extensions", + Path: "website/docs/r/thing.html.markdown", + Expect: "thing", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := TrimFileExtension(testCase.Path) + want := testCase.Expect + + if got != want { + t.Errorf("expected %s, got %s", want, got) + } + }) + } +} diff --git a/internal/check/file_mismatch.go b/internal/check/file_mismatch.go new file mode 100644 index 00000000..d84a4d60 --- /dev/null +++ b/internal/check/file_mismatch.go @@ -0,0 +1,277 @@ +package check + +import ( + "errors" + "fmt" + "log" + "os" + "sort" + + tfjson "github.com/hashicorp/terraform-json" +) + +type FileMismatchOptions struct { + *FileOptions + + IgnoreFileMismatch []string + + IgnoreFileMissing []string + + ProviderShortName string + + DatasourceEntries []os.DirEntry + + ResourceEntries []os.DirEntry + + FunctionEntries []os.DirEntry + + Schema *tfjson.ProviderSchema +} + +type FileMismatchCheck struct { + Options *FileMismatchOptions +} + +func NewFileMismatchCheck(opts *FileMismatchOptions) *FileMismatchCheck { + check := &FileMismatchCheck{ + Options: opts, + } + + if check.Options == nil { + check.Options = &FileMismatchOptions{} + } + + if check.Options.FileOptions == nil { + check.Options.FileOptions = &FileOptions{} + } + + return check +} + +func (check *FileMismatchCheck) Run() error { + var result error + + if check.Options.Schema == nil { + log.Printf("[DEBUG] Skipping file mismatch checks due to missing provider schema") + return nil + } + + if check.Options.ResourceEntries != nil { + err := check.ResourceFileMismatchCheck(check.Options.ResourceEntries, "resource", check.Options.Schema.ResourceSchemas) + result = errors.Join(result, err) + } + + if check.Options.DatasourceEntries != nil { + err := check.ResourceFileMismatchCheck(check.Options.DatasourceEntries, "datasource", check.Options.Schema.DataSourceSchemas) + result = errors.Join(result, err) + } + + if check.Options.FunctionEntries != nil { + err := check.FunctionFileMismatchCheck(check.Options.FunctionEntries, check.Options.Schema.Functions) + result = errors.Join(result, err) + } + + return result +} + +// ResourceFileMismatchCheck checks for mismatched files, either missing or extraneous, against the resource/datasouce schema +func (check *FileMismatchCheck) ResourceFileMismatchCheck(files []os.DirEntry, resourceType string, schemas map[string]*tfjson.Schema) error { + if len(files) == 0 { + log.Printf("[DEBUG] Skipping %s file mismatch checks due to missing file list", resourceType) + return nil + } + + if len(schemas) == 0 { + log.Printf("[DEBUG] Skipping %s file mismatch checks due to missing schemas", resourceType) + return nil + } + + var extraFiles []string + var missingFiles []string + + for _, file := range files { + if fileHasResource(schemas, check.Options.ProviderShortName, file.Name()) { + continue + } + + if check.IgnoreFileMismatch(file.Name()) { + continue + } + + extraFiles = append(extraFiles, file.Name()) + } + + for _, resourceName := range resourceNames(schemas) { + if resourceHasFile(files, check.Options.ProviderShortName, resourceName) { + continue + } + + if check.IgnoreFileMissing(resourceName) { + continue + } + + missingFiles = append(missingFiles, resourceName) + } + + var result error + + for _, extraFile := range extraFiles { + err := fmt.Errorf("matching %s for documentation file (%s) not found, file is extraneous or incorrectly named", resourceType, extraFile) + result = errors.Join(result, err) + } + + for _, missingFile := range missingFiles { + err := fmt.Errorf("missing documentation file for %s: %s", resourceType, missingFile) + result = errors.Join(result, err) + } + + return result + +} + +// FunctionFileMismatchCheck checks for mismatched files, either missing or extraneous, against the function signature +func (check *FileMismatchCheck) FunctionFileMismatchCheck(files []os.DirEntry, functions map[string]*tfjson.FunctionSignature) error { + if len(files) == 0 { + log.Printf("[DEBUG] Skipping function file mismatch checks due to missing file list") + return nil + } + + if len(functions) == 0 { + log.Printf("[DEBUG] Skipping function file mismatch checks due to missing schemas") + return nil + } + + var extraFiles []string + var missingFiles []string + + for _, file := range files { + if fileHasFunction(functions, file.Name()) { + continue + } + + if check.IgnoreFileMismatch(file.Name()) { + continue + } + + extraFiles = append(extraFiles, file.Name()) + } + + for _, functionName := range functionNames(functions) { + if functionHasFile(files, functionName) { + continue + } + + if check.IgnoreFileMissing(functionName) { + continue + } + + missingFiles = append(missingFiles, functionName) + } + + var result error + + for _, extraFile := range extraFiles { + err := fmt.Errorf("matching function for documentation file (%s) not found, file is extraneous or incorrectly named", extraFile) + result = errors.Join(result, err) + } + + for _, missingFile := range missingFiles { + err := fmt.Errorf("missing documentation file for function: %s", missingFile) + result = errors.Join(result, err) + } + + return result + +} + +func (check *FileMismatchCheck) IgnoreFileMismatch(file string) bool { + for _, ignoreResourceName := range check.Options.IgnoreFileMismatch { + if ignoreResourceName == fileResourceName(check.Options.ProviderShortName, file) { + return true + } + } + + return false +} + +func (check *FileMismatchCheck) IgnoreFileMissing(resourceName string) bool { + for _, ignoreResourceName := range check.Options.IgnoreFileMissing { + if ignoreResourceName == resourceName { + return true + } + } + + return false +} + +func fileHasResource(schemaResources map[string]*tfjson.Schema, providerName, file string) bool { + if _, ok := schemaResources[fileResourceName(providerName, file)]; ok { + return true + } + + return false +} + +func fileHasFunction(functions map[string]*tfjson.FunctionSignature, file string) bool { + if _, ok := functions[TrimFileExtension(file)]; ok { + return true + } + + return false +} + +func fileResourceName(providerName, fileName string) string { + resourceSuffix := TrimFileExtension(fileName) + + return fmt.Sprintf("%s_%s", providerName, resourceSuffix) +} + +func resourceHasFile(files []os.DirEntry, providerName, resourceName string) bool { + var found bool + + for _, file := range files { + if fileResourceName(providerName, file.Name()) == resourceName { + found = true + break + } + } + + return found +} + +func functionHasFile(files []os.DirEntry, functionName string) bool { + var found bool + + for _, file := range files { + if TrimFileExtension(file.Name()) == functionName { + found = true + break + } + } + + return found +} + +func resourceNames(resources map[string]*tfjson.Schema) []string { + names := make([]string, 0, len(resources)) + + for name := range resources { + names = append(names, name) + } + + sort.Strings(names) + + return names +} + +func functionNames(functions map[string]*tfjson.FunctionSignature) []string { + names := make([]string, 0, len(functions)) + + for name := range functions { + names = append(names, name) + } + + sort.Strings(names) + + return names +} diff --git a/internal/check/file_mismatch_test.go b/internal/check/file_mismatch_test.go new file mode 100644 index 00000000..82cd4154 --- /dev/null +++ b/internal/check/file_mismatch_test.go @@ -0,0 +1,350 @@ +package check + +import ( + "reflect" + "testing" + "testing/fstest" + + tfjson "github.com/hashicorp/terraform-json" +) + +func TestFileHasResource(t *testing.T) { + testCases := []struct { + Name string + File string + Resources map[string]*tfjson.Schema + Expect bool + }{ + { + Name: "found", + File: "resource1.md", + Resources: map[string]*tfjson.Schema{ + "test_resource1": {}, + "test_resource2": {}, + }, + Expect: true, + }, + { + Name: "not found", + File: "resource1.md", + Resources: map[string]*tfjson.Schema{ + "test_resource2": {}, + "test_resource3": {}, + }, + Expect: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := fileHasResource(testCase.Resources, "test", testCase.File) + want := testCase.Expect + + if got != want { + t.Errorf("expected %t, got %t", want, got) + } + }) + } +} + +func TestFileResourceName(t *testing.T) { + testCases := []struct { + Name string + File string + Expect string + }{ + { + Name: "filename with single extension", + File: "file.md", + Expect: "test_file", + }, + { + Name: "filename with multiple extensions", + File: "file.html.markdown", + Expect: "test_file", + }, + { + Name: "full path with single extensions", + File: "docs/resource/thing.md", + Expect: "test_thing", + }, + { + Name: "full path with multiple extensions", + File: "website/docs/r/thing.html.markdown", + Expect: "test_thing", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := fileResourceName("test", testCase.File) + want := testCase.Expect + + if got != want { + t.Errorf("expected %s, got %s", want, got) + } + }) + } +} + +func TestFileMismatchCheck(t *testing.T) { + testCases := []struct { + Name string + ResourceFiles fstest.MapFS + FunctionFiles fstest.MapFS + Options *FileMismatchOptions + ExpectError bool + }{ + { + Name: "all found", + ResourceFiles: fstest.MapFS{ + "resource1.md": {}, + "resource2.md": {}, + }, + FunctionFiles: fstest.MapFS{ + "function1.md": {}, + "function2.md": {}, + }, + Options: &FileMismatchOptions{ + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + ResourceSchemas: map[string]*tfjson.Schema{ + "test_resource1": {}, + "test_resource2": {}, + }, + Functions: map[string]*tfjson.FunctionSignature{ + "function1": {}, + "function2": {}, + }, + }, + }, + }, + { + Name: "extra file", + ResourceFiles: fstest.MapFS{ + "resource1.md": {}, + "resource2.md": {}, + "resource3.md": {}, + }, + Options: &FileMismatchOptions{ + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + ResourceSchemas: map[string]*tfjson.Schema{ + "test_resource1": {}, + "test_resource2": {}, + }, + }, + }, + ExpectError: true, + }, + { + Name: "ignore extra file", + ResourceFiles: fstest.MapFS{ + "resource1.md": {}, + "resource2.md": {}, + "resource3.md": {}, + }, + Options: &FileMismatchOptions{ + IgnoreFileMismatch: []string{"test_resource3"}, + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + ResourceSchemas: map[string]*tfjson.Schema{ + "test_resource1": {}, + "test_resource2": {}, + }, + }, + }, + }, + { + Name: "missing file", + ResourceFiles: fstest.MapFS{ + "resource1.md": {}, + }, + Options: &FileMismatchOptions{ + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + ResourceSchemas: map[string]*tfjson.Schema{ + "test_resource1": {}, + "test_resource2": {}, + }, + }, + }, + ExpectError: true, + }, + { + Name: "ignore missing file", + ResourceFiles: fstest.MapFS{ + "resource1.md": {}, + }, + Options: &FileMismatchOptions{ + IgnoreFileMissing: []string{"test_resource2"}, + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + ResourceSchemas: map[string]*tfjson.Schema{ + "test_resource1": {}, + "test_resource2": {}, + }, + }, + }, + }, + { + Name: "no files", + Options: &FileMismatchOptions{ + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + ResourceSchemas: map[string]*tfjson.Schema{ + "test_resource1": {}, + "test_resource2": {}, + }, + }, + }, + }, + { + Name: "no schemas", + ResourceFiles: fstest.MapFS{ + "resource1.md": {}, + }, + Options: &FileMismatchOptions{ + ProviderShortName: "test", + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + resourceFiles, _ := testCase.ResourceFiles.ReadDir(".") + functionFiles, _ := testCase.FunctionFiles.ReadDir(".") + testCase.Options.ResourceEntries = resourceFiles + testCase.Options.FunctionEntries = functionFiles + got := NewFileMismatchCheck(testCase.Options).Run() + + if got == nil && testCase.ExpectError { + t.Errorf("expected error, got no error") + } + + if got != nil && !testCase.ExpectError { + t.Errorf("expected no error, got error: %s", got) + } + }) + } +} + +func TestResourceHasFile(t *testing.T) { + testCases := []struct { + Name string + FS fstest.MapFS + ResourceName string + Expect bool + }{ + { + Name: "found", + FS: fstest.MapFS{ + "resource1.md": {}, + "resource2.md": {}, + }, + ResourceName: "test_resource1", + Expect: true, + }, + { + Name: "not found", + FS: fstest.MapFS{ + "resource1.md": {}, + "resource2.md": {}, + }, + ResourceName: "test_resource3", + Expect: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + files, _ := testCase.FS.ReadDir(".") + + got := resourceHasFile(files, "test", testCase.ResourceName) + want := testCase.Expect + + if got != want { + t.Errorf("expected %t, got %t", want, got) + } + }) + } +} + +func TestFunctionHasFile(t *testing.T) { + testCases := []struct { + Name string + FS fstest.MapFS + FunctionName string + Expect bool + }{ + { + Name: "found", + FS: fstest.MapFS{ + "function1.md": {}, + "function2.md": {}, + }, + FunctionName: "function1", + Expect: true, + }, + { + Name: "not found", + FS: fstest.MapFS{ + "function1.md": {}, + "function2.md": {}, + }, + FunctionName: "function3", + Expect: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + files, _ := testCase.FS.ReadDir(".") + + got := functionHasFile(files, testCase.FunctionName) + want := testCase.Expect + + if got != want { + t.Errorf("expected %t, got %t", want, got) + } + }) + } +} + +//TODO: add functionMismatchCheck test + +func TestResourceNames(t *testing.T) { + testCases := []struct { + Name string + Resources map[string]*tfjson.Schema + Expect []string + }{ + { + Name: "empty", + Resources: map[string]*tfjson.Schema{}, + Expect: []string{}, + }, + { + Name: "multiple", + Resources: map[string]*tfjson.Schema{ + "test_resource1": {}, + "test_resource2": {}, + }, + Expect: []string{ + "test_resource1", + "test_resource2", + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := resourceNames(testCase.Resources) + want := testCase.Expect + + if !reflect.DeepEqual(got, want) { + t.Errorf("expected %v, got %v", want, got) + } + }) + } +} diff --git a/internal/check/file_test.go b/internal/check/file_test.go new file mode 100644 index 00000000..6aa629ae --- /dev/null +++ b/internal/check/file_test.go @@ -0,0 +1,90 @@ +package check + +import ( + "os" + "testing" +) + +func TestFileSizeCheck(t *testing.T) { + testCases := []struct { + Name string + Size int64 + ExpectError bool + }{ + { + Name: "under limit", + Size: RegistryMaximumSizeOfFile - 1, + }, + { + Name: "on limit", + Size: RegistryMaximumSizeOfFile, + ExpectError: true, + }, + { + Name: "over limit", + Size: RegistryMaximumSizeOfFile + 1, + ExpectError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + file, err := os.CreateTemp(os.TempDir(), "TestFileSizeCheck") + + if err != nil { + t.Fatalf("error creating temporary file: %s", err) + } + + defer os.Remove(file.Name()) + + if err := file.Truncate(testCase.Size); err != nil { + t.Fatalf("error writing temporary file: %s", err) + } + + got := FileSizeCheck(file.Name()) + + if got == nil && testCase.ExpectError { + t.Errorf("expected error, got no error") + } + + if got != nil && !testCase.ExpectError { + t.Errorf("expected no error, got error: %s", got) + } + }) + } +} + +func TestFullPath(t *testing.T) { + testCases := []struct { + Name string + FileOptions *FileOptions + Path string + Expect string + }{ + { + Name: "without base path", + FileOptions: &FileOptions{}, + Path: "docs/resources/thing.md", + Expect: "docs/resources/thing.md", + }, + { + Name: "without base path", + FileOptions: &FileOptions{ + BasePath: "/full/path/to", + }, + Path: "docs/resources/thing.md", + Expect: "/full/path/to/docs/resources/thing.md", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := testCase.FileOptions.FullPath(testCase.Path) + want := testCase.Expect + + if got != want { + t.Errorf("expected %s, got %s", want, got) + } + }) + } +} diff --git a/internal/check/frontmatter.go b/internal/check/frontmatter.go new file mode 100644 index 00000000..8adf8d42 --- /dev/null +++ b/internal/check/frontmatter.go @@ -0,0 +1,82 @@ +package check + +import ( + "fmt" + + "gopkg.in/yaml.v2" +) + +type FrontMatterCheck struct { + Options *FrontMatterOptions +} + +// FrontMatterData represents the YAML frontmatter of Terraform Provider documentation. +type FrontMatterData struct { + Description *string `yaml:"description,omitempty"` + Layout *string `yaml:"layout,omitempty"` + PageTitle *string `yaml:"page_title,omitempty"` + SidebarCurrent *string `yaml:"sidebar_current,omitempty"` + Subcategory *string `yaml:"subcategory,omitempty"` +} + +// FrontMatterOptions represents configuration options for FrontMatter. +type FrontMatterOptions struct { + NoLayout bool + NoPageTitle bool + NoSidebarCurrent bool + NoSubcategory bool + RequireDescription bool + RequireLayout bool + RequirePageTitle bool +} + +func NewFrontMatterCheck(opts *FrontMatterOptions) *FrontMatterCheck { + check := &FrontMatterCheck{ + Options: opts, + } + + if check.Options == nil { + check.Options = &FrontMatterOptions{} + } + + return check +} + +func (check *FrontMatterCheck) Run(src []byte) error { + frontMatter := FrontMatterData{} + + err := yaml.Unmarshal(src, &frontMatter) + if err != nil { + return fmt.Errorf("error parsing YAML frontmatter: %w", err) + } + + if check.Options.NoLayout && frontMatter.Layout != nil { + return fmt.Errorf("YAML frontmatter should not contain layout") + } + + if check.Options.NoPageTitle && frontMatter.PageTitle != nil { + return fmt.Errorf("YAML frontmatter should not contain page_title") + } + + if check.Options.NoSidebarCurrent && frontMatter.SidebarCurrent != nil { + return fmt.Errorf("YAML frontmatter should not contain sidebar_current") + } + + if check.Options.NoSubcategory && frontMatter.Subcategory != nil { + return fmt.Errorf("YAML frontmatter should not contain subcategory") + } + + if check.Options.RequireDescription && frontMatter.Description == nil { + return fmt.Errorf("YAML frontmatter missing required description") + } + + if check.Options.RequireLayout && frontMatter.Layout == nil { + return fmt.Errorf("YAML frontmatter missing required layout") + } + + if check.Options.RequirePageTitle && frontMatter.PageTitle == nil { + return fmt.Errorf("YAML frontmatter missing required page_title") + } + + return nil +} diff --git a/internal/check/frontmatter_test.go b/internal/check/frontmatter_test.go new file mode 100644 index 00000000..d6c1a48c --- /dev/null +++ b/internal/check/frontmatter_test.go @@ -0,0 +1,161 @@ +package check + +import ( + "testing" +) + +func TestFrontMatterCheck(t *testing.T) { + testCases := []struct { + Name string + Source string + Options *FrontMatterOptions + ExpectError bool + }{ + { + Name: "empty source", + Source: ``, + }, + { + Name: "valid YAML with default options", + Source: ` +description: |- + Example description +layout: "example" +page_title: Example Page Title +subcategory: Example Subcategory +`, + }, + { + Name: "valid YAML section and Markdown with default options", + Source: ` +--- +description: |- + Example description +layout: "example" +page_title: Example Page Title +subcategory: Example Subcategory +--- + +# Markdown here we go! +`, + }, + { + Name: "invalid YAML", + Source: ` +description: |- + Example description +Extraneous newline +`, + ExpectError: true, + }, + { + Name: "no layout option", + Source: ` +description: |- + Example description +layout: "example" +page_title: Example Page Title +subcategory: Example Subcategory +`, + Options: &FrontMatterOptions{ + NoLayout: true, + }, + ExpectError: true, + }, + { + Name: "no page_title option", + Source: ` +description: |- + Example description +layout: "example" +page_title: Example Page Title +subcategory: Example Subcategory +`, + Options: &FrontMatterOptions{ + NoPageTitle: true, + }, + ExpectError: true, + }, + { + Name: "no sidebar_current option", + Source: ` +description: |- + Example description +layout: "example" +page_title: Example Page Title +sidebar_current: "example_resource" +subcategory: Example Subcategory +`, + Options: &FrontMatterOptions{ + NoSidebarCurrent: true, + }, + ExpectError: true, + }, + { + Name: "no subcategory option", + Source: ` +description: |- + Example description +layout: "example" +page_title: Example Page Title +subcategory: Example Subcategory +`, + Options: &FrontMatterOptions{ + NoSubcategory: true, + }, + ExpectError: true, + }, + { + Name: "require description option", + Source: ` +layout: "example" +page_title: Example Page Title +subcategory: Example Subcategory +`, + Options: &FrontMatterOptions{ + RequireDescription: true, + }, + ExpectError: true, + }, + { + Name: "require layout option", + Source: ` +description: |- + Example description +page_title: Example Page Title +subcategory: Example Subcategory +`, + Options: &FrontMatterOptions{ + RequireLayout: true, + }, + ExpectError: true, + }, + { + Name: "require page_title option", + Source: ` +description: |- + Example description +layout: "example" +subcategory: Example Subcategory +`, + Options: &FrontMatterOptions{ + RequirePageTitle: true, + }, + ExpectError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := NewFrontMatterCheck(testCase.Options).Run([]byte(testCase.Source)) + + if got == nil && testCase.ExpectError { + t.Errorf("expected error, got no error") + } + + if got != nil && !testCase.ExpectError { + t.Errorf("expected no error, got error: %s", got) + } + }) + } +} diff --git a/internal/check/provider_file.go b/internal/check/provider_file.go new file mode 100644 index 00000000..23fca92e --- /dev/null +++ b/internal/check/provider_file.go @@ -0,0 +1,80 @@ +package check + +import ( + "fmt" + "log" + "os" + + "github.com/hashicorp/go-multierror" +) + +type ProviderFileOptions struct { + *FileOptions + + FrontMatter *FrontMatterOptions + ValidExtensions []string +} + +type ProviderFileCheck struct { + FileCheck + + Options *ProviderFileOptions +} + +func NewProviderFileCheck(opts *ProviderFileOptions) *ProviderFileCheck { + check := &ProviderFileCheck{ + Options: opts, + } + + if check.Options == nil { + check.Options = &ProviderFileOptions{} + } + + if check.Options.FileOptions == nil { + check.Options.FileOptions = &FileOptions{} + } + + if check.Options.FrontMatter == nil { + check.Options.FrontMatter = &FrontMatterOptions{} + } + + return check +} + +func (check *ProviderFileCheck) Run(path string) error { + fullpath := check.Options.FullPath(path) + + log.Printf("[DEBUG] Checking file: %s", fullpath) + + if err := FileExtensionCheck(path, check.Options.ValidExtensions); err != nil { + return fmt.Errorf("%s: error checking file extension: %w", path, err) + } + + if err := FileSizeCheck(fullpath); err != nil { + return fmt.Errorf("%s: error checking file size: %w", path, err) + } + + content, err := os.ReadFile(fullpath) + + if err != nil { + return fmt.Errorf("%s: error reading file: %w", path, err) + } + + if err := NewFrontMatterCheck(check.Options.FrontMatter).Run(content); err != nil { + return fmt.Errorf("%s: error checking file frontmatter: %w", path, err) + } + + return nil +} + +func (check *ProviderFileCheck) RunAll(files []string) error { + var result *multierror.Error + + for _, file := range files { + if err := check.Run(file); err != nil { + result = multierror.Append(result, err) + } + } + + return result.ErrorOrNil() +} diff --git a/internal/cmd/validate.go b/internal/cmd/validate.go index c4a406cf..4eef2d87 100644 --- a/internal/cmd/validate.go +++ b/internal/cmd/validate.go @@ -4,6 +4,7 @@ package cmd import ( + "errors" "flag" "fmt" "strings" @@ -13,6 +14,11 @@ import ( type validateCmd struct { commonCmd + + flagProviderName string + flagProviderDir string + flagProvidersSchema string + tfVersion string } func (cmd *validateCmd) Synopsis() string { @@ -59,6 +65,10 @@ func (cmd *validateCmd) Help() string { func (cmd *validateCmd) Flags() *flag.FlagSet { fs := flag.NewFlagSet("validate", flag.ExitOnError) + fs.StringVar(&cmd.flagProviderName, "provider-name", "", "provider name, as used in Terraform configurations") + fs.StringVar(&cmd.flagProviderDir, "provider-dir", "", "relative or absolute path to the root provider code directory when running the command outside the root provider code directory") + fs.StringVar(&cmd.flagProvidersSchema, "providers-schema", "", "path to the providers schema JSON file, which contains the output of the terraform providers schema -json command. Setting this flag will skip building the provider and calling Terraform CLI") + fs.StringVar(&cmd.tfVersion, "tf-version", "", "terraform binary version to download. If not provided, will look for a terraform binary in the local environment. If not found in the environment, will download the latest version of Terraform") return fs } @@ -74,9 +84,14 @@ func (cmd *validateCmd) Run(args []string) int { } func (cmd *validateCmd) runInternal() error { - err := provider.Validate(cmd.ui) + err := provider.Validate(cmd.ui, + cmd.flagProviderDir, + cmd.flagProviderName, + cmd.flagProvidersSchema, + cmd.tfVersion, + ) if err != nil { - return fmt.Errorf("unable to validate website: %w", err) + return errors.Join(errors.New("validation errors found: "), err) } return nil diff --git a/internal/provider/logger.go b/internal/provider/logger.go new file mode 100644 index 00000000..eff51a31 --- /dev/null +++ b/internal/provider/logger.go @@ -0,0 +1,23 @@ +package provider + +import ( + "fmt" + + "github.com/hashicorp/cli" +) + +type Logger struct { + ui cli.Ui +} + +func NewLogger(ui cli.Ui) *Logger { + return &Logger{ui} +} + +func (l *Logger) infof(format string, args ...interface{}) { + l.ui.Info(fmt.Sprintf(format, args...)) +} + +func (l *Logger) warnf(format string, args ...interface{}) { + l.ui.Warn(fmt.Sprintf(format, args...)) +} diff --git a/internal/provider/schema.go b/internal/provider/schema.go new file mode 100644 index 00000000..9de293d0 --- /dev/null +++ b/internal/provider/schema.go @@ -0,0 +1,133 @@ +package provider + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + + "github.com/hashicorp/go-version" + install "github.com/hashicorp/hc-install" + "github.com/hashicorp/hc-install/checkpoint" + "github.com/hashicorp/hc-install/fs" + "github.com/hashicorp/hc-install/product" + "github.com/hashicorp/hc-install/releases" + "github.com/hashicorp/hc-install/src" + "github.com/hashicorp/terraform-exec/tfexec" + tfjson "github.com/hashicorp/terraform-json" +) + +func TerraformProviderSchemaFromTerraform(ctx context.Context, providerName, providerDir, tfVersion string, l *Logger) (*tfjson.ProviderSchema, error) { + var err error + + shortName := providerShortName(providerName) + + tmpDir, err := os.MkdirTemp("", "tfws") + if err != nil { + return nil, fmt.Errorf("unable to create temporary provider install directory %q: %w", tmpDir, err) + } + defer os.RemoveAll(tmpDir) + + l.infof("compiling provider %q", shortName) + providerPath := fmt.Sprintf("plugins/registry.terraform.io/hashicorp/%s/0.0.1/%s_%s", shortName, runtime.GOOS, runtime.GOARCH) + outFile := filepath.Join(tmpDir, providerPath, fmt.Sprintf("terraform-provider-%s", shortName)) + switch runtime.GOOS { + case "windows": + outFile = outFile + ".exe" + } + buildCmd := exec.Command("go", "build", "-o", outFile) + buildCmd.Dir = providerDir + // TODO: constrain env here to make it a little safer? + _, err = runCmd(buildCmd) + if err != nil { + return nil, fmt.Errorf("unable to execute go build command: %w", err) + } + + err = writeFile(filepath.Join(tmpDir, "provider.tf"), fmt.Sprintf(` +provider %[1]q { +} +`, shortName)) + if err != nil { + return nil, fmt.Errorf("unable to write provider.tf file: %w", err) + } + + i := install.NewInstaller() + var sources []src.Source + if tfVersion != "" { + l.infof("downloading Terraform CLI binary version from releases.hashicorp.com: %s", tfVersion) + sources = []src.Source{ + &releases.ExactVersion{ + Product: product.Terraform, + Version: version.Must(version.NewVersion(tfVersion)), + InstallDir: tmpDir, + }, + } + } else { + l.infof("using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary") + sources = []src.Source{ + &fs.AnyVersion{ + Product: &product.Terraform, + }, + &checkpoint.LatestVersion{ + InstallDir: tmpDir, + Product: product.Terraform, + }, + } + } + + tfBin, err := i.Ensure(context.Background(), sources) + if err != nil { + return nil, fmt.Errorf("unable to download Terraform binary: %w", err) + } + + tf, err := tfexec.NewTerraform(tmpDir, tfBin) + if err != nil { + return nil, fmt.Errorf("unable to create new terraform exec instance: %w", err) + } + + l.infof("running terraform init") + err = tf.Init(ctx, tfexec.Get(false), tfexec.PluginDir("./plugins")) + if err != nil { + return nil, fmt.Errorf("unable to run terraform init on provider: %w", err) + } + + l.infof("getting provider schema") + schemas, err := tf.ProvidersSchema(ctx) + if err != nil { + return nil, fmt.Errorf("unable to retrieve provider schema from terraform exec: %w", err) + } + + if ps, ok := schemas.Schemas[shortName]; ok { + return ps, nil + } + + if ps, ok := schemas.Schemas["registry.terraform.io/hashicorp/"+shortName]; ok { + return ps, nil + } + + return nil, fmt.Errorf("unable to find schema in JSON for provider %q", shortName) +} + +func TerraformProviderSchemaFromFile(providerName, providersSchemaPath string, l *Logger) (*tfjson.ProviderSchema, error) { + var err error + + shortName := providerShortName(providerName) + + l.infof("getting provider schema") + schemas, err := extractSchemaFromFile(providersSchemaPath) + if err != nil { + return nil, fmt.Errorf("unable to retrieve provider schema from JSON file: %w", err) + } + + if ps, ok := schemas.Schemas[shortName]; ok { + return ps, nil + } + + if ps, ok := schemas.Schemas["registry.terraform.io/hashicorp/"+shortName]; ok { + return ps, nil + } + + return nil, fmt.Errorf("unable to find schema in JSON for provider %q", shortName) +} diff --git a/internal/provider/template.go b/internal/provider/template.go index 10f2e655..ce49935f 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -270,7 +270,7 @@ func (t functionTemplate) Render(providerDir, name, providerName, renderedProvid const defaultResourceTemplate resourceTemplate = `--- ` + frontmatterComment + ` -page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +page_title: "{{.Name}} {{.Type}} - {{.ProviderShortName}}" subcategory: "" description: |- {{ .Description | plainmarkdown | trimspace | prefixlines " " }} @@ -299,7 +299,7 @@ Import is supported using the following syntax: const defaultFunctionTemplate functionTemplate = `--- ` + frontmatterComment + ` -page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +page_title: "{{.Name}} {{.Type}} - {{.ProviderShortName}}" subcategory: "" description: |- {{ .Summary | plainmarkdown | trimspace | prefixlines " " }} diff --git a/internal/provider/templates/template-description.gotmpl b/internal/provider/templates/template-description.gotmpl new file mode 100644 index 00000000..5b056cb7 --- /dev/null +++ b/internal/provider/templates/template-description.gotmpl @@ -0,0 +1,3 @@ +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider code. */}} \ No newline at end of file diff --git a/internal/provider/testdata/invalid-legacy-directories/website/docs/r/invalid/thing.html.markdown b/internal/provider/testdata/invalid-legacy-directories/website/docs/r/invalid/thing.html.markdown new file mode 100644 index 00000000..59bb04cc --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-directories/website/docs/r/invalid/thing.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-legacy-files/data_source_invalid_extension.txt b/internal/provider/testdata/invalid-legacy-files/data_source_invalid_extension.txt new file mode 100644 index 00000000..caee79a4 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/data_source_invalid_extension.txt @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-legacy-files/data_source_invalid_frontmatter.html.markdown b/internal/provider/testdata/invalid-legacy-files/data_source_invalid_frontmatter.html.markdown new file mode 100644 index 00000000..1cb8750a --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/data_source_invalid_frontmatter.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- +Missing indentation. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-legacy-files/data_source_with_sidebar_current.html.markdown b/internal/provider/testdata/invalid-legacy-files/data_source_with_sidebar_current.html.markdown new file mode 100644 index 00000000..510d48ae --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/data_source_with_sidebar_current.html.markdown @@ -0,0 +1,28 @@ +--- +subcategory: "Example" +sidebar_current: "example_thing" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-legacy-files/data_source_without_layout.html.markdown b/internal/provider/testdata/invalid-legacy-files/data_source_without_layout.html.markdown new file mode 100644 index 00000000..688fa598 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/data_source_without_layout.html.markdown @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-legacy-files/guide_invalid_extension.txt b/internal/provider/testdata/invalid-legacy-files/guide_invalid_extension.txt new file mode 100644 index 00000000..b02bcc8e --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/guide_invalid_extension.txt @@ -0,0 +1,11 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example Guide" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/guide_invalid_frontmatter.html.markdown b/internal/provider/testdata/invalid-legacy-files/guide_invalid_frontmatter.html.markdown new file mode 100644 index 00000000..33997fc6 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/guide_invalid_frontmatter.html.markdown @@ -0,0 +1,11 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example Guide" +description: |- +Missing indentation. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/guide_with_sidebar_current.html.markdown b/internal/provider/testdata/invalid-legacy-files/guide_with_sidebar_current.html.markdown new file mode 100644 index 00000000..5a101a3c --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/guide_with_sidebar_current.html.markdown @@ -0,0 +1,12 @@ +--- +subcategory: "Example" +sidebar_current: "example" +layout: "example" +page_title: "Example Guide" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/guide_without_layout.html.markdown b/internal/provider/testdata/invalid-legacy-files/guide_without_layout.html.markdown new file mode 100644 index 00000000..61331ef0 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/guide_without_layout.html.markdown @@ -0,0 +1,10 @@ +--- +subcategory: "Example" +page_title: "Example Guide" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/index_invalid_extension.txt b/internal/provider/testdata/invalid-legacy-files/index_invalid_extension.txt new file mode 100644 index 00000000..c4ab1478 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/index_invalid_extension.txt @@ -0,0 +1,10 @@ +--- +layout: "example" +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/index_invalid_frontmatter.html.markdown b/internal/provider/testdata/invalid-legacy-files/index_invalid_frontmatter.html.markdown new file mode 100644 index 00000000..c4903d5a --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/index_invalid_frontmatter.html.markdown @@ -0,0 +1,10 @@ +--- +layout: "example" +page_title: "Example Provider" +description: |- +Missing indentation. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/index_with_sidebar_current.html.markdown b/internal/provider/testdata/invalid-legacy-files/index_with_sidebar_current.html.markdown new file mode 100644 index 00000000..fe33d3f2 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/index_with_sidebar_current.html.markdown @@ -0,0 +1,11 @@ +--- +sidebar_current: "example" +layout: "example" +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/index_with_subcategory.html.markdown b/internal/provider/testdata/invalid-legacy-files/index_with_subcategory.html.markdown new file mode 100644 index 00000000..9766b6df --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/index_with_subcategory.html.markdown @@ -0,0 +1,10 @@ +--- +subcategory: "Example" +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/index_without_layout.html.markdown b/internal/provider/testdata/invalid-legacy-files/index_without_layout.html.markdown new file mode 100644 index 00000000..9daab155 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/index_without_layout.html.markdown @@ -0,0 +1,9 @@ +--- +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-legacy-files/resource_invalid_extension.txt b/internal/provider/testdata/invalid-legacy-files/resource_invalid_extension.txt new file mode 100644 index 00000000..59bb04cc --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/resource_invalid_extension.txt @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-legacy-files/resource_invalid_frontmatter.html.markdown b/internal/provider/testdata/invalid-legacy-files/resource_invalid_frontmatter.html.markdown new file mode 100644 index 00000000..0cd796a3 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/resource_invalid_frontmatter.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- +Missing indentation. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-legacy-files/resource_with_sidebar_current.html.markdown b/internal/provider/testdata/invalid-legacy-files/resource_with_sidebar_current.html.markdown new file mode 100644 index 00000000..32ec4684 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/resource_with_sidebar_current.html.markdown @@ -0,0 +1,28 @@ +--- +subcategory: "Example" +sidebar_current: "example_thing" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-legacy-files/resource_without_layout.html.markdown b/internal/provider/testdata/invalid-legacy-files/resource_without_layout.html.markdown new file mode 100644 index 00000000..aa156f35 --- /dev/null +++ b/internal/provider/testdata/invalid-legacy-files/resource_without_layout.html.markdown @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-mixed-directories/docs/resources/thing.md b/internal/provider/testdata/invalid-mixed-directories/docs/resources/thing.md new file mode 100644 index 00000000..59bb04cc --- /dev/null +++ b/internal/provider/testdata/invalid-mixed-directories/docs/resources/thing.md @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-mixed-directories/website/docs/r/thing.html.markdown b/internal/provider/testdata/invalid-mixed-directories/website/docs/r/thing.html.markdown new file mode 100644 index 00000000..e69de29b diff --git a/internal/provider/testdata/invalid-registry-directories/docs/resources/invalid/thing.md b/internal/provider/testdata/invalid-registry-directories/docs/resources/invalid/thing.md new file mode 100644 index 00000000..aa156f35 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-directories/docs/resources/invalid/thing.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-registry-files/data_source_invalid_extension.markdown b/internal/provider/testdata/invalid-registry-files/data_source_invalid_extension.markdown new file mode 100644 index 00000000..688fa598 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/data_source_invalid_extension.markdown @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-registry-files/data_source_invalid_frontmatter.md b/internal/provider/testdata/invalid-registry-files/data_source_invalid_frontmatter.md new file mode 100644 index 00000000..224dcf7e --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/data_source_invalid_frontmatter.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- +Missing indentation. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-registry-files/data_source_with_layout.md b/internal/provider/testdata/invalid-registry-files/data_source_with_layout.md new file mode 100644 index 00000000..caee79a4 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/data_source_with_layout.md @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-registry-files/data_source_with_sidebar_current.md b/internal/provider/testdata/invalid-registry-files/data_source_with_sidebar_current.md new file mode 100644 index 00000000..afc1af9e --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/data_source_with_sidebar_current.md @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +sidebar_current: "example_thing" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-registry-files/guide_invalid_extension.markdown b/internal/provider/testdata/invalid-registry-files/guide_invalid_extension.markdown new file mode 100644 index 00000000..9acc401d --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/guide_invalid_extension.markdown @@ -0,0 +1,10 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/guide_invalid_frontmatter.md b/internal/provider/testdata/invalid-registry-files/guide_invalid_frontmatter.md new file mode 100644 index 00000000..1dbe9583 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/guide_invalid_frontmatter.md @@ -0,0 +1,10 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- +Missing indentation. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/guide_with_layout.md b/internal/provider/testdata/invalid-registry-files/guide_with_layout.md new file mode 100644 index 00000000..ff7efb92 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/guide_with_layout.md @@ -0,0 +1,11 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/guide_with_sidebar_current.md b/internal/provider/testdata/invalid-registry-files/guide_with_sidebar_current.md new file mode 100644 index 00000000..1954289d --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/guide_with_sidebar_current.md @@ -0,0 +1,11 @@ +--- +subcategory: "Example" +sidebar_current: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/index_invalid_extension.markdown b/internal/provider/testdata/invalid-registry-files/index_invalid_extension.markdown new file mode 100644 index 00000000..9daab155 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/index_invalid_extension.markdown @@ -0,0 +1,9 @@ +--- +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/index_invalid_frontmatter.md b/internal/provider/testdata/invalid-registry-files/index_invalid_frontmatter.md new file mode 100644 index 00000000..f19cf719 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/index_invalid_frontmatter.md @@ -0,0 +1,9 @@ +--- +page_title: "Example Provider" +description: |- +Missing indentation. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/index_with_layout.md b/internal/provider/testdata/invalid-registry-files/index_with_layout.md new file mode 100644 index 00000000..c4ab1478 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/index_with_layout.md @@ -0,0 +1,10 @@ +--- +layout: "example" +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/index_with_sidebar_current.md b/internal/provider/testdata/invalid-registry-files/index_with_sidebar_current.md new file mode 100644 index 00000000..cd0a42c5 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/index_with_sidebar_current.md @@ -0,0 +1,10 @@ +--- +sidebar_current: "example" +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/index_with_subcategory.md b/internal/provider/testdata/invalid-registry-files/index_with_subcategory.md new file mode 100644 index 00000000..eb4751c9 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/index_with_subcategory.md @@ -0,0 +1,10 @@ +--- +subcategory: "example" +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/invalid-registry-files/resource_invalid_extension.markdown b/internal/provider/testdata/invalid-registry-files/resource_invalid_extension.markdown new file mode 100644 index 00000000..aa156f35 --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/resource_invalid_extension.markdown @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-registry-files/resource_invalid_frontmatter.md b/internal/provider/testdata/invalid-registry-files/resource_invalid_frontmatter.md new file mode 100644 index 00000000..39a2823b --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/resource_invalid_frontmatter.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- +Missing indentation. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-registry-files/resource_with_layout.md b/internal/provider/testdata/invalid-registry-files/resource_with_layout.md new file mode 100644 index 00000000..59bb04cc --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/resource_with_layout.md @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/invalid-registry-files/resource_with_sidebar_current.md b/internal/provider/testdata/invalid-registry-files/resource_with_sidebar_current.md new file mode 100644 index 00000000..91e6dcbb --- /dev/null +++ b/internal/provider/testdata/invalid-registry-files/resource_with_sidebar_current.md @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +sidebar_current: "example_thing" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/cdktf/typescript/d/thing.html.markdown b/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/cdktf/typescript/d/thing.html.markdown new file mode 100644 index 00000000..32e9de67 --- /dev/null +++ b/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/cdktf/typescript/d/thing.html.markdown @@ -0,0 +1,37 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```ts +import { Construct } from "construct"; +import { TerraformStack } from "cdktf"; +import { DataExample } from "./.gen/providers/example/data_example_thing"; + +class MyStack extends TerraformStack { + constructs(scope: Construct, name: string) { + super(scope, name); + + new DataExampleThing(this, "example", { + name: "example", + }); + } +} +``` + +## Argument Reference + +- `name` - (Required) Name of thing. + +## Attribute Reference + +- `id` - Name of thing. diff --git a/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/cdktf/typescript/r/thing.html.markdown b/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/cdktf/typescript/r/thing.html.markdown new file mode 100644 index 00000000..962ae86b --- /dev/null +++ b/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/cdktf/typescript/r/thing.html.markdown @@ -0,0 +1,37 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```ts +import { Construct } from "construct"; +import { TerraformStack } from "cdktf"; +import { Thing } from "./.gen/providers/example/thing"; + +class MyStack extends TerraformStack { + constructs(scope: Construct, name: string) { + super(scope, name); + + new Thing(this, "example", { + name: "example", + }); + } +} +``` + +## Argument Reference + +- `name` - (Required) Name of thing. + +## Attribute Reference + +- `id` - Name of thing. diff --git a/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/d/thing.html.markdown b/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/d/thing.html.markdown new file mode 100644 index 00000000..caee79a4 --- /dev/null +++ b/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/d/thing.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/r/thing.html.markdown b/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/r/thing.html.markdown new file mode 100644 index 00000000..59bb04cc --- /dev/null +++ b/internal/provider/testdata/valid-legacy-directories-with-cdktf/website/docs/r/thing.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-legacy-directories/website/docs/d/thing.html.markdown b/internal/provider/testdata/valid-legacy-directories/website/docs/d/thing.html.markdown new file mode 100644 index 00000000..caee79a4 --- /dev/null +++ b/internal/provider/testdata/valid-legacy-directories/website/docs/d/thing.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-legacy-directories/website/docs/r/thing.html.markdown b/internal/provider/testdata/valid-legacy-directories/website/docs/r/thing.html.markdown new file mode 100644 index 00000000..59bb04cc --- /dev/null +++ b/internal/provider/testdata/valid-legacy-directories/website/docs/r/thing.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-legacy-files/2.0-guide.html.markdown b/internal/provider/testdata/valid-legacy-files/2.0-guide.html.markdown new file mode 100644 index 00000000..b02bcc8e --- /dev/null +++ b/internal/provider/testdata/valid-legacy-files/2.0-guide.html.markdown @@ -0,0 +1,11 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example Guide" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/valid-legacy-files/data_source.html.markdown b/internal/provider/testdata/valid-legacy-files/data_source.html.markdown new file mode 100644 index 00000000..caee79a4 --- /dev/null +++ b/internal/provider/testdata/valid-legacy-files/data_source.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-legacy-files/guide.html.markdown b/internal/provider/testdata/valid-legacy-files/guide.html.markdown new file mode 100644 index 00000000..b02bcc8e --- /dev/null +++ b/internal/provider/testdata/valid-legacy-files/guide.html.markdown @@ -0,0 +1,11 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example Guide" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/valid-legacy-files/index.html.markdown b/internal/provider/testdata/valid-legacy-files/index.html.markdown new file mode 100644 index 00000000..c4ab1478 --- /dev/null +++ b/internal/provider/testdata/valid-legacy-files/index.html.markdown @@ -0,0 +1,10 @@ +--- +layout: "example" +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/valid-legacy-files/resource.html.markdown b/internal/provider/testdata/valid-legacy-files/resource.html.markdown new file mode 100644 index 00000000..59bb04cc --- /dev/null +++ b/internal/provider/testdata/valid-legacy-files/resource.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-mixed-directories/docs/CONTRIBUTING.md b/internal/provider/testdata/valid-mixed-directories/docs/CONTRIBUTING.md new file mode 100644 index 00000000..423305c4 --- /dev/null +++ b/internal/provider/testdata/valid-mixed-directories/docs/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing Guide + +This file has contents and no YAML frontmatter, because it is not a Terraform Provider documentation file and that is okay. diff --git a/internal/provider/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md b/internal/provider/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md new file mode 100644 index 00000000..a7f1fce5 --- /dev/null +++ b/internal/provider/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md @@ -0,0 +1,3 @@ +# Valid + +Files in `/docs`, but outside Registry documentation directories, should be ignored. diff --git a/internal/provider/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown b/internal/provider/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown new file mode 100644 index 00000000..59bb04cc --- /dev/null +++ b/internal/provider/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/CONTRIBUTING.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/CONTRIBUTING.md new file mode 100644 index 00000000..423305c4 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing Guide + +This file has contents and no YAML frontmatter, because it is not a Terraform Provider documentation file and that is okay. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/CONTRIBUTING.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/CONTRIBUTING.md new file mode 100644 index 00000000..423305c4 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing Guide + +This file has contents and no YAML frontmatter, because it is not a Terraform Provider documentation file and that is okay. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/data-sources/thing.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/data-sources/thing.md new file mode 100644 index 00000000..5c415dba --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/data-sources/thing.md @@ -0,0 +1,36 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```ts +import { Construct } from "construct"; +import { TerraformStack } from "cdktf"; +import { DataExample } from "./.gen/providers/example/data_example_thing"; + +class MyStack extends TerraformStack { + constructs(scope: Construct, name: string) { + super(scope, name); + + new DataExampleThing(this, "example", { + name: "example", + }); + } +} +``` + +## Argument Reference + +- `name` - (Required) Name of thing. + +## Attribute Reference + +- `id` - Name of thing. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/index.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/index.md new file mode 100644 index 00000000..9daab155 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/index.md @@ -0,0 +1,9 @@ +--- +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/nonregistrydocs/valid.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/nonregistrydocs/valid.md new file mode 100644 index 00000000..a7f1fce5 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/nonregistrydocs/valid.md @@ -0,0 +1,3 @@ +# Valid + +Files in `/docs`, but outside Registry documentation directories, should be ignored. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/resources/thing.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/resources/thing.md new file mode 100644 index 00000000..1f46f48b --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/cdktf/typescript/resources/thing.md @@ -0,0 +1,36 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```ts +import { Construct } from "construct"; +import { TerraformStack } from "cdktf"; +import { Thing } from "./.gen/providers/example/thing"; + +class MyStack extends TerraformStack { + constructs(scope: Construct, name: string) { + super(scope, name); + + new Thing(this, "example", { + name: "example", + }); + } +} +``` + +## Argument Reference + +- `name` - (Required) Name of thing. + +## Attribute Reference + +- `id` - Name of thing. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/data-sources/thing.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/data-sources/thing.md new file mode 100644 index 00000000..688fa598 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/data-sources/thing.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/index.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/index.md new file mode 100644 index 00000000..9daab155 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/index.md @@ -0,0 +1,9 @@ +--- +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/nonregistrydocs/valid.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/nonregistrydocs/valid.md new file mode 100644 index 00000000..a7f1fce5 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/nonregistrydocs/valid.md @@ -0,0 +1,3 @@ +# Valid + +Files in `/docs`, but outside Registry documentation directories, should be ignored. diff --git a/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/resources/thing.md b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/resources/thing.md new file mode 100644 index 00000000..aa156f35 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories-with-cdktf/docs/resources/thing.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-registry-directories/docs/CONTRIBUTING.md b/internal/provider/testdata/valid-registry-directories/docs/CONTRIBUTING.md new file mode 100644 index 00000000..423305c4 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories/docs/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing Guide + +This file has contents and no YAML frontmatter, because it is not a Terraform Provider documentation file and that is okay. diff --git a/internal/provider/testdata/valid-registry-directories/docs/data-sources/thing.md b/internal/provider/testdata/valid-registry-directories/docs/data-sources/thing.md new file mode 100644 index 00000000..688fa598 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories/docs/data-sources/thing.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-registry-directories/docs/index.md b/internal/provider/testdata/valid-registry-directories/docs/index.md new file mode 100644 index 00000000..9daab155 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories/docs/index.md @@ -0,0 +1,9 @@ +--- +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/valid-registry-directories/docs/nonregistrydocs/valid.md b/internal/provider/testdata/valid-registry-directories/docs/nonregistrydocs/valid.md new file mode 100644 index 00000000..a7f1fce5 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories/docs/nonregistrydocs/valid.md @@ -0,0 +1,3 @@ +# Valid + +Files in `/docs`, but outside Registry documentation directories, should be ignored. diff --git a/internal/provider/testdata/valid-registry-directories/docs/resources/thing.md b/internal/provider/testdata/valid-registry-directories/docs/resources/thing.md new file mode 100644 index 00000000..aa156f35 --- /dev/null +++ b/internal/provider/testdata/valid-registry-directories/docs/resources/thing.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-registry-files/2.0-guide.md b/internal/provider/testdata/valid-registry-files/2.0-guide.md new file mode 100644 index 00000000..9acc401d --- /dev/null +++ b/internal/provider/testdata/valid-registry-files/2.0-guide.md @@ -0,0 +1,10 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/valid-registry-files/data_source.md b/internal/provider/testdata/valid-registry-files/data_source.md new file mode 100644 index 00000000..688fa598 --- /dev/null +++ b/internal/provider/testdata/valid-registry-files/data_source.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Data Source: example_thing + +Byline. + +## Example Usage + +```terraform +data "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/testdata/valid-registry-files/guide.md b/internal/provider/testdata/valid-registry-files/guide.md new file mode 100644 index 00000000..9acc401d --- /dev/null +++ b/internal/provider/testdata/valid-registry-files/guide.md @@ -0,0 +1,10 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Example Guide + +Example contents. diff --git a/internal/provider/testdata/valid-registry-files/index.md b/internal/provider/testdata/valid-registry-files/index.md new file mode 100644 index 00000000..9daab155 --- /dev/null +++ b/internal/provider/testdata/valid-registry-files/index.md @@ -0,0 +1,9 @@ +--- +page_title: "Example Provider" +description: |- + Example description. +--- + +# Example Provider + +Example contents. diff --git a/internal/provider/testdata/valid-registry-files/resource.md b/internal/provider/testdata/valid-registry-files/resource.md new file mode 100644 index 00000000..aa156f35 --- /dev/null +++ b/internal/provider/testdata/valid-registry-files/resource.md @@ -0,0 +1,26 @@ +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Resource: example_thing + +Byline. + +## Example Usage + +```terraform +resource "example_thing" "example" { + name = "example" +} +``` + +## Argument Reference + +* `name` - (Required) Name of thing. + +## Attribute Reference + +* `id` - Name of thing. diff --git a/internal/provider/validate.go b/internal/provider/validate.go index c93ff3ec..b72aa417 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -4,269 +4,321 @@ package provider import ( + "context" + "errors" "fmt" "os" + "path" "path/filepath" - "strings" + "github.com/bmatcuk/doublestar/v4" "github.com/hashicorp/cli" -) + tfjson "github.com/hashicorp/terraform-json" -func Validate(ui cli.Ui) error { - dirExists := func(name string) bool { - if _, err := os.Stat(name); err != nil { - return false - } + "github.com/hashicorp/terraform-plugin-docs/internal/check" +) - return true - } +const ( + FileExtensionHtmlMarkdown = `.html.markdown` + FileExtensionHtmlMd = `.html.md` + FileExtensionMarkdown = `.markdown` + FileExtensionMd = `.md` + DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources},website/docs}/**/*` +) - switch { - default: - ui.Warn("no website detected, exiting") - case dirExists("templates"): - ui.Info("detected templates directory, running checks...") - err := validateTemplates(ui, "templates") - if err != nil { - return err - } - if dirExists("examples") { - ui.Info("detected examples directory for templates, running checks...") - err = validateExamples(ui, "examples") - if err != nil { - return err - } - } - return err - case dirExists("docs"): - ui.Info("detected static docs directory, running checks") - return validateStaticDocs(ui, "docs") - case dirExists("website"): - ui.Info("detected legacy website directory, running checks") - return validateLegacyWebsite(ui, "website") - } +var ValidLegacyFileExtensions = []string{ + FileExtensionHtmlMarkdown, + FileExtensionHtmlMd, + FileExtensionMarkdown, + FileExtensionMd, +} - return nil +var ValidRegistryFileExtensions = []string{ + FileExtensionMd, } -func validateExamples(ui cli.Ui, dir string) error { - return nil +type validator struct { + providerName string + providerDir string + providersSchemaPath string + + tfVersion string + providerSchema *tfjson.ProviderSchema + + logger *Logger } -func validateTemplates(ui cli.Ui, dir string) error { - checks := []check{ - checkAllowedFiles( - "index.md", - "index.md.tmpl", - ), - checkAllowedDirs( - "data-sources", - "guides", - "functions", - "resources", - ), - checkBlockedExtensions( - ".html.md.tmpl", - ), - checkAllowedExtensions( - ".md", - ".md.tmpl", - ), - } - issues := []issue{} - for _, c := range checks { - checkIssues, err := c(dir) +func Validate(ui cli.Ui, providerDir, providerName, providersSchemaPath, tfversion string) error { + // Ensure provider directory is resolved absolute path + if providerDir == "" { + wd, err := os.Getwd() + if err != nil { - return err + return fmt.Errorf("error getting working directory: %w", err) } - issues = append(issues, checkIssues...) - } - for _, issue := range issues { - ui.Warn(fmt.Sprintf("%s: %s", issue.file, issue.message)) - } - if len(issues) > 0 { - return fmt.Errorf("invalid templates directory") - } - return nil -} -func validateStaticDocs(ui cli.Ui, dir string) error { - checks := []check{ - checkAllowedFiles( - "index.md", - ), - checkAllowedDirs( - "data-sources", - "guides", - "functions", - "resources", - "cdktf", - ), - checkBlockedExtensions( - ".html.md.tmpl", - ".html.md", - ".md.tmpl", - ), - checkAllowedExtensions( - ".md", - ), - } - issues := []issue{} - for _, c := range checks { - checkIssues, err := c(dir) + providerDir = wd + } else { + absProviderDir, err := filepath.Abs(providerDir) + if err != nil { - return err + return fmt.Errorf("error getting absolute path with provider directory %q: %w", providerDir, err) } - issues = append(issues, checkIssues...) + + providerDir = absProviderDir } - for _, issue := range issues { - ui.Warn(fmt.Sprintf("%s: %s", issue.file, issue.message)) + + // Verify provider directory + providerDirFileInfo, err := os.Stat(providerDir) + + if err != nil { + return fmt.Errorf("error getting information for provider directory %q: %w", providerDir, err) } - if len(issues) > 0 { - return fmt.Errorf("invalid templates directory") + + if !providerDirFileInfo.IsDir() { + return fmt.Errorf("expected %q to be a directory", providerDir) } - return nil -} -func validateLegacyWebsite(ui cli.Ui, dir string) error { - panic("not implemented") -} + v := &validator{ + providerName: providerName, + providerDir: providerDir, + providersSchemaPath: providersSchemaPath, + tfVersion: tfversion, + + logger: NewLogger(ui), + } + + ctx := context.Background() -type issue struct { - file string - message string + return v.validate(ctx) } -type check func(dir string) ([]issue, error) - -func checkBlockedExtensions(exts ...string) check { - return func(dir string) ([]issue, error) { - issues := []issue{} - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - for _, ext := range exts { - if strings.HasSuffix(path, ext) { - _, file := filepath.Split(path) - issues = append(issues, issue{ - file: path, - message: fmt.Sprintf("the extension for %q is not supported", file), - }) - break - } - } - return nil - }) +func (v *validator) validate(ctx context.Context) error { + var result error + + var err error + + if v.providersSchemaPath == "" { + v.logger.infof("exporting schema from Terraform") + v.providerSchema, err = TerraformProviderSchemaFromTerraform(ctx, v.providerName, v.providerDir, v.tfVersion, v.logger) if err != nil { - return nil, err + return fmt.Errorf("error exporting provider schema from Terraform: %w", err) } - return issues, nil - } -} - -func checkAllowedExtensions(exts ...string) check { - return func(dir string) ([]issue, error) { - issues := []issue{} - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - valid := false - for _, ext := range exts { - if strings.HasSuffix(path, ext) { - valid = true - break - } - } - if !valid { - _, file := filepath.Split(path) - issues = append(issues, issue{ - file: path, - message: fmt.Sprintf("the extension for %q is not expected", file), - }) - } - return nil - }) + } else { + v.logger.infof("exporting schema from JSON file") + v.providerSchema, err = TerraformProviderSchemaFromFile(v.providerName, v.providersSchemaPath, v.logger) if err != nil { - return nil, err + return fmt.Errorf("error exporting provider schema from JSON file: %w", err) } - return issues, nil } + + if dirExists(path.Join(v.providerDir, "docs")) && dirExists(path.Join(v.providerDir, "website")) { + result = errors.Join(result, errors.New("both static docs and legacy website directories detected, please remove one")) + } + + if dirExists(path.Join(v.providerDir, "docs")) { + v.logger.infof("detected static docs directory, running checks") + err = v.validateStaticDocs(path.Join(v.providerDir, "docs")) + result = errors.Join(result, err) + + } + if dirExists(path.Join(v.providerDir, "website/docs")) { + v.logger.infof("detected legacy website directory, running checks") + err = v.validateLegacyWebsite(path.Join(v.providerDir, "website/docs")) + result = errors.Join(result, err) + } + + return result } -func checkAllowedDirs(dirs ...string) check { - allowedDirs := map[string]bool{} - for _, d := range dirs { - allowedDirs[d] = true +func (v *validator) validateStaticDocs(dir string) error { + + var result error + + options := &check.ProviderFileOptions{ + FrontMatter: RegistryFrontMatterOptions, + ValidExtensions: ValidRegistryFileExtensions, } - return func(dir string) ([]issue, error) { - issues := []issue{} + var files []string - f, err := os.Open(dir) + err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { + rel, err := filepath.Rel(v.providerDir, path) if err != nil { - return nil, err + return err } - infos, err := f.Readdir(-1) + match, err := doublestar.Match(DocumentationGlobPattern, rel) if err != nil { - return nil, err + return err + } + if !match { + return nil // skip non-documentation directories/files + } + if d.IsDir() { + v.logger.infof("running invalid directories check on %s", rel) + result = errors.Join(result, check.InvalidDirectoriesCheck(rel)) + return nil } - for _, fi := range infos { - if !fi.IsDir() { - continue - } - - if !allowedDirs[fi.Name()] { - issues = append(issues, issue{ - file: filepath.Join(dir, fi.Name()), - message: fmt.Sprintf("directory %q is not allowed", fi.Name()), - }) - } + // Configure FrontMatterOptions based on file type + if d.Name() == "index.md" { + options.FrontMatter = RegistryIndexFrontMatterOptions + } else if _, relErr := filepath.Rel(rel, "guides"); relErr != nil { + options.FrontMatter = RegistryGuideFrontMatterOptions + } else { + options.FrontMatter = RegistryFrontMatterOptions } + v.logger.infof("running file checks on %s", rel) + result = errors.Join(result, check.NewProviderFileCheck(options).Run(path)) - return issues, nil + files = append(files, path) + return nil + }) + if err != nil { + return fmt.Errorf("error walking directory %q: %w", dir, err) + } + + mismatchOpt := &check.FileMismatchOptions{ + ProviderShortName: providerShortName(v.providerName), + Schema: v.providerSchema, + } + + if dirExists(filepath.Join(dir, "data-sources")) { + dataSourceFiles, _ := os.ReadDir(filepath.Join(dir, "data-sources")) + mismatchOpt.DatasourceEntries = dataSourceFiles + } + if dirExists(filepath.Join(dir, "resources")) { + resourceFiles, _ := os.ReadDir(filepath.Join(dir, "resources")) + mismatchOpt.ResourceEntries = resourceFiles + } + if dirExists(filepath.Join(dir, "functions")) { + functionFiles, _ := os.ReadDir(filepath.Join(dir, "functions")) + mismatchOpt.FunctionEntries = functionFiles } -} -func checkAllowedFiles(dirs ...string) check { - allowedFiles := map[string]bool{} - for _, d := range dirs { - allowedFiles[d] = true + v.logger.infof("running file mismatch check") + if err := check.NewFileMismatchCheck(mismatchOpt).Run(); err != nil { + result = errors.Join(result, err) } - return func(dir string) ([]issue, error) { - issues := []issue{} + return result +} + +func (v *validator) validateLegacyWebsite(dir string) error { + + var result error - f, err := os.Open(dir) + options := &check.ProviderFileOptions{ + FrontMatter: LegacyFrontMatterOptions, + ValidExtensions: ValidLegacyFileExtensions, + } + + var files []string + err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { + rel, err := filepath.Rel(v.providerDir, path) if err != nil { - return nil, err + return err } - infos, err := f.Readdir(-1) + match, err := doublestar.Match(DocumentationGlobPattern, rel) if err != nil { - return nil, err + return err + } + if !match { + return nil // skip non-documentation directories/files + } + if d.IsDir() { + v.logger.infof("running invalid directories check on %s", rel) + result = errors.Join(result, check.InvalidDirectoriesCheck(rel)) + return nil } - for _, fi := range infos { - if fi.IsDir() { - continue - } - - if !allowedFiles[fi.Name()] { - issues = append(issues, issue{ - file: filepath.Join(dir, fi.Name()), - message: fmt.Sprintf("file %q is not allowed", fi.Name()), - }) - } + // Configure FrontMatterOptions based on file type + if d.Name() == "index.md" { + options.FrontMatter = LegacyIndexFrontMatterOptions + } else if _, relErr := filepath.Rel(rel, "guides"); relErr != nil { + options.FrontMatter = LegacyGuideFrontMatterOptions + } else { + options.FrontMatter = LegacyFrontMatterOptions } + v.logger.infof("running file checks on %s", rel) + result = errors.Join(result, check.NewProviderFileCheck(options).Run(path)) + + files = append(files, path) + return nil + }) + if err != nil { + return fmt.Errorf("error walking directory %q: %w", dir, err) + } - return issues, nil + mismatchOpt := &check.FileMismatchOptions{ + ProviderShortName: providerShortName(v.providerName), + Schema: v.providerSchema, } + + if dirExists(filepath.Join(dir, "d")) { + dataSourceFiles, _ := os.ReadDir(filepath.Join(dir, "d")) + mismatchOpt.DatasourceEntries = dataSourceFiles + } + if dirExists(filepath.Join(dir, "r")) { + resourceFiles, _ := os.ReadDir(filepath.Join(dir, "r")) + mismatchOpt.ResourceEntries = resourceFiles + } + if dirExists(filepath.Join(dir, "functions")) { + functionFiles, _ := os.ReadDir(filepath.Join(dir, "functions")) + mismatchOpt.FunctionEntries = functionFiles + } + + v.logger.infof("running file mismatch check") + if err := check.NewFileMismatchCheck(mismatchOpt).Run(); err != nil { + result = errors.Join(result, err) + } + + return result +} + +func dirExists(name string) bool { + if _, err := os.Stat(name); err != nil { + return false + } + + return true +} + +var LegacyFrontMatterOptions = &check.FrontMatterOptions{ + NoSidebarCurrent: true, + RequireDescription: true, + RequireLayout: true, + RequirePageTitle: true, +} + +var LegacyIndexFrontMatterOptions = &check.FrontMatterOptions{ + NoSidebarCurrent: true, + NoSubcategory: true, + RequireDescription: true, + RequireLayout: true, + RequirePageTitle: true, +} + +var LegacyGuideFrontMatterOptions = &check.FrontMatterOptions{ + NoSidebarCurrent: true, + RequireDescription: true, + RequireLayout: true, + RequirePageTitle: true, +} + +var RegistryFrontMatterOptions = &check.FrontMatterOptions{ + NoLayout: true, + NoSidebarCurrent: true, +} + +var RegistryIndexFrontMatterOptions = &check.FrontMatterOptions{ + NoLayout: true, + NoSidebarCurrent: true, + NoSubcategory: true, +} + +var RegistryGuideFrontMatterOptions = &check.FrontMatterOptions{ + NoLayout: true, + NoSidebarCurrent: true, + RequirePageTitle: true, } diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go new file mode 100644 index 00000000..dea78c1e --- /dev/null +++ b/internal/provider/validate_test.go @@ -0,0 +1,132 @@ +package provider + +import ( + "path" + "testing" + + "github.com/hashicorp/cli" +) + +func TestValidator_validate(t *testing.T) { + t.Parallel() + + v := &validator{ + providerDir: "testdata/valid-registry-directories", + providerName: "terraform-provider-null", + + logger: NewLogger(cli.NewMockUi()), + } + + err := v.validateStaticDocs(path.Join(v.providerDir, "docs")) + if err != nil { + t.Fatalf("error retrieving schema: %q", err) + } +} + +func TestValidateStaticDocs(t *testing.T) { + testCases := []struct { + Name string + BasePath string + ExpectError bool + ExpectedError string + }{ + { + Name: "valid registry directories", + BasePath: "testdata/valid-registry-directories", + }, + { + Name: "valid registry directories with cdktf docs", + BasePath: "testdata/valid-registry-directories-with-cdktf", + }, + { + Name: "valid mixed directories", + BasePath: "testdata/valid-mixed-directories", + }, + { + Name: "invalid registry directories", + BasePath: "testdata/invalid-registry-directories", + ExpectError: true, + ExpectedError: "invalid Terraform Provider documentation directory found: docs/resources/invalid", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + + v := &validator{ + providerDir: testCase.BasePath, + providerName: "terraform-provider-test", + + logger: NewLogger(cli.NewMockUi()), + } + + got := v.validateStaticDocs(path.Join(v.providerDir, "docs")) + + if got == nil && testCase.ExpectError { + t.Errorf("expected error, got no error") + } + + if got != nil && !testCase.ExpectError { + t.Errorf("expected no error, got error: %s", got) + } + + if got != nil && got.Error() != testCase.ExpectedError { + t.Errorf("expected error: %s, got error: %s", testCase.ExpectedError, got) + } + }) + } +} + +func TestValidateLegacyWebsite(t *testing.T) { + testCases := []struct { + Name string + BasePath string + ExpectError bool + ExpectedError string + }{ + { + Name: "valid legacy directories", + BasePath: "testdata/valid-legacy-directories", + }, + { + Name: "valid legacy directories with cdktf docs", + BasePath: "testdata/valid-legacy-directories-with-cdktf", + }, + { + Name: "valid mixed directories", + BasePath: "testdata/valid-mixed-directories", + }, + { + Name: "invalid legacy directories", + BasePath: "testdata/invalid-legacy-directories", + ExpectError: true, + ExpectedError: "invalid Terraform Provider documentation directory found: website/docs/r/invalid", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + + v := &validator{ + providerDir: testCase.BasePath, + providerName: "terraform-provider-test", + + logger: NewLogger(cli.NewMockUi()), + } + + got := v.validateLegacyWebsite(path.Join(v.providerDir, "website")) + + if got == nil && testCase.ExpectError { + t.Errorf("expected error, got no error") + } + + if got != nil && !testCase.ExpectError { + t.Errorf("expected no error, got error: %s", got) + } + + if got != nil && got.Error() != testCase.ExpectedError { + t.Errorf("expected error: %s, got error: %s", testCase.ExpectedError, got) + } + }) + } +} From fd6e5133f5a7c659d399cb0eb3f1ddc4fbca3016 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 4 Mar 2024 15:54:48 -0500 Subject: [PATCH 02/29] Add function tests to TestFileMismatchCheck --- internal/check/file_mismatch_test.go | 104 ++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 11 deletions(-) diff --git a/internal/check/file_mismatch_test.go b/internal/check/file_mismatch_test.go index 82cd4154..7bd3d020 100644 --- a/internal/check/file_mismatch_test.go +++ b/internal/check/file_mismatch_test.go @@ -96,15 +96,11 @@ func TestFileMismatchCheck(t *testing.T) { ExpectError bool }{ { - Name: "all found", + Name: "all found - resource", ResourceFiles: fstest.MapFS{ "resource1.md": {}, "resource2.md": {}, }, - FunctionFiles: fstest.MapFS{ - "function1.md": {}, - "function2.md": {}, - }, Options: &FileMismatchOptions{ ProviderShortName: "test", Schema: &tfjson.ProviderSchema{ @@ -112,6 +108,18 @@ func TestFileMismatchCheck(t *testing.T) { "test_resource1": {}, "test_resource2": {}, }, + }, + }, + }, + { + Name: "all found - function", + FunctionFiles: fstest.MapFS{ + "function1.md": {}, + "function2.md": {}, + }, + Options: &FileMismatchOptions{ + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ Functions: map[string]*tfjson.FunctionSignature{ "function1": {}, "function2": {}, @@ -120,7 +128,7 @@ func TestFileMismatchCheck(t *testing.T) { }, }, { - Name: "extra file", + Name: "extra file - resource", ResourceFiles: fstest.MapFS{ "resource1.md": {}, "resource2.md": {}, @@ -138,7 +146,25 @@ func TestFileMismatchCheck(t *testing.T) { ExpectError: true, }, { - Name: "ignore extra file", + Name: "extra file - function", + FunctionFiles: fstest.MapFS{ + "function1.md": {}, + "function2.md": {}, + "function3.md": {}, + }, + Options: &FileMismatchOptions{ + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + Functions: map[string]*tfjson.FunctionSignature{ + "function1": {}, + "function2": {}, + }, + }, + }, + ExpectError: true, + }, + { + Name: "ignore extra file - resource", ResourceFiles: fstest.MapFS{ "resource1.md": {}, "resource2.md": {}, @@ -156,7 +182,26 @@ func TestFileMismatchCheck(t *testing.T) { }, }, { - Name: "missing file", + Name: "ignore extra file - function", + FunctionFiles: fstest.MapFS{ + "function1.md": {}, + "function2.md": {}, + "function3.md": {}, + }, + Options: &FileMismatchOptions{ + IgnoreFileMismatch: []string{"function3"}, + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + Functions: map[string]*tfjson.FunctionSignature{ + "function1": {}, + "function2": {}, + "function3": {}, + }, + }, + }, + }, + { + Name: "missing file - resource", ResourceFiles: fstest.MapFS{ "resource1.md": {}, }, @@ -172,7 +217,23 @@ func TestFileMismatchCheck(t *testing.T) { ExpectError: true, }, { - Name: "ignore missing file", + Name: "missing file - function", + FunctionFiles: fstest.MapFS{ + "function1.md": {}, + }, + Options: &FileMismatchOptions{ + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + Functions: map[string]*tfjson.FunctionSignature{ + "function1": {}, + "function2": {}, + }, + }, + }, + ExpectError: true, + }, + { + Name: "ignore missing file - resource", ResourceFiles: fstest.MapFS{ "resource1.md": {}, }, @@ -187,6 +248,22 @@ func TestFileMismatchCheck(t *testing.T) { }, }, }, + { + Name: "ignore missing file - function", + FunctionFiles: fstest.MapFS{ + "function1.md": {}, + }, + Options: &FileMismatchOptions{ + IgnoreFileMissing: []string{"function2"}, + ProviderShortName: "test", + Schema: &tfjson.ProviderSchema{ + Functions: map[string]*tfjson.FunctionSignature{ + "function1": {}, + "function2": {}, + }, + }, + }, + }, { Name: "no files", Options: &FileMismatchOptions{ @@ -196,6 +273,10 @@ func TestFileMismatchCheck(t *testing.T) { "test_resource1": {}, "test_resource2": {}, }, + Functions: map[string]*tfjson.FunctionSignature{ + "function1": {}, + "function2": {}, + }, }, }, }, @@ -204,6 +285,9 @@ func TestFileMismatchCheck(t *testing.T) { ResourceFiles: fstest.MapFS{ "resource1.md": {}, }, + FunctionFiles: fstest.MapFS{ + "function1.md": {}, + }, Options: &FileMismatchOptions{ ProviderShortName: "test", }, @@ -311,8 +395,6 @@ func TestFunctionHasFile(t *testing.T) { } } -//TODO: add functionMismatchCheck test - func TestResourceNames(t *testing.T) { testCases := []struct { Name string From f6810dfe786de289adfb33019c3f19589adf7de1 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 4 Mar 2024 16:53:08 -0500 Subject: [PATCH 03/29] Add MixedDirectoryCheck and refactor relevant tests --- internal/check/directory.go | 42 +++++++++++++++++++ internal/check/directory_test.go | 32 ++++++++++++++ .../docs/resources/thing.md | 0 .../website/docs/r/thing.html.markdown | 0 .../docs/CONTRIBUTING.md | 0 .../docs/nonregistrydocs/valid.md | 0 .../website/docs/r/thing.html.markdown | 0 internal/provider/validate.go | 5 +-- internal/provider/validate_test.go | 8 ---- 9 files changed, 76 insertions(+), 11 deletions(-) rename internal/{provider => check}/testdata/invalid-mixed-directories/docs/resources/thing.md (100%) rename internal/{provider => check}/testdata/invalid-mixed-directories/website/docs/r/thing.html.markdown (100%) rename internal/{provider => check}/testdata/valid-mixed-directories/docs/CONTRIBUTING.md (100%) rename internal/{provider => check}/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md (100%) rename internal/{provider => check}/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown (100%) diff --git a/internal/check/directory.go b/internal/check/directory.go index fcbb3719..8387ec2e 100644 --- a/internal/check/directory.go +++ b/internal/check/directory.go @@ -3,6 +3,10 @@ package check import ( "fmt" "log" + "os" + "path/filepath" + + "github.com/bmatcuk/doublestar/v4" ) const ( @@ -20,6 +24,8 @@ const ( RegistryResourcesDirectory = `resources` RegistryFunctionsDirectory = `functions` + DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources},website/docs}/**/*` + // Terraform Registry Storage Limits // https://www.terraform.io/docs/registry/providers/docs.html#storage-limits RegistryMaximumNumberOfFiles = 2000 @@ -82,6 +88,42 @@ func InvalidDirectoriesCheck(dirPath string) error { } +func MixedDirectoriesCheck(providerDir string) error { + var legacyDirectoryFound bool + var registryDirectoryFound bool + err := fmt.Errorf("mixed Terraform Provider documentation directory layouts found, must use only legacy or registry layout") + + providerFs := os.DirFS(providerDir) + + files, globErr := doublestar.Glob(providerFs, DocumentationGlobPattern) + if globErr != nil { + return fmt.Errorf("error finding documentation files: %w", err) + } + + for _, file := range files { + directory := filepath.Dir(file) + + // Allow docs/ with other files + if IsValidRegistryDirectory(directory) && directory != RegistryIndexDirectory { + registryDirectoryFound = true + + if legacyDirectoryFound { + return err + } + } + + if IsValidLegacyDirectory(directory) { + legacyDirectoryFound = true + + if registryDirectoryFound { + return err + } + } + } + + return nil +} + // NumberOfFilesCheck verifies that documentation is below the Terraform Registry storage limit. // This check presumes that all provided directories are valid, e.g. that directory checking // for invalid or mixed directory structures was previously completed. diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index c6cc9cfc..64c30d37 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -42,6 +42,38 @@ func TestNumberOfFilesCheck(t *testing.T) { } } +func TestMixedDirectoriesCheck(t *testing.T) { + testCases := []struct { + Name string + BasePath string + ExpectError bool + }{ + { + Name: "valid mixed directories", + BasePath: "testdata/valid-mixed-directories", + }, + { + Name: "invalid mixed directories", + BasePath: "testdata/invalid-mixed-directories", + ExpectError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + got := MixedDirectoriesCheck(testCase.BasePath) + + if got == nil && testCase.ExpectError { + t.Errorf("expected error, got no error") + } + + if got != nil && !testCase.ExpectError { + t.Errorf("expected no error, got error: %s", got) + } + }) + } +} + func testGenerateDirectories(numberOfFiles int) map[string][]string { files := make([]string, numberOfFiles) diff --git a/internal/provider/testdata/invalid-mixed-directories/docs/resources/thing.md b/internal/check/testdata/invalid-mixed-directories/docs/resources/thing.md similarity index 100% rename from internal/provider/testdata/invalid-mixed-directories/docs/resources/thing.md rename to internal/check/testdata/invalid-mixed-directories/docs/resources/thing.md diff --git a/internal/provider/testdata/invalid-mixed-directories/website/docs/r/thing.html.markdown b/internal/check/testdata/invalid-mixed-directories/website/docs/r/thing.html.markdown similarity index 100% rename from internal/provider/testdata/invalid-mixed-directories/website/docs/r/thing.html.markdown rename to internal/check/testdata/invalid-mixed-directories/website/docs/r/thing.html.markdown diff --git a/internal/provider/testdata/valid-mixed-directories/docs/CONTRIBUTING.md b/internal/check/testdata/valid-mixed-directories/docs/CONTRIBUTING.md similarity index 100% rename from internal/provider/testdata/valid-mixed-directories/docs/CONTRIBUTING.md rename to internal/check/testdata/valid-mixed-directories/docs/CONTRIBUTING.md diff --git a/internal/provider/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md b/internal/check/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md similarity index 100% rename from internal/provider/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md rename to internal/check/testdata/valid-mixed-directories/docs/nonregistrydocs/valid.md diff --git a/internal/provider/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown b/internal/check/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown similarity index 100% rename from internal/provider/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown rename to internal/check/testdata/valid-mixed-directories/website/docs/r/thing.html.markdown diff --git a/internal/provider/validate.go b/internal/provider/validate.go index b72aa417..7998f4f0 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -112,9 +112,8 @@ func (v *validator) validate(ctx context.Context) error { } } - if dirExists(path.Join(v.providerDir, "docs")) && dirExists(path.Join(v.providerDir, "website")) { - result = errors.Join(result, errors.New("both static docs and legacy website directories detected, please remove one")) - } + err = check.MixedDirectoriesCheck(v.providerDir) + result = errors.Join(result, err) if dirExists(path.Join(v.providerDir, "docs")) { v.logger.infof("detected static docs directory, running checks") diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index dea78c1e..da6707d9 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -38,10 +38,6 @@ func TestValidateStaticDocs(t *testing.T) { Name: "valid registry directories with cdktf docs", BasePath: "testdata/valid-registry-directories-with-cdktf", }, - { - Name: "valid mixed directories", - BasePath: "testdata/valid-mixed-directories", - }, { Name: "invalid registry directories", BasePath: "testdata/invalid-registry-directories", @@ -92,10 +88,6 @@ func TestValidateLegacyWebsite(t *testing.T) { Name: "valid legacy directories with cdktf docs", BasePath: "testdata/valid-legacy-directories-with-cdktf", }, - { - Name: "valid mixed directories", - BasePath: "testdata/valid-mixed-directories", - }, { Name: "invalid legacy directories", BasePath: "testdata/invalid-legacy-directories", From 7e3458e9ef3c1acd583c6cb8d7e72ed195cd9a5c Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 10:40:03 -0500 Subject: [PATCH 04/29] Add functions and guides to glob pattern --- ...amework_provider_success_legacy_docs.txtar | 15 +++++ ...ework_provider_success_registry_docs.txtar | 18 ++++++ internal/check/directory.go | 2 +- internal/provider/validate.go | 52 +++++++++++------ internal/provider/validate_test.go | 56 +++++++++++++++++++ 5 files changed, 125 insertions(+), 18 deletions(-) diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar index 5ece1598..ee8ea9b0 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar @@ -14,10 +14,25 @@ running invalid directories check on website/docs/d running file checks on website/docs/d/example.html.md running invalid directories check on website/docs/functions running file checks on website/docs/functions/example.html.md +running invalid directories check on website/docs/guides +running file checks on website/docs/guides/example.html.md running file checks on website/docs/index.html.md running invalid directories check on website/docs/r running file checks on website/docs/r/example.html.md running file mismatch check +-- website/docs/guides/example.html.md -- +--- +subcategory: "Example" +layout: "example" +page_title: "Example Guide" +description: |- + Example description. +--- + +# Example Guide + +Example contents. + -- website/docs/r/example.html.md -- --- subcategory: "Example" diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar index f64988b0..e0eaab5b 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar @@ -10,9 +10,27 @@ cmpenv stdout expected-output.txt exporting schema from JSON file getting provider schema detected static docs directory, running checks +running invalid directories check on docs/data-sources running file checks on docs/data-sources/example.md +running invalid directories check on docs/functions +running file checks on docs/functions/example.md +running invalid directories check on docs/guides +running file checks on docs/guides/example.md +running invalid directories check on docs/resources running file checks on docs/resources/example.md running file mismatch check +-- docs/guides/example.md -- +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- + +# Example Guide + +Example contents. + -- docs/resources/example.md -- --- subcategory: "Example" diff --git a/internal/check/directory.go b/internal/check/directory.go index 8387ec2e..653715fa 100644 --- a/internal/check/directory.go +++ b/internal/check/directory.go @@ -24,7 +24,7 @@ const ( RegistryResourcesDirectory = `resources` RegistryFunctionsDirectory = `functions` - DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources},website/docs}/**/*` + DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources,functions},website/docs}/**/*` // Terraform Registry Storage Limits // https://www.terraform.io/docs/registry/providers/docs.html#storage-limits diff --git a/internal/provider/validate.go b/internal/provider/validate.go index 7998f4f0..4d8ff062 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -19,11 +19,12 @@ import ( ) const ( - FileExtensionHtmlMarkdown = `.html.markdown` - FileExtensionHtmlMd = `.html.md` - FileExtensionMarkdown = `.markdown` - FileExtensionMd = `.md` - DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources},website/docs}/**/*` + FileExtensionHtmlMarkdown = `.html.markdown` + FileExtensionHtmlMd = `.html.md` + FileExtensionMarkdown = `.markdown` + FileExtensionMd = `.md` + DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources,functions},website/docs}/**/*` + DocumentationDirGlobPattern = `{docs/{,cdktf/}{data-sources,guides,resources,functions}{,/*},website/docs/{d,r,guides,functions}{,/*}}` ) var ValidLegacyFileExtensions = []string{ @@ -146,17 +147,25 @@ func (v *validator) validateStaticDocs(dir string) error { if err != nil { return err } + if d.IsDir() { + match, err := doublestar.Match(DocumentationDirGlobPattern, rel) + if err != nil { + return err + } + if !match { + return nil // skip valid non-documentation directories + } + + v.logger.infof("running invalid directories check on %s", rel) + result = errors.Join(result, check.InvalidDirectoriesCheck(rel)) + return nil + } match, err := doublestar.Match(DocumentationGlobPattern, rel) if err != nil { return err } if !match { - return nil // skip non-documentation directories/files - } - if d.IsDir() { - v.logger.infof("running invalid directories check on %s", rel) - result = errors.Join(result, check.InvalidDirectoriesCheck(rel)) - return nil + return nil // skip valid non-documentation files } // Configure FrontMatterOptions based on file type @@ -218,17 +227,26 @@ func (v *validator) validateLegacyWebsite(dir string) error { if err != nil { return err } + if d.IsDir() { + match, err := doublestar.Match(DocumentationDirGlobPattern, rel) + if err != nil { + return err + } + if !match { + return nil // skip valid non-documentation directories + } + + v.logger.infof("running invalid directories check on %s", rel) + result = errors.Join(result, check.InvalidDirectoriesCheck(rel)) + return nil + } + match, err := doublestar.Match(DocumentationGlobPattern, rel) if err != nil { return err } if !match { - return nil // skip non-documentation directories/files - } - if d.IsDir() { - v.logger.infof("running invalid directories check on %s", rel) - result = errors.Join(result, check.InvalidDirectoriesCheck(rel)) - return nil + return nil // skip non-documentation files } // Configure FrontMatterOptions based on file type diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index da6707d9..4504550b 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -4,6 +4,7 @@ import ( "path" "testing" + "github.com/bmatcuk/doublestar/v4" "github.com/hashicorp/cli" ) @@ -122,3 +123,58 @@ func TestValidateLegacyWebsite(t *testing.T) { }) } } + +func TestDocumentationDirGlobPattern(t *testing.T) { + testCases := []struct { + Name string + ExpectMatch bool + }{ + { + Name: "docs/data-sources", + ExpectMatch: true, + }, + { + Name: "docs/guides", + ExpectMatch: true, + }, + { + Name: "docs/resources", + ExpectMatch: true, + }, + { + Name: "website/docs/r", + ExpectMatch: true, + }, + { + Name: "website/docs/d", + ExpectMatch: true, + }, + { + Name: "docs/resources/invalid", + ExpectMatch: true, + }, + { + Name: "docs/index.md", + ExpectMatch: false, + }, + { + Name: "docs/invalid", + ExpectMatch: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + + match, err := doublestar.Match(DocumentationDirGlobPattern, testCase.Name) + if err != nil { + t.Fatalf("error matching pattern: %q", err) + } + + if match != testCase.ExpectMatch { + t.Errorf("expected match: %t, got match: %t", testCase.ExpectMatch, match) + } + }) + } + +} From 71b2b7262cfcc049b8e513097e8bf459022d118c Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 12:15:42 -0500 Subject: [PATCH 05/29] Update README.md --- README.md | 23 ++++++++++++++++++++++- internal/cmd/validate.go | 4 ++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f7f8b40b..84f820bf 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Available commands are: the generate command is run by default generate generates a plugin website from code, templates, and examples migrate migrates website files from either the legacy rendered website directory (`website/docs/r`) or the docs rendered website directory (`docs/resources`) to the tfplugindocs supported structure (`templates/`). - validate validates a plugin website for the current directory + validate validates a plugin website ``` @@ -81,6 +81,11 @@ Usage: tfplugindocs generate [] $ tfplugindocs validate --help Usage: tfplugindocs validate [] + + --provider-dir relative or absolute path to the root provider code directory; this will default to the current working directory if not set + --provider-name provider name, as used in Terraform configurations + --providers-schema path to the providers schema JSON file, which contains the output of the terraform providers schema -json command. Setting this flag will skip building the provider and calling Terraform CLI + --tf-version terraform binary version to download. If not provided, will look for a terraform binary in the local environment. If not found in the environment, will download the latest version of Terraform ``` `migrate` command: @@ -145,6 +150,22 @@ Otherwise, the provider developer can set an arbitrary description like this: // ... ``` +#### Validate subcommand + +The `validate` subcommand can be used to validate the provider website documentation against the Terraform Registry's guidelines and provider documentation best practices. The current checks in the validate command are: + +| Check | Description | +|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `InvalidDirectoriesCheck` | Checks for valid subdirectory structure and throws an error if an invalid Terraform Provider documentation subdirectory is found. | +| `MixedDirectoriesCheck` | Throws an error if both legacy documentation (`/website/docs`) and registry documentation (`/docs`) are found. | +| `NumberOfFilesCheck` | Throws an error if the number of files in a directory is larger than the registry limit. | +| `FileSizeCheck` | Throws an error if the documentation file is above the registry storage limit. | +| `FileExtensionCheck` | Throws an error if the extension of the given file is not a valid registry documentation extension. | +| `FrontMatterCheck` | Checks the YAML frontmatter of documentation for missing required fields or invalid fields. | +| `FileMismatchCheck` | Throws an error if the names/number of resources/datasources/functions in the provider schema does not match the names/number of files in the corresponding documentation directory | + +All check errors are wrapped and returned as a single error message to stderr. + #### Migrate subcommand The `migrate` subcommand can be used to migrate website files from either the legacy rendered website directory (`website/docs/r`) or the docs diff --git a/internal/cmd/validate.go b/internal/cmd/validate.go index 4eef2d87..8999c4e8 100644 --- a/internal/cmd/validate.go +++ b/internal/cmd/validate.go @@ -22,7 +22,7 @@ type validateCmd struct { } func (cmd *validateCmd) Synopsis() string { - return "validates a plugin website for the current directory" + return "validates a plugin website" } func (cmd *validateCmd) Help() string { @@ -66,7 +66,7 @@ func (cmd *validateCmd) Help() string { func (cmd *validateCmd) Flags() *flag.FlagSet { fs := flag.NewFlagSet("validate", flag.ExitOnError) fs.StringVar(&cmd.flagProviderName, "provider-name", "", "provider name, as used in Terraform configurations") - fs.StringVar(&cmd.flagProviderDir, "provider-dir", "", "relative or absolute path to the root provider code directory when running the command outside the root provider code directory") + fs.StringVar(&cmd.flagProviderDir, "provider-dir", "", "relative or absolute path to the root provider code directory; this will default to the current working directory if not set") fs.StringVar(&cmd.flagProvidersSchema, "providers-schema", "", "path to the providers schema JSON file, which contains the output of the terraform providers schema -json command. Setting this flag will skip building the provider and calling Terraform CLI") fs.StringVar(&cmd.tfVersion, "tf-version", "", "terraform binary version to download. If not provided, will look for a terraform binary in the local environment. If not found in the environment, will download the latest version of Terraform") return fs From 7a851fdb6e73f5d379f2b3a9baba7adf9e9e3ed3 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 12:16:03 -0500 Subject: [PATCH 06/29] Update documentationDirGlob to pass tests --- internal/provider/template.go | 4 ++-- internal/provider/validate.go | 2 +- internal/provider/validate_test.go | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/internal/provider/template.go b/internal/provider/template.go index ce49935f..10f2e655 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -270,7 +270,7 @@ func (t functionTemplate) Render(providerDir, name, providerName, renderedProvid const defaultResourceTemplate resourceTemplate = `--- ` + frontmatterComment + ` -page_title: "{{.Name}} {{.Type}} - {{.ProviderShortName}}" +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" subcategory: "" description: |- {{ .Description | plainmarkdown | trimspace | prefixlines " " }} @@ -299,7 +299,7 @@ Import is supported using the following syntax: const defaultFunctionTemplate functionTemplate = `--- ` + frontmatterComment + ` -page_title: "{{.Name}} {{.Type}} - {{.ProviderShortName}}" +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" subcategory: "" description: |- {{ .Summary | plainmarkdown | trimspace | prefixlines " " }} diff --git a/internal/provider/validate.go b/internal/provider/validate.go index 4d8ff062..e5e1cd8b 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -24,7 +24,7 @@ const ( FileExtensionMarkdown = `.markdown` FileExtensionMd = `.md` DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources,functions},website/docs}/**/*` - DocumentationDirGlobPattern = `{docs/{,cdktf/}{data-sources,guides,resources,functions}{,/*},website/docs/{d,r,guides,functions}{,/*}}` + DocumentationDirGlobPattern = `{docs/{,cdktf/}{data-sources,guides,resources,functions}{,/*},website/docs/**/*}` ) var ValidLegacyFileExtensions = []string{ diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index 4504550b..667e8184 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -145,10 +145,18 @@ func TestDocumentationDirGlobPattern(t *testing.T) { Name: "website/docs/r", ExpectMatch: true, }, + { + Name: "website/docs/r/invalid", + ExpectMatch: true, + }, { Name: "website/docs/d", ExpectMatch: true, }, + { + Name: "website/docs/invalid", + ExpectMatch: true, + }, { Name: "docs/resources/invalid", ExpectMatch: true, From bbaa51385a7fbf325927e5cbb07ffd215c8d2eb3 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 12:17:44 -0500 Subject: [PATCH 07/29] Add copyright headers --- internal/check/directory.go | 3 +++ internal/check/directory_test.go | 3 +++ internal/check/file.go | 3 +++ internal/check/file_extension.go | 3 +++ internal/check/file_extension_test.go | 3 +++ internal/check/file_mismatch.go | 3 +++ internal/check/file_mismatch_test.go | 3 +++ internal/check/file_test.go | 3 +++ internal/check/frontmatter.go | 3 +++ internal/check/frontmatter_test.go | 3 +++ internal/check/provider_file.go | 3 +++ internal/provider/logger.go | 3 +++ internal/provider/schema.go | 3 +++ internal/provider/validate_test.go | 3 +++ 14 files changed, 42 insertions(+) diff --git a/internal/check/directory.go b/internal/check/directory.go index 653715fa..6b0eb947 100644 --- a/internal/check/directory.go +++ b/internal/check/directory.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index 64c30d37..fb2deb66 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/file.go b/internal/check/file.go index bfc06111..f038f8eb 100644 --- a/internal/check/file.go +++ b/internal/check/file.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/file_extension.go b/internal/check/file_extension.go index 1eea3db0..dd5f37b6 100644 --- a/internal/check/file_extension.go +++ b/internal/check/file_extension.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/file_extension_test.go b/internal/check/file_extension_test.go index 7857d35a..bd3c60e6 100644 --- a/internal/check/file_extension_test.go +++ b/internal/check/file_extension_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/file_mismatch.go b/internal/check/file_mismatch.go index d84a4d60..35e6e3a5 100644 --- a/internal/check/file_mismatch.go +++ b/internal/check/file_mismatch.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/file_mismatch_test.go b/internal/check/file_mismatch_test.go index 7bd3d020..e871e71b 100644 --- a/internal/check/file_mismatch_test.go +++ b/internal/check/file_mismatch_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/file_test.go b/internal/check/file_test.go index 6aa629ae..40c5a263 100644 --- a/internal/check/file_test.go +++ b/internal/check/file_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/frontmatter.go b/internal/check/frontmatter.go index 8adf8d42..5d80c4fd 100644 --- a/internal/check/frontmatter.go +++ b/internal/check/frontmatter.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/frontmatter_test.go b/internal/check/frontmatter_test.go index d6c1a48c..88c58ebd 100644 --- a/internal/check/frontmatter_test.go +++ b/internal/check/frontmatter_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/check/provider_file.go b/internal/check/provider_file.go index 23fca92e..8ebfdca5 100644 --- a/internal/check/provider_file.go +++ b/internal/check/provider_file.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package check import ( diff --git a/internal/provider/logger.go b/internal/provider/logger.go index eff51a31..62e04daf 100644 --- a/internal/provider/logger.go +++ b/internal/provider/logger.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package provider import ( diff --git a/internal/provider/schema.go b/internal/provider/schema.go index 9de293d0..3338fe98 100644 --- a/internal/provider/schema.go +++ b/internal/provider/schema.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package provider import ( diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index 667e8184..8b099b37 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package provider import ( From 31cea0263a11c63e662f23b947b83a767481eae1 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 12:36:03 -0500 Subject: [PATCH 08/29] Resolve linting errors --- internal/check/directory_test.go | 6 ++++++ internal/check/file_extension_test.go | 3 +++ internal/check/file_mismatch_test.go | 23 +++++++++++++++++++++++ internal/check/file_test.go | 8 ++++++++ internal/check/frontmatter_test.go | 4 ++++ 5 files changed, 44 insertions(+) diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index fb2deb66..85d4e90e 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -9,6 +9,7 @@ import ( ) func TestNumberOfFilesCheck(t *testing.T) { + t.Parallel() testCases := []struct { Name string Directories map[string][]string @@ -31,7 +32,9 @@ func TestNumberOfFilesCheck(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() got := NumberOfFilesCheck(testCase.Directories) if got == nil && testCase.ExpectError { @@ -46,6 +49,7 @@ func TestNumberOfFilesCheck(t *testing.T) { } func TestMixedDirectoriesCheck(t *testing.T) { + t.Parallel() testCases := []struct { Name string BasePath string @@ -63,7 +67,9 @@ func TestMixedDirectoriesCheck(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() got := MixedDirectoriesCheck(testCase.BasePath) if got == nil && testCase.ExpectError { diff --git a/internal/check/file_extension_test.go b/internal/check/file_extension_test.go index bd3c60e6..339a3796 100644 --- a/internal/check/file_extension_test.go +++ b/internal/check/file_extension_test.go @@ -8,6 +8,7 @@ import ( ) func TestTrimFileExtension(t *testing.T) { + t.Parallel() testCases := []struct { Name string Path string @@ -41,7 +42,9 @@ func TestTrimFileExtension(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() got := TrimFileExtension(testCase.Path) want := testCase.Expect diff --git a/internal/check/file_mismatch_test.go b/internal/check/file_mismatch_test.go index e871e71b..540c2d21 100644 --- a/internal/check/file_mismatch_test.go +++ b/internal/check/file_mismatch_test.go @@ -12,6 +12,7 @@ import ( ) func TestFileHasResource(t *testing.T) { + t.Parallel() testCases := []struct { Name string File string @@ -39,7 +40,10 @@ func TestFileHasResource(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + got := fileHasResource(testCase.Resources, "test", testCase.File) want := testCase.Expect @@ -51,6 +55,7 @@ func TestFileHasResource(t *testing.T) { } func TestFileResourceName(t *testing.T) { + t.Parallel() testCases := []struct { Name string File string @@ -79,7 +84,9 @@ func TestFileResourceName(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() got := fileResourceName("test", testCase.File) want := testCase.Expect @@ -91,6 +98,7 @@ func TestFileResourceName(t *testing.T) { } func TestFileMismatchCheck(t *testing.T) { + t.Parallel() testCases := []struct { Name string ResourceFiles fstest.MapFS @@ -298,7 +306,10 @@ func TestFileMismatchCheck(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + resourceFiles, _ := testCase.ResourceFiles.ReadDir(".") functionFiles, _ := testCase.FunctionFiles.ReadDir(".") testCase.Options.ResourceEntries = resourceFiles @@ -317,6 +328,7 @@ func TestFileMismatchCheck(t *testing.T) { } func TestResourceHasFile(t *testing.T) { + t.Parallel() testCases := []struct { Name string FS fstest.MapFS @@ -344,7 +356,10 @@ func TestResourceHasFile(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + files, _ := testCase.FS.ReadDir(".") got := resourceHasFile(files, "test", testCase.ResourceName) @@ -358,6 +373,7 @@ func TestResourceHasFile(t *testing.T) { } func TestFunctionHasFile(t *testing.T) { + t.Parallel() testCases := []struct { Name string FS fstest.MapFS @@ -385,7 +401,10 @@ func TestFunctionHasFile(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + files, _ := testCase.FS.ReadDir(".") got := functionHasFile(files, testCase.FunctionName) @@ -399,6 +418,7 @@ func TestFunctionHasFile(t *testing.T) { } func TestResourceNames(t *testing.T) { + t.Parallel() testCases := []struct { Name string Resources map[string]*tfjson.Schema @@ -423,7 +443,10 @@ func TestResourceNames(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + got := resourceNames(testCase.Resources) want := testCase.Expect diff --git a/internal/check/file_test.go b/internal/check/file_test.go index 40c5a263..92d83239 100644 --- a/internal/check/file_test.go +++ b/internal/check/file_test.go @@ -9,6 +9,7 @@ import ( ) func TestFileSizeCheck(t *testing.T) { + t.Parallel() testCases := []struct { Name string Size int64 @@ -31,7 +32,10 @@ func TestFileSizeCheck(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + file, err := os.CreateTemp(os.TempDir(), "TestFileSizeCheck") if err != nil { @@ -58,6 +62,7 @@ func TestFileSizeCheck(t *testing.T) { } func TestFullPath(t *testing.T) { + t.Parallel() testCases := []struct { Name string FileOptions *FileOptions @@ -81,7 +86,10 @@ func TestFullPath(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + got := testCase.FileOptions.FullPath(testCase.Path) want := testCase.Expect diff --git a/internal/check/frontmatter_test.go b/internal/check/frontmatter_test.go index 88c58ebd..7f49eb1c 100644 --- a/internal/check/frontmatter_test.go +++ b/internal/check/frontmatter_test.go @@ -8,6 +8,7 @@ import ( ) func TestFrontMatterCheck(t *testing.T) { + t.Parallel() testCases := []struct { Name string Source string @@ -149,7 +150,10 @@ subcategory: Example Subcategory } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() + got := NewFrontMatterCheck(testCase.Options).Run([]byte(testCase.Source)) if got == nil && testCase.ExpectError { From ffb595990c812c7b6fae06d67f334f3aafdbb8a3 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 12:41:34 -0500 Subject: [PATCH 09/29] Update README.md with a link to provider documentation guidelines --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84f820bf..66227625 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ Otherwise, the provider developer can set an arbitrary description like this: #### Validate subcommand -The `validate` subcommand can be used to validate the provider website documentation against the Terraform Registry's guidelines and provider documentation best practices. The current checks in the validate command are: +The `validate` subcommand can be used to validate the provider website documentation against the [Terraform Registry's provider documentation guidelines](https://developer.hashicorp.com/terraform/registry/providers/docs) and provider documentation best practices. The current checks in the validate command are: | Check | Description | |---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| From 53a168f7391155fd7682c5b5ee6fedaee97df245 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 13:52:17 -0500 Subject: [PATCH 10/29] Resolve linting errors --- README.md | 2 +- internal/check/directory_test.go | 1 + internal/provider/logger.go | 1 + internal/provider/util.go | 1 + internal/provider/validate.go | 8 ++++++++ internal/provider/validate_test.go | 9 +++++++++ schemamd/behaviors_test.go | 1 - 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66227625..fc2f3895 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ Otherwise, the provider developer can set an arbitrary description like this: #### Validate subcommand -The `validate` subcommand can be used to validate the provider website documentation against the [Terraform Registry's provider documentation guidelines](https://developer.hashicorp.com/terraform/registry/providers/docs) and provider documentation best practices. The current checks in the validate command are: +The `validate` subcommand can be used to validate the provider website documentation against the [Terraform Registry's provider documentation guidelines](https://developer.hashicorp.com/terraform/registry/providers/docs) and provider documentation best practices. The current checks in the `validate` command are: | Check | Description | |---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index 85d4e90e..9d95a5d5 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -70,6 +70,7 @@ func TestMixedDirectoriesCheck(t *testing.T) { testCase := testCase t.Run(testCase.Name, func(t *testing.T) { t.Parallel() + got := MixedDirectoriesCheck(testCase.BasePath) if got == nil && testCase.ExpectError { diff --git a/internal/provider/logger.go b/internal/provider/logger.go index 62e04daf..366812bc 100644 --- a/internal/provider/logger.go +++ b/internal/provider/logger.go @@ -21,6 +21,7 @@ func (l *Logger) infof(format string, args ...interface{}) { l.ui.Info(fmt.Sprintf(format, args...)) } +//nolint:unused func (l *Logger) warnf(format string, args ...interface{}) { l.ui.Warn(fmt.Sprintf(format, args...)) } diff --git a/internal/provider/util.go b/internal/provider/util.go index 9bc50528..7a3ec336 100644 --- a/internal/provider/util.go +++ b/internal/provider/util.go @@ -103,6 +103,7 @@ func writeFile(path string, data string) error { return nil } +//nolint:unparam func runCmd(cmd *exec.Cmd) ([]byte, error) { output, err := cmd.CombinedOutput() if err != nil { diff --git a/internal/provider/validate.go b/internal/provider/validate.go index e5e1cd8b..a89363b7 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -143,6 +143,10 @@ func (v *validator) validateStaticDocs(dir string) error { var files []string err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("error walking directory %q: %w", dir, err) + } + rel, err := filepath.Rel(v.providerDir, path) if err != nil { return err @@ -223,6 +227,10 @@ func (v *validator) validateLegacyWebsite(dir string) error { var files []string err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("error walking directory %q: %w", dir, err) + } + rel, err := filepath.Rel(v.providerDir, path) if err != nil { return err diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index 8b099b37..cf5bff47 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -28,6 +28,7 @@ func TestValidator_validate(t *testing.T) { } func TestValidateStaticDocs(t *testing.T) { + t.Parallel() testCases := []struct { Name string BasePath string @@ -51,7 +52,9 @@ func TestValidateStaticDocs(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() v := &validator{ providerDir: testCase.BasePath, @@ -78,6 +81,7 @@ func TestValidateStaticDocs(t *testing.T) { } func TestValidateLegacyWebsite(t *testing.T) { + t.Parallel() testCases := []struct { Name string BasePath string @@ -101,7 +105,9 @@ func TestValidateLegacyWebsite(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() v := &validator{ providerDir: testCase.BasePath, @@ -128,6 +134,7 @@ func TestValidateLegacyWebsite(t *testing.T) { } func TestDocumentationDirGlobPattern(t *testing.T) { + t.Parallel() testCases := []struct { Name string ExpectMatch bool @@ -175,7 +182,9 @@ func TestDocumentationDirGlobPattern(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.Name, func(t *testing.T) { + t.Parallel() match, err := doublestar.Match(DocumentationDirGlobPattern, testCase.Name) if err != nil { diff --git a/schemamd/behaviors_test.go b/schemamd/behaviors_test.go index cdc93af2..48e03336 100644 --- a/schemamd/behaviors_test.go +++ b/schemamd/behaviors_test.go @@ -13,7 +13,6 @@ import ( func TestChildAttributeIsRequired(t *testing.T) { t.Parallel() - for _, c := range []struct { name string att *tfjson.SchemaAttribute From 57bb756ed843b7e921b242a1651550c3cc078e07 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 14:10:25 -0500 Subject: [PATCH 11/29] Add changelog entries --- .changes/unreleased/FEATURES-20240305-135426.yaml | 5 +++++ .changes/unreleased/FEATURES-20240305-135726.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240305-135933.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240305-140106.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240305-140234.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240305-140346.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240305-140451.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240305-140622.yaml | 6 ++++++ 8 files changed, 47 insertions(+) create mode 100644 .changes/unreleased/FEATURES-20240305-135426.yaml create mode 100644 .changes/unreleased/FEATURES-20240305-135726.yaml create mode 100644 .changes/unreleased/FEATURES-20240305-135933.yaml create mode 100644 .changes/unreleased/FEATURES-20240305-140106.yaml create mode 100644 .changes/unreleased/FEATURES-20240305-140234.yaml create mode 100644 .changes/unreleased/FEATURES-20240305-140346.yaml create mode 100644 .changes/unreleased/FEATURES-20240305-140451.yaml create mode 100644 .changes/unreleased/FEATURES-20240305-140622.yaml diff --git a/.changes/unreleased/FEATURES-20240305-135426.yaml b/.changes/unreleased/FEATURES-20240305-135426.yaml new file mode 100644 index 00000000..50abd474 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240305-135426.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'validate: Add support for Provider-defined Function documentation' +time: 2024-03-05T13:54:26.307742-05:00 +custom: + Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-135726.yaml b/.changes/unreleased/FEATURES-20240305-135726.yaml new file mode 100644 index 00000000..e26b8557 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240305-135726.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'validate: Add `InvalidDirectoriesCheck` which checks for valid provider documentation + structure' +time: 2024-03-05T13:57:26.273538-05:00 +custom: + Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-135933.yaml b/.changes/unreleased/FEATURES-20240305-135933.yaml new file mode 100644 index 00000000..825d2f9c --- /dev/null +++ b/.changes/unreleased/FEATURES-20240305-135933.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'validate: Add `MixedDirectoriesCheck` which throws an error if both legacy + documentation and registry documentation are found' +time: 2024-03-05T13:59:33.741601-05:00 +custom: + Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-140106.yaml b/.changes/unreleased/FEATURES-20240305-140106.yaml new file mode 100644 index 00000000..3ca53585 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240305-140106.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'validate: Add `NumberOfFilesCheck` which checks the number of provider + documentation files against the registry limit' +time: 2024-03-05T14:01:06.742843-05:00 +custom: + Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-140234.yaml b/.changes/unreleased/FEATURES-20240305-140234.yaml new file mode 100644 index 00000000..083639e2 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240305-140234.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'validate: Add `FileSizeCheck` which checks the provider documentation file + size against the registry limit' +time: 2024-03-05T14:02:34.112782-05:00 +custom: + Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-140346.yaml b/.changes/unreleased/FEATURES-20240305-140346.yaml new file mode 100644 index 00000000..1f440228 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240305-140346.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'validate: Add `FileExtensionCheck` which checks for valid provider documentation + file extensions' +time: 2024-03-05T14:03:46.816256-05:00 +custom: + Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-140451.yaml b/.changes/unreleased/FEATURES-20240305-140451.yaml new file mode 100644 index 00000000..e5d15828 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240305-140451.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'validate: Add `FrontMatterCheck` which checks the YAML frontmatter of provider + documentation for missing required fields or invalid fields' +time: 2024-03-05T14:04:51.781688-05:00 +custom: + Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-140622.yaml b/.changes/unreleased/FEATURES-20240305-140622.yaml new file mode 100644 index 00000000..57ab3a7c --- /dev/null +++ b/.changes/unreleased/FEATURES-20240305-140622.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'validate: Add `FileMismatchCheck` which checks the names/number of provider + documentation files against the provider schema' +time: 2024-03-05T14:06:22.168518-05:00 +custom: + Issue: "341" From 86eb271c0a4e6f92d7ad9ede23becb7a197ecdc4 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 14:54:28 -0500 Subject: [PATCH 12/29] Add `NumberOfFilesCheck` to validate --- internal/check/directory.go | 35 ++++++++++++++++---------------- internal/check/directory_test.go | 32 +++++++++++++++++------------ internal/provider/validate.go | 12 ++++++++++- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/internal/check/directory.go b/internal/check/directory.go index 6b0eb947..07e46857 100644 --- a/internal/check/directory.go +++ b/internal/check/directory.go @@ -6,10 +6,7 @@ package check import ( "fmt" "log" - "os" "path/filepath" - - "github.com/bmatcuk/doublestar/v4" ) const ( @@ -91,19 +88,12 @@ func InvalidDirectoriesCheck(dirPath string) error { } -func MixedDirectoriesCheck(providerDir string) error { +func MixedDirectoriesCheck(docFiles []string) error { var legacyDirectoryFound bool var registryDirectoryFound bool err := fmt.Errorf("mixed Terraform Provider documentation directory layouts found, must use only legacy or registry layout") - providerFs := os.DirFS(providerDir) - - files, globErr := doublestar.Glob(providerFs, DocumentationGlobPattern) - if globErr != nil { - return fmt.Errorf("error finding documentation files: %w", err) - } - - for _, file := range files { + for _, file := range docFiles { directory := filepath.Dir(file) // Allow docs/ with other files @@ -130,18 +120,29 @@ func MixedDirectoriesCheck(providerDir string) error { // NumberOfFilesCheck verifies that documentation is below the Terraform Registry storage limit. // This check presumes that all provided directories are valid, e.g. that directory checking // for invalid or mixed directory structures was previously completed. -func NumberOfFilesCheck(directories map[string][]string) error { +func NumberOfFilesCheck(docFiles []string) error { var numberOfFiles int - for directory, files := range directories { + directoryCounts := make(map[string]int) + for _, file := range docFiles { + directory := filepath.Dir(file) + // Ignore CDKTF files. The file limit is per-language and presumably there is one CDKTF file per source HCL file. if IsValidCdktfDirectory(directory) { continue } - directoryNumberOfFiles := len(files) - log.Printf("[TRACE] Found %d documentation files in directory: %s", directoryNumberOfFiles, directory) - numberOfFiles = numberOfFiles + directoryNumberOfFiles + if directory == RegistryIndexDirectory || directory == LegacyIndexDirectory { + continue + } + + directoryCounts[directory]++ + } + + for directory, count := range directoryCounts { + + log.Printf("[TRACE] Found %d documentation files in directory: %s", count, directory) + numberOfFiles = numberOfFiles + count } log.Printf("[DEBUG] Found %d documentation files with limit of %d", numberOfFiles, RegistryMaximumNumberOfFiles) diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index 9d95a5d5..d6b84249 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -5,28 +5,31 @@ package check import ( "fmt" + "os" "testing" + + "github.com/bmatcuk/doublestar/v4" ) func TestNumberOfFilesCheck(t *testing.T) { t.Parallel() testCases := []struct { Name string - Directories map[string][]string + files []string ExpectError bool }{ { - Name: "under limit", - Directories: testGenerateDirectories(RegistryMaximumNumberOfFiles - 1), + Name: "under limit", + files: testGenerateFiles(RegistryMaximumNumberOfFiles - 1), }, { Name: "at limit", - Directories: testGenerateDirectories(RegistryMaximumNumberOfFiles), + files: testGenerateFiles(RegistryMaximumNumberOfFiles), ExpectError: true, }, { Name: "over limit", - Directories: testGenerateDirectories(RegistryMaximumNumberOfFiles + 1), + files: testGenerateFiles(RegistryMaximumNumberOfFiles + 1), ExpectError: true, }, } @@ -35,7 +38,7 @@ func TestNumberOfFilesCheck(t *testing.T) { testCase := testCase t.Run(testCase.Name, func(t *testing.T) { t.Parallel() - got := NumberOfFilesCheck(testCase.Directories) + got := NumberOfFilesCheck(testCase.files) if got == nil && testCase.ExpectError { t.Errorf("expected error, got no error") @@ -71,7 +74,14 @@ func TestMixedDirectoriesCheck(t *testing.T) { t.Run(testCase.Name, func(t *testing.T) { t.Parallel() - got := MixedDirectoriesCheck(testCase.BasePath) + providerFs := os.DirFS(testCase.BasePath) + + files, err := doublestar.Glob(providerFs, DocumentationGlobPattern) + if err != nil { + t.Fatalf("error finding documentation files: %s", err) + } + + got := MixedDirectoriesCheck(files) if got == nil && testCase.ExpectError { t.Errorf("expected error, got no error") @@ -84,16 +94,12 @@ func TestMixedDirectoriesCheck(t *testing.T) { } } -func testGenerateDirectories(numberOfFiles int) map[string][]string { +func testGenerateFiles(numberOfFiles int) []string { files := make([]string, numberOfFiles) for i := 0; i < numberOfFiles; i++ { files[i] = fmt.Sprintf("thing%d.md", i) } - directories := map[string][]string{ - "docs/resources": files, - } - - return directories + return files } diff --git a/internal/provider/validate.go b/internal/provider/validate.go index a89363b7..9740d901 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -113,7 +113,17 @@ func (v *validator) validate(ctx context.Context) error { } } - err = check.MixedDirectoriesCheck(v.providerDir) + providerFs := os.DirFS(v.providerDir) + + files, globErr := doublestar.Glob(providerFs, DocumentationGlobPattern) + if globErr != nil { + return fmt.Errorf("error finding documentation files: %w", err) + } + + err = check.MixedDirectoriesCheck(files) + result = errors.Join(result, err) + + err = check.NumberOfFilesCheck(files) result = errors.Join(result, err) if dirExists(path.Join(v.providerDir, "docs")) { From 6fefacb18e974881a4035dc56157f13d2f395e0b Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 15:56:54 -0500 Subject: [PATCH 13/29] Make file paths OS-agnostic --- internal/check/directory.go | 18 +++++++++--------- internal/check/directory_test.go | 5 +++-- internal/check/file_mismatch_test.go | 5 +++-- internal/check/file_test.go | 13 +++++++------ internal/provider/generate.go | 5 ++--- internal/provider/validate.go | 17 ++++++++--------- internal/provider/validate_test.go | 22 +++++++++++----------- 7 files changed, 43 insertions(+), 42 deletions(-) diff --git a/internal/check/directory.go b/internal/check/directory.go index 07e46857..18c0d1c4 100644 --- a/internal/check/directory.go +++ b/internal/check/directory.go @@ -132,7 +132,7 @@ func NumberOfFilesCheck(docFiles []string) error { continue } - if directory == RegistryIndexDirectory || directory == LegacyIndexDirectory { + if directory == RegistryIndexDirectory || directory == filepath.FromSlash(LegacyIndexDirectory) { continue } @@ -155,7 +155,7 @@ func NumberOfFilesCheck(docFiles []string) error { func IsValidLegacyDirectory(directory string) bool { for _, validLegacyDirectory := range ValidLegacyDirectories { - if directory == validLegacyDirectory { + if directory == filepath.FromSlash(validLegacyDirectory) { return true } } @@ -165,7 +165,7 @@ func IsValidLegacyDirectory(directory string) bool { func IsValidRegistryDirectory(directory string) bool { for _, validRegistryDirectory := range ValidRegistryDirectories { - if directory == validRegistryDirectory { + if directory == filepath.FromSlash(validRegistryDirectory) { return true } } @@ -174,32 +174,32 @@ func IsValidRegistryDirectory(directory string) bool { } func IsValidCdktfDirectory(directory string) bool { - if directory == fmt.Sprintf("%s/%s", LegacyIndexDirectory, CdktfIndexDirectory) { + if directory == filepath.FromSlash(fmt.Sprintf("%s/%s", LegacyIndexDirectory, CdktfIndexDirectory)) { return true } - if directory == fmt.Sprintf("%s/%s", RegistryIndexDirectory, CdktfIndexDirectory) { + if directory == filepath.FromSlash(fmt.Sprintf("%s/%s", RegistryIndexDirectory, CdktfIndexDirectory)) { return true } for _, validCdktfLanguage := range ValidCdktfLanguages { - if directory == fmt.Sprintf("%s/%s/%s", LegacyIndexDirectory, CdktfIndexDirectory, validCdktfLanguage) { + if directory == filepath.FromSlash(fmt.Sprintf("%s/%s/%s", LegacyIndexDirectory, CdktfIndexDirectory, validCdktfLanguage)) { return true } - if directory == fmt.Sprintf("%s/%s/%s", RegistryIndexDirectory, CdktfIndexDirectory, validCdktfLanguage) { + if directory == filepath.FromSlash(fmt.Sprintf("%s/%s/%s", RegistryIndexDirectory, CdktfIndexDirectory, validCdktfLanguage)) { return true } for _, validLegacySubdirectory := range ValidLegacySubdirectories { - if directory == fmt.Sprintf("%s/%s/%s/%s", LegacyIndexDirectory, CdktfIndexDirectory, validCdktfLanguage, validLegacySubdirectory) { + if directory == filepath.FromSlash(fmt.Sprintf("%s/%s/%s/%s", LegacyIndexDirectory, CdktfIndexDirectory, validCdktfLanguage, validLegacySubdirectory)) { return true } } for _, validRegistrySubdirectory := range ValidRegistrySubdirectories { - if directory == fmt.Sprintf("%s/%s/%s/%s", RegistryIndexDirectory, CdktfIndexDirectory, validCdktfLanguage, validRegistrySubdirectory) { + if directory == filepath.FromSlash(fmt.Sprintf("%s/%s/%s/%s", RegistryIndexDirectory, CdktfIndexDirectory, validCdktfLanguage, validRegistrySubdirectory)) { return true } } diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index d6b84249..c5e0849f 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -6,6 +6,7 @@ package check import ( "fmt" "os" + "path/filepath" "testing" "github.com/bmatcuk/doublestar/v4" @@ -60,11 +61,11 @@ func TestMixedDirectoriesCheck(t *testing.T) { }{ { Name: "valid mixed directories", - BasePath: "testdata/valid-mixed-directories", + BasePath: filepath.Join("testdata", "valid-mixed-directories"), }, { Name: "invalid mixed directories", - BasePath: "testdata/invalid-mixed-directories", + BasePath: filepath.Join("testdata", "invalid-mixed-directories"), ExpectError: true, }, } diff --git a/internal/check/file_mismatch_test.go b/internal/check/file_mismatch_test.go index 540c2d21..85e3d1c7 100644 --- a/internal/check/file_mismatch_test.go +++ b/internal/check/file_mismatch_test.go @@ -4,6 +4,7 @@ package check import ( + "path/filepath" "reflect" "testing" "testing/fstest" @@ -73,12 +74,12 @@ func TestFileResourceName(t *testing.T) { }, { Name: "full path with single extensions", - File: "docs/resource/thing.md", + File: filepath.Join("docs", "resource", "thing.md"), Expect: "test_thing", }, { Name: "full path with multiple extensions", - File: "website/docs/r/thing.html.markdown", + File: filepath.Join("website", "docs", "r", "thing.html.markdown"), Expect: "test_thing", }, } diff --git a/internal/check/file_test.go b/internal/check/file_test.go index 92d83239..badade4b 100644 --- a/internal/check/file_test.go +++ b/internal/check/file_test.go @@ -5,6 +5,7 @@ package check import ( "os" + "path/filepath" "testing" ) @@ -72,16 +73,16 @@ func TestFullPath(t *testing.T) { { Name: "without base path", FileOptions: &FileOptions{}, - Path: "docs/resources/thing.md", - Expect: "docs/resources/thing.md", + Path: filepath.FromSlash("docs/resources/thing.md"), + Expect: filepath.FromSlash("docs/resources/thing.md"), }, { - Name: "without base path", + Name: "with base path", FileOptions: &FileOptions{ - BasePath: "/full/path/to", + BasePath: filepath.FromSlash("/full/path/to"), }, - Path: "docs/resources/thing.md", - Expect: "/full/path/to/docs/resources/thing.md", + Path: filepath.FromSlash("docs/resources/thing.md"), + Expect: filepath.FromSlash("/full/path/to/docs/resources/thing.md"), }, } diff --git a/internal/provider/generate.go b/internal/provider/generate.go index d0c53a54..d0c3c965 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -8,7 +8,6 @@ import ( "fmt" "os" "os/exec" - "path" "path/filepath" "runtime" "strings" @@ -455,7 +454,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e // Remove subdirectories managed by tfplugindocs if file.IsDir() && slices.Contains(managedWebsiteSubDirectories, file.Name()) { g.infof("removing directory: %q", file.Name()) - err = os.RemoveAll(path.Join(g.ProviderDocsDir(), file.Name())) + err = os.RemoveAll(filepath.Join(g.ProviderDocsDir(), file.Name())) if err != nil { return fmt.Errorf("unable to remove directory %q from rendered website directory: %w", file.Name(), err) } @@ -465,7 +464,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e // Remove files managed by tfplugindocs if !file.IsDir() && slices.Contains(managedWebsiteFiles, file.Name()) { g.infof("removing file: %q", file.Name()) - err = os.RemoveAll(path.Join(g.ProviderDocsDir(), file.Name())) + err = os.RemoveAll(filepath.Join(g.ProviderDocsDir(), file.Name())) if err != nil { return fmt.Errorf("unable to remove file %q from rendered website directory: %w", file.Name(), err) } diff --git a/internal/provider/validate.go b/internal/provider/validate.go index 9740d901..6f9873fb 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "os" - "path" "path/filepath" "github.com/bmatcuk/doublestar/v4" @@ -126,15 +125,15 @@ func (v *validator) validate(ctx context.Context) error { err = check.NumberOfFilesCheck(files) result = errors.Join(result, err) - if dirExists(path.Join(v.providerDir, "docs")) { + if dirExists(filepath.Join(v.providerDir, "docs")) { v.logger.infof("detected static docs directory, running checks") - err = v.validateStaticDocs(path.Join(v.providerDir, "docs")) + err = v.validateStaticDocs(filepath.Join(v.providerDir, "docs")) result = errors.Join(result, err) } - if dirExists(path.Join(v.providerDir, "website/docs")) { + if dirExists(filepath.Join(v.providerDir, filepath.Join("website", "docs"))) { v.logger.infof("detected legacy website directory, running checks") - err = v.validateLegacyWebsite(path.Join(v.providerDir, "website/docs")) + err = v.validateLegacyWebsite(filepath.Join(v.providerDir, "website/docs")) result = errors.Join(result, err) } @@ -162,7 +161,7 @@ func (v *validator) validateStaticDocs(dir string) error { return err } if d.IsDir() { - match, err := doublestar.Match(DocumentationDirGlobPattern, rel) + match, err := doublestar.PathMatch(filepath.FromSlash(DocumentationDirGlobPattern), rel) if err != nil { return err } @@ -174,7 +173,7 @@ func (v *validator) validateStaticDocs(dir string) error { result = errors.Join(result, check.InvalidDirectoriesCheck(rel)) return nil } - match, err := doublestar.Match(DocumentationGlobPattern, rel) + match, err := doublestar.PathMatch(filepath.FromSlash(DocumentationGlobPattern), rel) if err != nil { return err } @@ -246,7 +245,7 @@ func (v *validator) validateLegacyWebsite(dir string) error { return err } if d.IsDir() { - match, err := doublestar.Match(DocumentationDirGlobPattern, rel) + match, err := doublestar.PathMatch(filepath.FromSlash(DocumentationDirGlobPattern), rel) if err != nil { return err } @@ -259,7 +258,7 @@ func (v *validator) validateLegacyWebsite(dir string) error { return nil } - match, err := doublestar.Match(DocumentationGlobPattern, rel) + match, err := doublestar.PathMatch(filepath.FromSlash(DocumentationGlobPattern), rel) if err != nil { return err } diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index cf5bff47..76d627e7 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -4,7 +4,7 @@ package provider import ( - "path" + "path/filepath" "testing" "github.com/bmatcuk/doublestar/v4" @@ -15,13 +15,13 @@ func TestValidator_validate(t *testing.T) { t.Parallel() v := &validator{ - providerDir: "testdata/valid-registry-directories", + providerDir: filepath.Join("testdata", "valid-registry-directories"), providerName: "terraform-provider-null", logger: NewLogger(cli.NewMockUi()), } - err := v.validateStaticDocs(path.Join(v.providerDir, "docs")) + err := v.validateStaticDocs(filepath.Join(v.providerDir, "docs")) if err != nil { t.Fatalf("error retrieving schema: %q", err) } @@ -37,15 +37,15 @@ func TestValidateStaticDocs(t *testing.T) { }{ { Name: "valid registry directories", - BasePath: "testdata/valid-registry-directories", + BasePath: filepath.Join("testdata", "valid-registry-directories"), }, { Name: "valid registry directories with cdktf docs", - BasePath: "testdata/valid-registry-directories-with-cdktf", + BasePath: filepath.Join("testdata", "valid-registry-directories-with-cdktf"), }, { Name: "invalid registry directories", - BasePath: "testdata/invalid-registry-directories", + BasePath: filepath.Join("testdata", "invalid-registry-directories"), ExpectError: true, ExpectedError: "invalid Terraform Provider documentation directory found: docs/resources/invalid", }, @@ -63,7 +63,7 @@ func TestValidateStaticDocs(t *testing.T) { logger: NewLogger(cli.NewMockUi()), } - got := v.validateStaticDocs(path.Join(v.providerDir, "docs")) + got := v.validateStaticDocs(filepath.Join(v.providerDir, "docs")) if got == nil && testCase.ExpectError { t.Errorf("expected error, got no error") @@ -90,15 +90,15 @@ func TestValidateLegacyWebsite(t *testing.T) { }{ { Name: "valid legacy directories", - BasePath: "testdata/valid-legacy-directories", + BasePath: filepath.Join("testdata", "valid-legacy-directories"), }, { Name: "valid legacy directories with cdktf docs", - BasePath: "testdata/valid-legacy-directories-with-cdktf", + BasePath: filepath.Join("testdata", "valid-legacy-directories-with-cdktf"), }, { Name: "invalid legacy directories", - BasePath: "testdata/invalid-legacy-directories", + BasePath: filepath.Join("testdata", "invalid-legacy-directories"), ExpectError: true, ExpectedError: "invalid Terraform Provider documentation directory found: website/docs/r/invalid", }, @@ -116,7 +116,7 @@ func TestValidateLegacyWebsite(t *testing.T) { logger: NewLogger(cli.NewMockUi()), } - got := v.validateLegacyWebsite(path.Join(v.providerDir, "website")) + got := v.validateLegacyWebsite(filepath.Join(v.providerDir, "website")) if got == nil && testCase.ExpectError { t.Errorf("expected error, got no error") From 7e9d98aa5eb27e3ee2d5421780e7058a0106ba17 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 16:12:00 -0500 Subject: [PATCH 14/29] Fix file path in test error assertion --- internal/provider/validate_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index 76d627e7..03c5e3fc 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -47,7 +47,7 @@ func TestValidateStaticDocs(t *testing.T) { Name: "invalid registry directories", BasePath: filepath.Join("testdata", "invalid-registry-directories"), ExpectError: true, - ExpectedError: "invalid Terraform Provider documentation directory found: docs/resources/invalid", + ExpectedError: "invalid Terraform Provider documentation directory found: " + filepath.Join("docs", "resources", "invalid"), }, } @@ -100,7 +100,7 @@ func TestValidateLegacyWebsite(t *testing.T) { Name: "invalid legacy directories", BasePath: filepath.Join("testdata", "invalid-legacy-directories"), ExpectError: true, - ExpectedError: "invalid Terraform Provider documentation directory found: website/docs/r/invalid", + ExpectedError: "invalid Terraform Provider documentation directory found: " + filepath.Join("website", "docs", "r", "invalid"), }, } From 0fd871a6a9bb4e7c11ec3e666b7dcfd943ab51d5 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 5 Mar 2024 17:01:00 -0500 Subject: [PATCH 15/29] Update Go version to `1.21` --- README.md | 6 ++++++ go.mod | 2 +- go.sum | 20 ++++++++++++++++++++ tools/go.mod | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fc2f3895..df5c19c6 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,12 @@ Your help and patience is truly appreciated. ## Contributing +## Go Compatibility + +This project follows the [support policy](https://golang.org/doc/devel/release.html#policy) of Go as its support policy. The two latest major releases of Go are supported by the project. + +Currently, that means Go **1.21** or later must be used when including this project as a dependency. + ### License Headers All source code files in this repository (excluding autogenerated files like `go.mod`, prose, and files excluded in [.copywrite.hcl](.copywrite.hcl)) must have a license header at the top. diff --git a/go.mod b/go.mod index 426b05a7..33ad3247 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform-plugin-docs -go 1.20 +go 1.21 require ( github.com/Kunde21/markdownfmt/v3 v3.1.0 diff --git a/go.sum b/go.sum index 7d853a37..8ae48fa2 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -8,6 +9,7 @@ github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYr github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0= github.com/ProtonMail/go-crypto v1.1.0-alpha.0/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= @@ -21,17 +23,24 @@ github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTS github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -67,9 +76,13 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -84,6 +97,7 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= @@ -91,10 +105,12 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= @@ -103,7 +119,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= @@ -126,6 +144,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -156,6 +175,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/tools/go.mod b/tools/go.mod index 12fc6bfb..6f27030f 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,6 +1,6 @@ module tools -go 1.19 +go 1.21 require github.com/hashicorp/copywrite v0.18.0 From b2eef9244d9cc3e3b67d35b466af8d6ea7a59c61 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 13 Mar 2024 16:38:16 -0400 Subject: [PATCH 16/29] Change wording to past tense on changelog entries --- .changes/unreleased/FEATURES-20240305-135426.yaml | 2 +- .changes/unreleased/FEATURES-20240305-135726.yaml | 4 ++-- .changes/unreleased/FEATURES-20240305-135933.yaml | 2 +- .changes/unreleased/FEATURES-20240305-140106.yaml | 2 +- .changes/unreleased/FEATURES-20240305-140234.yaml | 2 +- .changes/unreleased/FEATURES-20240305-140346.yaml | 2 +- .changes/unreleased/FEATURES-20240305-140451.yaml | 2 +- .changes/unreleased/FEATURES-20240305-140622.yaml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.changes/unreleased/FEATURES-20240305-135426.yaml b/.changes/unreleased/FEATURES-20240305-135426.yaml index 50abd474..12267b80 100644 --- a/.changes/unreleased/FEATURES-20240305-135426.yaml +++ b/.changes/unreleased/FEATURES-20240305-135426.yaml @@ -1,5 +1,5 @@ kind: FEATURES -body: 'validate: Add support for Provider-defined Function documentation' +body: 'validate: Added support for Provider-defined Function documentation to all checks' time: 2024-03-05T13:54:26.307742-05:00 custom: Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-135726.yaml b/.changes/unreleased/FEATURES-20240305-135726.yaml index e26b8557..fcd2dcab 100644 --- a/.changes/unreleased/FEATURES-20240305-135726.yaml +++ b/.changes/unreleased/FEATURES-20240305-135726.yaml @@ -1,6 +1,6 @@ kind: FEATURES -body: 'validate: Add `InvalidDirectoriesCheck` which checks for valid provider documentation - structure' +body: 'validate: Added `InvalidDirectoriesCheck` which checks for valid provider documentation + folder structure' time: 2024-03-05T13:57:26.273538-05:00 custom: Issue: "341" diff --git a/.changes/unreleased/FEATURES-20240305-135933.yaml b/.changes/unreleased/FEATURES-20240305-135933.yaml index 825d2f9c..3660390d 100644 --- a/.changes/unreleased/FEATURES-20240305-135933.yaml +++ b/.changes/unreleased/FEATURES-20240305-135933.yaml @@ -1,5 +1,5 @@ kind: FEATURES -body: 'validate: Add `MixedDirectoriesCheck` which throws an error if both legacy +body: 'validate: Added `MixedDirectoriesCheck` which throws an error if both legacy documentation and registry documentation are found' time: 2024-03-05T13:59:33.741601-05:00 custom: diff --git a/.changes/unreleased/FEATURES-20240305-140106.yaml b/.changes/unreleased/FEATURES-20240305-140106.yaml index 3ca53585..95e24909 100644 --- a/.changes/unreleased/FEATURES-20240305-140106.yaml +++ b/.changes/unreleased/FEATURES-20240305-140106.yaml @@ -1,5 +1,5 @@ kind: FEATURES -body: 'validate: Add `NumberOfFilesCheck` which checks the number of provider +body: 'validate: Added `NumberOfFilesCheck` which checks the number of provider documentation files against the registry limit' time: 2024-03-05T14:01:06.742843-05:00 custom: diff --git a/.changes/unreleased/FEATURES-20240305-140234.yaml b/.changes/unreleased/FEATURES-20240305-140234.yaml index 083639e2..1fbb4aa1 100644 --- a/.changes/unreleased/FEATURES-20240305-140234.yaml +++ b/.changes/unreleased/FEATURES-20240305-140234.yaml @@ -1,5 +1,5 @@ kind: FEATURES -body: 'validate: Add `FileSizeCheck` which checks the provider documentation file +body: 'validate: Added `FileSizeCheck` which checks the provider documentation file size against the registry limit' time: 2024-03-05T14:02:34.112782-05:00 custom: diff --git a/.changes/unreleased/FEATURES-20240305-140346.yaml b/.changes/unreleased/FEATURES-20240305-140346.yaml index 1f440228..348fab9b 100644 --- a/.changes/unreleased/FEATURES-20240305-140346.yaml +++ b/.changes/unreleased/FEATURES-20240305-140346.yaml @@ -1,5 +1,5 @@ kind: FEATURES -body: 'validate: Add `FileExtensionCheck` which checks for valid provider documentation +body: 'validate: Added `FileExtensionCheck` which checks for valid provider documentation file extensions' time: 2024-03-05T14:03:46.816256-05:00 custom: diff --git a/.changes/unreleased/FEATURES-20240305-140451.yaml b/.changes/unreleased/FEATURES-20240305-140451.yaml index e5d15828..0ed3883a 100644 --- a/.changes/unreleased/FEATURES-20240305-140451.yaml +++ b/.changes/unreleased/FEATURES-20240305-140451.yaml @@ -1,5 +1,5 @@ kind: FEATURES -body: 'validate: Add `FrontMatterCheck` which checks the YAML frontmatter of provider +body: 'validate: Added `FrontMatterCheck` which checks the YAML frontmatter of provider documentation for missing required fields or invalid fields' time: 2024-03-05T14:04:51.781688-05:00 custom: diff --git a/.changes/unreleased/FEATURES-20240305-140622.yaml b/.changes/unreleased/FEATURES-20240305-140622.yaml index 57ab3a7c..cbb637e7 100644 --- a/.changes/unreleased/FEATURES-20240305-140622.yaml +++ b/.changes/unreleased/FEATURES-20240305-140622.yaml @@ -1,5 +1,5 @@ kind: FEATURES -body: 'validate: Add `FileMismatchCheck` which checks the names/number of provider +body: 'validate: Added `FileMismatchCheck` which checks the names/number of provider documentation files against the provider schema' time: 2024-03-05T14:06:22.168518-05:00 custom: From e4fff52ea90ebf6a249023620569533ea7f3ab8b Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 13 Mar 2024 16:38:30 -0400 Subject: [PATCH 17/29] Remove unused file --- internal/provider/templates/template-description.gotmpl | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 internal/provider/templates/template-description.gotmpl diff --git a/internal/provider/templates/template-description.gotmpl b/internal/provider/templates/template-description.gotmpl deleted file mode 100644 index 5b056cb7..00000000 --- a/internal/provider/templates/template-description.gotmpl +++ /dev/null @@ -1,3 +0,0 @@ -{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. - -For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider code. */}} \ No newline at end of file From cc6300a5cd93c046f1a3223d29b1922057637195 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 13 Mar 2024 16:39:04 -0400 Subject: [PATCH 18/29] Remove `filecheck` interface --- internal/check/file.go | 5 ----- internal/check/provider_file.go | 2 -- 2 files changed, 7 deletions(-) diff --git a/internal/check/file.go b/internal/check/file.go index f038f8eb..cb079b3a 100644 --- a/internal/check/file.go +++ b/internal/check/file.go @@ -10,11 +10,6 @@ import ( "path/filepath" ) -type FileCheck interface { - Run(string) error - RunAll([]string) error -} - type FileOptions struct { BasePath string } diff --git a/internal/check/provider_file.go b/internal/check/provider_file.go index 8ebfdca5..54acdb19 100644 --- a/internal/check/provider_file.go +++ b/internal/check/provider_file.go @@ -19,8 +19,6 @@ type ProviderFileOptions struct { } type ProviderFileCheck struct { - FileCheck - Options *ProviderFileOptions } From a1b156e178f678bf37d36248d6bd08ff968e4d49 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 13 Mar 2024 16:39:52 -0400 Subject: [PATCH 19/29] Refactor `schemamd` and `functionmd` packages to `internal/` --- README.md | 10 ++-------- {functionmd => internal/functionmd}/render.go | 2 +- {functionmd => internal/functionmd}/render_test.go | 2 +- .../functionmd}/testdata/example_arguments.md | 0 .../functionmd}/testdata/example_signature.md | 0 .../functionmd}/testdata/example_vararg.md | 0 .../testdata/function_signature.schema.json | 0 internal/provider/template.go | 4 ++-- {schemamd => internal/schemamd}/behaviors.go | 0 {schemamd => internal/schemamd}/behaviors_test.go | 0 {schemamd => internal/schemamd}/render.go | 0 {schemamd => internal/schemamd}/render_test.go | 3 ++- .../schemamd}/testdata/aws_acm_certificate.md | 0 .../schemamd}/testdata/aws_acm_certificate.schema.json | 0 .../schemamd}/testdata/aws_route_table_association.md | 0 .../testdata/aws_route_table_association.schema.json | 0 .../schemamd}/testdata/awscc_acmpca_certificate.md | 0 .../testdata/awscc_acmpca_certificate.schema.json | 0 .../schemamd}/testdata/awscc_logs_log_group.md | 0 .../testdata/awscc_logs_log_group.schema.json | 0 .../schemamd}/write_attribute_description.go | 0 .../schemamd}/write_attribute_description_test.go | 2 +- .../schemamd}/write_block_type_description.go | 0 .../schemamd}/write_block_type_description_test.go | 3 ++- .../write_nested_attribute_type_description.go | 0 .../write_nested_attribute_type_description_test.go | 3 ++- {schemamd => internal/schemamd}/write_type.go | 0 {schemamd => internal/schemamd}/write_type_test.go | 2 +- 28 files changed, 14 insertions(+), 17 deletions(-) rename {functionmd => internal/functionmd}/render.go (97%) rename {functionmd => internal/functionmd}/render_test.go (97%) rename {functionmd => internal/functionmd}/testdata/example_arguments.md (100%) rename {functionmd => internal/functionmd}/testdata/example_signature.md (100%) rename {functionmd => internal/functionmd}/testdata/example_vararg.md (100%) rename {functionmd => internal/functionmd}/testdata/function_signature.schema.json (100%) rename {schemamd => internal/schemamd}/behaviors.go (100%) rename {schemamd => internal/schemamd}/behaviors_test.go (100%) rename {schemamd => internal/schemamd}/render.go (100%) rename {schemamd => internal/schemamd}/render_test.go (96%) rename {schemamd => internal/schemamd}/testdata/aws_acm_certificate.md (100%) rename {schemamd => internal/schemamd}/testdata/aws_acm_certificate.schema.json (100%) rename {schemamd => internal/schemamd}/testdata/aws_route_table_association.md (100%) rename {schemamd => internal/schemamd}/testdata/aws_route_table_association.schema.json (100%) rename {schemamd => internal/schemamd}/testdata/awscc_acmpca_certificate.md (100%) rename {schemamd => internal/schemamd}/testdata/awscc_acmpca_certificate.schema.json (100%) rename {schemamd => internal/schemamd}/testdata/awscc_logs_log_group.md (100%) rename {schemamd => internal/schemamd}/testdata/awscc_logs_log_group.schema.json (100%) rename {schemamd => internal/schemamd}/write_attribute_description.go (100%) rename {schemamd => internal/schemamd}/write_attribute_description_test.go (98%) rename {schemamd => internal/schemamd}/write_block_type_description.go (100%) rename {schemamd => internal/schemamd}/write_block_type_description_test.go (98%) rename {schemamd => internal/schemamd}/write_nested_attribute_type_description.go (100%) rename {schemamd => internal/schemamd}/write_nested_attribute_type_description_test.go (97%) rename {schemamd => internal/schemamd}/write_type.go (100%) rename {schemamd => internal/schemamd}/write_type_test.go (95%) diff --git a/README.md b/README.md index df5c19c6..8d9c7d38 100644 --- a/README.md +++ b/README.md @@ -158,8 +158,8 @@ The `validate` subcommand can be used to validate the provider website documenta |---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `InvalidDirectoriesCheck` | Checks for valid subdirectory structure and throws an error if an invalid Terraform Provider documentation subdirectory is found. | | `MixedDirectoriesCheck` | Throws an error if both legacy documentation (`/website/docs`) and registry documentation (`/docs`) are found. | -| `NumberOfFilesCheck` | Throws an error if the number of files in a directory is larger than the registry limit. | -| `FileSizeCheck` | Throws an error if the documentation file is above the registry storage limit. | +| `NumberOfFilesCheck` | Throws an error if the number of files in a directory is larger than the public registry limit. | +| `FileSizeCheck` | Throws an error if the documentation file is above the public registry storage limit. | | `FileExtensionCheck` | Throws an error if the extension of the given file is not a valid registry documentation extension. | | `FrontMatterCheck` | Checks the YAML frontmatter of documentation for missing required fields or invalid fields. | | `FileMismatchCheck` | Throws an error if the names/number of resources/datasources/functions in the provider schema does not match the names/number of files in the corresponding documentation directory | @@ -327,12 +327,6 @@ Your help and patience is truly appreciated. ## Contributing -## Go Compatibility - -This project follows the [support policy](https://golang.org/doc/devel/release.html#policy) of Go as its support policy. The two latest major releases of Go are supported by the project. - -Currently, that means Go **1.21** or later must be used when including this project as a dependency. - ### License Headers All source code files in this repository (excluding autogenerated files like `go.mod`, prose, and files excluded in [.copywrite.hcl](.copywrite.hcl)) must have a license header at the top. diff --git a/functionmd/render.go b/internal/functionmd/render.go similarity index 97% rename from functionmd/render.go rename to internal/functionmd/render.go index fdea3a82..7b4951b7 100644 --- a/functionmd/render.go +++ b/internal/functionmd/render.go @@ -10,7 +10,7 @@ import ( tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/schemamd" + "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" ) // RenderArguments returns a Markdown formatted string of the function arguments. diff --git a/functionmd/render_test.go b/internal/functionmd/render_test.go similarity index 97% rename from functionmd/render_test.go rename to internal/functionmd/render_test.go index c76c37a7..dbc8e5cf 100644 --- a/functionmd/render_test.go +++ b/internal/functionmd/render_test.go @@ -12,7 +12,7 @@ import ( "github.com/google/go-cmp/cmp" tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/functionmd" + "github.com/hashicorp/terraform-plugin-docs/internal/functionmd" ) func TestRenderArguments(t *testing.T) { diff --git a/functionmd/testdata/example_arguments.md b/internal/functionmd/testdata/example_arguments.md similarity index 100% rename from functionmd/testdata/example_arguments.md rename to internal/functionmd/testdata/example_arguments.md diff --git a/functionmd/testdata/example_signature.md b/internal/functionmd/testdata/example_signature.md similarity index 100% rename from functionmd/testdata/example_signature.md rename to internal/functionmd/testdata/example_signature.md diff --git a/functionmd/testdata/example_vararg.md b/internal/functionmd/testdata/example_vararg.md similarity index 100% rename from functionmd/testdata/example_vararg.md rename to internal/functionmd/testdata/example_vararg.md diff --git a/functionmd/testdata/function_signature.schema.json b/internal/functionmd/testdata/function_signature.schema.json similarity index 100% rename from functionmd/testdata/function_signature.schema.json rename to internal/functionmd/testdata/function_signature.schema.json diff --git a/internal/provider/template.go b/internal/provider/template.go index 10f2e655..fad187c4 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -16,10 +16,10 @@ import ( tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/functionmd" + "github.com/hashicorp/terraform-plugin-docs/internal/functionmd" "github.com/hashicorp/terraform-plugin-docs/internal/mdplain" + "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" "github.com/hashicorp/terraform-plugin-docs/internal/tmplfuncs" - "github.com/hashicorp/terraform-plugin-docs/schemamd" ) const ( diff --git a/schemamd/behaviors.go b/internal/schemamd/behaviors.go similarity index 100% rename from schemamd/behaviors.go rename to internal/schemamd/behaviors.go diff --git a/schemamd/behaviors_test.go b/internal/schemamd/behaviors_test.go similarity index 100% rename from schemamd/behaviors_test.go rename to internal/schemamd/behaviors_test.go diff --git a/schemamd/render.go b/internal/schemamd/render.go similarity index 100% rename from schemamd/render.go rename to internal/schemamd/render.go diff --git a/schemamd/render_test.go b/internal/schemamd/render_test.go similarity index 96% rename from schemamd/render_test.go rename to internal/schemamd/render_test.go index 447826db..6d46ecd4 100644 --- a/schemamd/render_test.go +++ b/internal/schemamd/render_test.go @@ -11,7 +11,8 @@ import ( "github.com/google/go-cmp/cmp" tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/schemamd" + + "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" ) func TestRender(t *testing.T) { diff --git a/schemamd/testdata/aws_acm_certificate.md b/internal/schemamd/testdata/aws_acm_certificate.md similarity index 100% rename from schemamd/testdata/aws_acm_certificate.md rename to internal/schemamd/testdata/aws_acm_certificate.md diff --git a/schemamd/testdata/aws_acm_certificate.schema.json b/internal/schemamd/testdata/aws_acm_certificate.schema.json similarity index 100% rename from schemamd/testdata/aws_acm_certificate.schema.json rename to internal/schemamd/testdata/aws_acm_certificate.schema.json diff --git a/schemamd/testdata/aws_route_table_association.md b/internal/schemamd/testdata/aws_route_table_association.md similarity index 100% rename from schemamd/testdata/aws_route_table_association.md rename to internal/schemamd/testdata/aws_route_table_association.md diff --git a/schemamd/testdata/aws_route_table_association.schema.json b/internal/schemamd/testdata/aws_route_table_association.schema.json similarity index 100% rename from schemamd/testdata/aws_route_table_association.schema.json rename to internal/schemamd/testdata/aws_route_table_association.schema.json diff --git a/schemamd/testdata/awscc_acmpca_certificate.md b/internal/schemamd/testdata/awscc_acmpca_certificate.md similarity index 100% rename from schemamd/testdata/awscc_acmpca_certificate.md rename to internal/schemamd/testdata/awscc_acmpca_certificate.md diff --git a/schemamd/testdata/awscc_acmpca_certificate.schema.json b/internal/schemamd/testdata/awscc_acmpca_certificate.schema.json similarity index 100% rename from schemamd/testdata/awscc_acmpca_certificate.schema.json rename to internal/schemamd/testdata/awscc_acmpca_certificate.schema.json diff --git a/schemamd/testdata/awscc_logs_log_group.md b/internal/schemamd/testdata/awscc_logs_log_group.md similarity index 100% rename from schemamd/testdata/awscc_logs_log_group.md rename to internal/schemamd/testdata/awscc_logs_log_group.md diff --git a/schemamd/testdata/awscc_logs_log_group.schema.json b/internal/schemamd/testdata/awscc_logs_log_group.schema.json similarity index 100% rename from schemamd/testdata/awscc_logs_log_group.schema.json rename to internal/schemamd/testdata/awscc_logs_log_group.schema.json diff --git a/schemamd/write_attribute_description.go b/internal/schemamd/write_attribute_description.go similarity index 100% rename from schemamd/write_attribute_description.go rename to internal/schemamd/write_attribute_description.go diff --git a/schemamd/write_attribute_description_test.go b/internal/schemamd/write_attribute_description_test.go similarity index 98% rename from schemamd/write_attribute_description_test.go rename to internal/schemamd/write_attribute_description_test.go index dc2724af..c1318425 100644 --- a/schemamd/write_attribute_description_test.go +++ b/internal/schemamd/write_attribute_description_test.go @@ -11,7 +11,7 @@ import ( tfjson "github.com/hashicorp/terraform-json" "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/terraform-plugin-docs/schemamd" + "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" ) func TestWriteAttributeDescription(t *testing.T) { diff --git a/schemamd/write_block_type_description.go b/internal/schemamd/write_block_type_description.go similarity index 100% rename from schemamd/write_block_type_description.go rename to internal/schemamd/write_block_type_description.go diff --git a/schemamd/write_block_type_description_test.go b/internal/schemamd/write_block_type_description_test.go similarity index 98% rename from schemamd/write_block_type_description_test.go rename to internal/schemamd/write_block_type_description_test.go index 16183303..0ee14a26 100644 --- a/schemamd/write_block_type_description_test.go +++ b/internal/schemamd/write_block_type_description_test.go @@ -9,7 +9,8 @@ import ( "github.com/google/go-cmp/cmp" tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/schemamd" + + "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" ) func TestWriteBlockTypeDescription(t *testing.T) { diff --git a/schemamd/write_nested_attribute_type_description.go b/internal/schemamd/write_nested_attribute_type_description.go similarity index 100% rename from schemamd/write_nested_attribute_type_description.go rename to internal/schemamd/write_nested_attribute_type_description.go diff --git a/schemamd/write_nested_attribute_type_description_test.go b/internal/schemamd/write_nested_attribute_type_description_test.go similarity index 97% rename from schemamd/write_nested_attribute_type_description_test.go rename to internal/schemamd/write_nested_attribute_type_description_test.go index c576f2df..170284c1 100644 --- a/schemamd/write_nested_attribute_type_description_test.go +++ b/internal/schemamd/write_nested_attribute_type_description_test.go @@ -9,8 +9,9 @@ import ( "github.com/google/go-cmp/cmp" tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/schemamd" "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" ) func TestWriteNestedAttributeTypeDescription(t *testing.T) { diff --git a/schemamd/write_type.go b/internal/schemamd/write_type.go similarity index 100% rename from schemamd/write_type.go rename to internal/schemamd/write_type.go diff --git a/schemamd/write_type_test.go b/internal/schemamd/write_type_test.go similarity index 95% rename from schemamd/write_type_test.go rename to internal/schemamd/write_type_test.go index 2256b370..3def2586 100644 --- a/schemamd/write_type_test.go +++ b/internal/schemamd/write_type_test.go @@ -11,7 +11,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/terraform-plugin-docs/schemamd" + "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" ) func TestWriteType(t *testing.T) { From 96e07d433c547ba3560c963795f3628af4afc078 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Wed, 13 Mar 2024 16:41:43 -0400 Subject: [PATCH 20/29] Remove unused method --- internal/check/provider_file.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/internal/check/provider_file.go b/internal/check/provider_file.go index 54acdb19..5358b669 100644 --- a/internal/check/provider_file.go +++ b/internal/check/provider_file.go @@ -7,8 +7,6 @@ import ( "fmt" "log" "os" - - "github.com/hashicorp/go-multierror" ) type ProviderFileOptions struct { @@ -67,15 +65,3 @@ func (check *ProviderFileCheck) Run(path string) error { return nil } - -func (check *ProviderFileCheck) RunAll(files []string) error { - var result *multierror.Error - - for _, file := range files { - if err := check.Run(file); err != nil { - result = multierror.Append(result, err) - } - } - - return result.ErrorOrNil() -} From 64373fb831a0d66cd4e3168021b51d0442d1bfe1 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Fri, 22 Mar 2024 17:09:16 -0400 Subject: [PATCH 21/29] Revert "Refactor `schemamd` and `functionmd` packages to `internal/`" This reverts commit a1b156e178f678bf37d36248d6bd08ff968e4d49. --- README.md | 10 ++++++++-- {internal/functionmd => functionmd}/render.go | 2 +- {internal/functionmd => functionmd}/render_test.go | 2 +- .../testdata/example_arguments.md | 0 .../testdata/example_signature.md | 0 .../testdata/example_vararg.md | 0 .../testdata/function_signature.schema.json | 0 internal/provider/template.go | 4 ++-- {internal/schemamd => schemamd}/behaviors.go | 0 {internal/schemamd => schemamd}/behaviors_test.go | 0 {internal/schemamd => schemamd}/render.go | 0 {internal/schemamd => schemamd}/render_test.go | 3 +-- .../testdata/aws_acm_certificate.md | 0 .../testdata/aws_acm_certificate.schema.json | 0 .../testdata/aws_route_table_association.md | 0 .../testdata/aws_route_table_association.schema.json | 0 .../testdata/awscc_acmpca_certificate.md | 0 .../testdata/awscc_acmpca_certificate.schema.json | 0 .../testdata/awscc_logs_log_group.md | 0 .../testdata/awscc_logs_log_group.schema.json | 0 .../write_attribute_description.go | 0 .../write_attribute_description_test.go | 2 +- .../write_block_type_description.go | 0 .../write_block_type_description_test.go | 3 +-- .../write_nested_attribute_type_description.go | 0 .../write_nested_attribute_type_description_test.go | 3 +-- {internal/schemamd => schemamd}/write_type.go | 0 {internal/schemamd => schemamd}/write_type_test.go | 2 +- 28 files changed, 17 insertions(+), 14 deletions(-) rename {internal/functionmd => functionmd}/render.go (97%) rename {internal/functionmd => functionmd}/render_test.go (97%) rename {internal/functionmd => functionmd}/testdata/example_arguments.md (100%) rename {internal/functionmd => functionmd}/testdata/example_signature.md (100%) rename {internal/functionmd => functionmd}/testdata/example_vararg.md (100%) rename {internal/functionmd => functionmd}/testdata/function_signature.schema.json (100%) rename {internal/schemamd => schemamd}/behaviors.go (100%) rename {internal/schemamd => schemamd}/behaviors_test.go (100%) rename {internal/schemamd => schemamd}/render.go (100%) rename {internal/schemamd => schemamd}/render_test.go (96%) rename {internal/schemamd => schemamd}/testdata/aws_acm_certificate.md (100%) rename {internal/schemamd => schemamd}/testdata/aws_acm_certificate.schema.json (100%) rename {internal/schemamd => schemamd}/testdata/aws_route_table_association.md (100%) rename {internal/schemamd => schemamd}/testdata/aws_route_table_association.schema.json (100%) rename {internal/schemamd => schemamd}/testdata/awscc_acmpca_certificate.md (100%) rename {internal/schemamd => schemamd}/testdata/awscc_acmpca_certificate.schema.json (100%) rename {internal/schemamd => schemamd}/testdata/awscc_logs_log_group.md (100%) rename {internal/schemamd => schemamd}/testdata/awscc_logs_log_group.schema.json (100%) rename {internal/schemamd => schemamd}/write_attribute_description.go (100%) rename {internal/schemamd => schemamd}/write_attribute_description_test.go (98%) rename {internal/schemamd => schemamd}/write_block_type_description.go (100%) rename {internal/schemamd => schemamd}/write_block_type_description_test.go (98%) rename {internal/schemamd => schemamd}/write_nested_attribute_type_description.go (100%) rename {internal/schemamd => schemamd}/write_nested_attribute_type_description_test.go (97%) rename {internal/schemamd => schemamd}/write_type.go (100%) rename {internal/schemamd => schemamd}/write_type_test.go (95%) diff --git a/README.md b/README.md index 8d9c7d38..df5c19c6 100644 --- a/README.md +++ b/README.md @@ -158,8 +158,8 @@ The `validate` subcommand can be used to validate the provider website documenta |---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `InvalidDirectoriesCheck` | Checks for valid subdirectory structure and throws an error if an invalid Terraform Provider documentation subdirectory is found. | | `MixedDirectoriesCheck` | Throws an error if both legacy documentation (`/website/docs`) and registry documentation (`/docs`) are found. | -| `NumberOfFilesCheck` | Throws an error if the number of files in a directory is larger than the public registry limit. | -| `FileSizeCheck` | Throws an error if the documentation file is above the public registry storage limit. | +| `NumberOfFilesCheck` | Throws an error if the number of files in a directory is larger than the registry limit. | +| `FileSizeCheck` | Throws an error if the documentation file is above the registry storage limit. | | `FileExtensionCheck` | Throws an error if the extension of the given file is not a valid registry documentation extension. | | `FrontMatterCheck` | Checks the YAML frontmatter of documentation for missing required fields or invalid fields. | | `FileMismatchCheck` | Throws an error if the names/number of resources/datasources/functions in the provider schema does not match the names/number of files in the corresponding documentation directory | @@ -327,6 +327,12 @@ Your help and patience is truly appreciated. ## Contributing +## Go Compatibility + +This project follows the [support policy](https://golang.org/doc/devel/release.html#policy) of Go as its support policy. The two latest major releases of Go are supported by the project. + +Currently, that means Go **1.21** or later must be used when including this project as a dependency. + ### License Headers All source code files in this repository (excluding autogenerated files like `go.mod`, prose, and files excluded in [.copywrite.hcl](.copywrite.hcl)) must have a license header at the top. diff --git a/internal/functionmd/render.go b/functionmd/render.go similarity index 97% rename from internal/functionmd/render.go rename to functionmd/render.go index 7b4951b7..fdea3a82 100644 --- a/internal/functionmd/render.go +++ b/functionmd/render.go @@ -10,7 +10,7 @@ import ( tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" + "github.com/hashicorp/terraform-plugin-docs/schemamd" ) // RenderArguments returns a Markdown formatted string of the function arguments. diff --git a/internal/functionmd/render_test.go b/functionmd/render_test.go similarity index 97% rename from internal/functionmd/render_test.go rename to functionmd/render_test.go index dbc8e5cf..c76c37a7 100644 --- a/internal/functionmd/render_test.go +++ b/functionmd/render_test.go @@ -12,7 +12,7 @@ import ( "github.com/google/go-cmp/cmp" tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/internal/functionmd" + "github.com/hashicorp/terraform-plugin-docs/functionmd" ) func TestRenderArguments(t *testing.T) { diff --git a/internal/functionmd/testdata/example_arguments.md b/functionmd/testdata/example_arguments.md similarity index 100% rename from internal/functionmd/testdata/example_arguments.md rename to functionmd/testdata/example_arguments.md diff --git a/internal/functionmd/testdata/example_signature.md b/functionmd/testdata/example_signature.md similarity index 100% rename from internal/functionmd/testdata/example_signature.md rename to functionmd/testdata/example_signature.md diff --git a/internal/functionmd/testdata/example_vararg.md b/functionmd/testdata/example_vararg.md similarity index 100% rename from internal/functionmd/testdata/example_vararg.md rename to functionmd/testdata/example_vararg.md diff --git a/internal/functionmd/testdata/function_signature.schema.json b/functionmd/testdata/function_signature.schema.json similarity index 100% rename from internal/functionmd/testdata/function_signature.schema.json rename to functionmd/testdata/function_signature.schema.json diff --git a/internal/provider/template.go b/internal/provider/template.go index fad187c4..10f2e655 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -16,10 +16,10 @@ import ( tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-docs/internal/functionmd" + "github.com/hashicorp/terraform-plugin-docs/functionmd" "github.com/hashicorp/terraform-plugin-docs/internal/mdplain" - "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" "github.com/hashicorp/terraform-plugin-docs/internal/tmplfuncs" + "github.com/hashicorp/terraform-plugin-docs/schemamd" ) const ( diff --git a/internal/schemamd/behaviors.go b/schemamd/behaviors.go similarity index 100% rename from internal/schemamd/behaviors.go rename to schemamd/behaviors.go diff --git a/internal/schemamd/behaviors_test.go b/schemamd/behaviors_test.go similarity index 100% rename from internal/schemamd/behaviors_test.go rename to schemamd/behaviors_test.go diff --git a/internal/schemamd/render.go b/schemamd/render.go similarity index 100% rename from internal/schemamd/render.go rename to schemamd/render.go diff --git a/internal/schemamd/render_test.go b/schemamd/render_test.go similarity index 96% rename from internal/schemamd/render_test.go rename to schemamd/render_test.go index 6d46ecd4..447826db 100644 --- a/internal/schemamd/render_test.go +++ b/schemamd/render_test.go @@ -11,8 +11,7 @@ import ( "github.com/google/go-cmp/cmp" tfjson "github.com/hashicorp/terraform-json" - - "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" + "github.com/hashicorp/terraform-plugin-docs/schemamd" ) func TestRender(t *testing.T) { diff --git a/internal/schemamd/testdata/aws_acm_certificate.md b/schemamd/testdata/aws_acm_certificate.md similarity index 100% rename from internal/schemamd/testdata/aws_acm_certificate.md rename to schemamd/testdata/aws_acm_certificate.md diff --git a/internal/schemamd/testdata/aws_acm_certificate.schema.json b/schemamd/testdata/aws_acm_certificate.schema.json similarity index 100% rename from internal/schemamd/testdata/aws_acm_certificate.schema.json rename to schemamd/testdata/aws_acm_certificate.schema.json diff --git a/internal/schemamd/testdata/aws_route_table_association.md b/schemamd/testdata/aws_route_table_association.md similarity index 100% rename from internal/schemamd/testdata/aws_route_table_association.md rename to schemamd/testdata/aws_route_table_association.md diff --git a/internal/schemamd/testdata/aws_route_table_association.schema.json b/schemamd/testdata/aws_route_table_association.schema.json similarity index 100% rename from internal/schemamd/testdata/aws_route_table_association.schema.json rename to schemamd/testdata/aws_route_table_association.schema.json diff --git a/internal/schemamd/testdata/awscc_acmpca_certificate.md b/schemamd/testdata/awscc_acmpca_certificate.md similarity index 100% rename from internal/schemamd/testdata/awscc_acmpca_certificate.md rename to schemamd/testdata/awscc_acmpca_certificate.md diff --git a/internal/schemamd/testdata/awscc_acmpca_certificate.schema.json b/schemamd/testdata/awscc_acmpca_certificate.schema.json similarity index 100% rename from internal/schemamd/testdata/awscc_acmpca_certificate.schema.json rename to schemamd/testdata/awscc_acmpca_certificate.schema.json diff --git a/internal/schemamd/testdata/awscc_logs_log_group.md b/schemamd/testdata/awscc_logs_log_group.md similarity index 100% rename from internal/schemamd/testdata/awscc_logs_log_group.md rename to schemamd/testdata/awscc_logs_log_group.md diff --git a/internal/schemamd/testdata/awscc_logs_log_group.schema.json b/schemamd/testdata/awscc_logs_log_group.schema.json similarity index 100% rename from internal/schemamd/testdata/awscc_logs_log_group.schema.json rename to schemamd/testdata/awscc_logs_log_group.schema.json diff --git a/internal/schemamd/write_attribute_description.go b/schemamd/write_attribute_description.go similarity index 100% rename from internal/schemamd/write_attribute_description.go rename to schemamd/write_attribute_description.go diff --git a/internal/schemamd/write_attribute_description_test.go b/schemamd/write_attribute_description_test.go similarity index 98% rename from internal/schemamd/write_attribute_description_test.go rename to schemamd/write_attribute_description_test.go index c1318425..dc2724af 100644 --- a/internal/schemamd/write_attribute_description_test.go +++ b/schemamd/write_attribute_description_test.go @@ -11,7 +11,7 @@ import ( tfjson "github.com/hashicorp/terraform-json" "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" + "github.com/hashicorp/terraform-plugin-docs/schemamd" ) func TestWriteAttributeDescription(t *testing.T) { diff --git a/internal/schemamd/write_block_type_description.go b/schemamd/write_block_type_description.go similarity index 100% rename from internal/schemamd/write_block_type_description.go rename to schemamd/write_block_type_description.go diff --git a/internal/schemamd/write_block_type_description_test.go b/schemamd/write_block_type_description_test.go similarity index 98% rename from internal/schemamd/write_block_type_description_test.go rename to schemamd/write_block_type_description_test.go index 0ee14a26..16183303 100644 --- a/internal/schemamd/write_block_type_description_test.go +++ b/schemamd/write_block_type_description_test.go @@ -9,8 +9,7 @@ import ( "github.com/google/go-cmp/cmp" tfjson "github.com/hashicorp/terraform-json" - - "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" + "github.com/hashicorp/terraform-plugin-docs/schemamd" ) func TestWriteBlockTypeDescription(t *testing.T) { diff --git a/internal/schemamd/write_nested_attribute_type_description.go b/schemamd/write_nested_attribute_type_description.go similarity index 100% rename from internal/schemamd/write_nested_attribute_type_description.go rename to schemamd/write_nested_attribute_type_description.go diff --git a/internal/schemamd/write_nested_attribute_type_description_test.go b/schemamd/write_nested_attribute_type_description_test.go similarity index 97% rename from internal/schemamd/write_nested_attribute_type_description_test.go rename to schemamd/write_nested_attribute_type_description_test.go index 170284c1..c576f2df 100644 --- a/internal/schemamd/write_nested_attribute_type_description_test.go +++ b/schemamd/write_nested_attribute_type_description_test.go @@ -9,9 +9,8 @@ import ( "github.com/google/go-cmp/cmp" tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-plugin-docs/schemamd" "github.com/zclconf/go-cty/cty" - - "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" ) func TestWriteNestedAttributeTypeDescription(t *testing.T) { diff --git a/internal/schemamd/write_type.go b/schemamd/write_type.go similarity index 100% rename from internal/schemamd/write_type.go rename to schemamd/write_type.go diff --git a/internal/schemamd/write_type_test.go b/schemamd/write_type_test.go similarity index 95% rename from internal/schemamd/write_type_test.go rename to schemamd/write_type_test.go index 3def2586..2256b370 100644 --- a/internal/schemamd/write_type_test.go +++ b/schemamd/write_type_test.go @@ -11,7 +11,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/zclconf/go-cty/cty" - "github.com/hashicorp/terraform-plugin-docs/internal/schemamd" + "github.com/hashicorp/terraform-plugin-docs/schemamd" ) func TestWriteType(t *testing.T) { From 3f27ed27d30ab5c12d674f50052ec03bb0aee82e Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 25 Mar 2024 13:00:55 -0400 Subject: [PATCH 22/29] Remove go compatability section --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index df5c19c6..fc2f3895 100644 --- a/README.md +++ b/README.md @@ -327,12 +327,6 @@ Your help and patience is truly appreciated. ## Contributing -## Go Compatibility - -This project follows the [support policy](https://golang.org/doc/devel/release.html#policy) of Go as its support policy. The two latest major releases of Go are supported by the project. - -Currently, that means Go **1.21** or later must be used when including this project as a dependency. - ### License Headers All source code files in this repository (excluding autogenerated files like `go.mod`, prose, and files excluded in [.copywrite.hcl](.copywrite.hcl)) must have a license header at the top. From 1bbf77f07419a16138855a27c1536f254b01baa1 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 25 Mar 2024 13:10:06 -0400 Subject: [PATCH 23/29] Switch to `yuin/goldmark` for frontmatter parsing --- go.mod | 7 +++++-- go.sum | 8 ++++++-- internal/check/frontmatter.go | 23 +++++++++++++++++++++-- internal/check/frontmatter_test.go | 7 +++++-- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 33ad3247..b133806b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/google/go-cmp v0.6.0 github.com/hashicorp/cli v1.1.6 - github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hc-install v0.6.3 github.com/hashicorp/terraform-exec v0.20.0 @@ -17,12 +16,13 @@ require ( github.com/yuin/goldmark v1.7.0 github.com/yuin/goldmark-meta v1.1.0 github.com/zclconf/go-cty v1.14.3 + go.abhg.dev/goldmark/frontmatter v0.2.0 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df golang.org/x/text v0.14.0 - gopkg.in/yaml.v2 v2.3.0 ) require ( + github.com/BurntSushi/toml v1.2.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect @@ -36,6 +36,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.15 // indirect @@ -50,4 +51,6 @@ require ( golang.org/x/mod v0.15.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/tools v0.13.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8ae48fa2..98fe6af1 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -118,8 +120,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -129,6 +131,8 @@ github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUei github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/zclconf/go-cty v1.14.3 h1:1JXy1XroaGrzZuG6X9dt7HL6s9AwbY+l4UNL8o5B6ho= github.com/zclconf/go-cty v1.14.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= +go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= diff --git a/internal/check/frontmatter.go b/internal/check/frontmatter.go index 5d80c4fd..65ac43aa 100644 --- a/internal/check/frontmatter.go +++ b/internal/check/frontmatter.go @@ -4,9 +4,12 @@ package check import ( + "bytes" "fmt" - "gopkg.in/yaml.v2" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/parser" + "go.abhg.dev/goldmark/frontmatter" ) type FrontMatterCheck struct { @@ -48,7 +51,23 @@ func NewFrontMatterCheck(opts *FrontMatterOptions) *FrontMatterCheck { func (check *FrontMatterCheck) Run(src []byte) error { frontMatter := FrontMatterData{} - err := yaml.Unmarshal(src, &frontMatter) + md := goldmark.New( + goldmark.WithExtensions(&frontmatter.Extender{}), + ) + + ctx := parser.NewContext() + var buff bytes.Buffer + + err := md.Convert(src, &buff, parser.WithContext(ctx)) + if err != nil { + return err + } + d := frontmatter.Get(ctx) + if d == nil { + return fmt.Errorf("no frontmatter found") + } + + err = d.Decode(&frontMatter) if err != nil { return fmt.Errorf("error parsing YAML frontmatter: %w", err) } diff --git a/internal/check/frontmatter_test.go b/internal/check/frontmatter_test.go index 7f49eb1c..9ac935eb 100644 --- a/internal/check/frontmatter_test.go +++ b/internal/check/frontmatter_test.go @@ -16,17 +16,20 @@ func TestFrontMatterCheck(t *testing.T) { ExpectError bool }{ { - Name: "empty source", - Source: ``, + Name: "empty source", + Source: ``, + ExpectError: true, }, { Name: "valid YAML with default options", Source: ` +--- description: |- Example description layout: "example" page_title: Example Page Title subcategory: Example Subcategory +--- `, }, { From 3f57c3a542164cd8090381dc172081fa9c311dd5 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 25 Mar 2024 13:23:40 -0400 Subject: [PATCH 24/29] Remove unused test --- internal/provider/validate_test.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index 03c5e3fc..1bfc2f34 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -11,22 +11,6 @@ import ( "github.com/hashicorp/cli" ) -func TestValidator_validate(t *testing.T) { - t.Parallel() - - v := &validator{ - providerDir: filepath.Join("testdata", "valid-registry-directories"), - providerName: "terraform-provider-null", - - logger: NewLogger(cli.NewMockUi()), - } - - err := v.validateStaticDocs(filepath.Join(v.providerDir, "docs")) - if err != nil { - t.Fatalf("error retrieving schema: %q", err) - } -} - func TestValidateStaticDocs(t *testing.T) { t.Parallel() testCases := []struct { From 498a52463d51be6cc01a30fa7dec5ded2628e6ef Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 25 Mar 2024 13:24:41 -0400 Subject: [PATCH 25/29] Move global variables to top of file --- internal/provider/validate.go | 82 ++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/internal/provider/validate.go b/internal/provider/validate.go index 6f9873fb..38a4b442 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -37,6 +37,45 @@ var ValidRegistryFileExtensions = []string{ FileExtensionMd, } +var LegacyFrontMatterOptions = &check.FrontMatterOptions{ + NoSidebarCurrent: true, + RequireDescription: true, + RequireLayout: true, + RequirePageTitle: true, +} + +var LegacyIndexFrontMatterOptions = &check.FrontMatterOptions{ + NoSidebarCurrent: true, + NoSubcategory: true, + RequireDescription: true, + RequireLayout: true, + RequirePageTitle: true, +} + +var LegacyGuideFrontMatterOptions = &check.FrontMatterOptions{ + NoSidebarCurrent: true, + RequireDescription: true, + RequireLayout: true, + RequirePageTitle: true, +} + +var RegistryFrontMatterOptions = &check.FrontMatterOptions{ + NoLayout: true, + NoSidebarCurrent: true, +} + +var RegistryIndexFrontMatterOptions = &check.FrontMatterOptions{ + NoLayout: true, + NoSidebarCurrent: true, + NoSubcategory: true, +} + +var RegistryGuideFrontMatterOptions = &check.FrontMatterOptions{ + NoLayout: true, + NoSidebarCurrent: true, + RequirePageTitle: true, +} + type validator struct { providerName string providerDir string @@ -311,48 +350,11 @@ func (v *validator) validateLegacyWebsite(dir string) error { } func dirExists(name string) bool { - if _, err := os.Stat(name); err != nil { + if file, err := os.Stat(name); err != nil { + return false + } else if !file.IsDir() { return false } return true } - -var LegacyFrontMatterOptions = &check.FrontMatterOptions{ - NoSidebarCurrent: true, - RequireDescription: true, - RequireLayout: true, - RequirePageTitle: true, -} - -var LegacyIndexFrontMatterOptions = &check.FrontMatterOptions{ - NoSidebarCurrent: true, - NoSubcategory: true, - RequireDescription: true, - RequireLayout: true, - RequirePageTitle: true, -} - -var LegacyGuideFrontMatterOptions = &check.FrontMatterOptions{ - NoSidebarCurrent: true, - RequireDescription: true, - RequireLayout: true, - RequirePageTitle: true, -} - -var RegistryFrontMatterOptions = &check.FrontMatterOptions{ - NoLayout: true, - NoSidebarCurrent: true, -} - -var RegistryIndexFrontMatterOptions = &check.FrontMatterOptions{ - NoLayout: true, - NoSidebarCurrent: true, - NoSubcategory: true, -} - -var RegistryGuideFrontMatterOptions = &check.FrontMatterOptions{ - NoLayout: true, - NoSidebarCurrent: true, - RequirePageTitle: true, -} From ed37a07ccfbcd74e9faa10f8f6cc2b2d46377de4 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 26 Mar 2024 16:35:27 -0400 Subject: [PATCH 26/29] Refactor `testcases` to use `map[string]struct` --- internal/check/directory_test.go | 31 +++---- internal/check/file_extension_test.go | 23 ++--- internal/check/file_mismatch_test.go | 120 +++++++++++--------------- internal/check/file_test.go | 31 +++---- internal/check/frontmatter_test.go | 41 ++++----- internal/provider/validate_test.go | 76 +++++++--------- 6 files changed, 128 insertions(+), 194 deletions(-) diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index c5e0849f..829189d2 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -14,30 +14,27 @@ import ( func TestNumberOfFilesCheck(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { files []string ExpectError bool }{ - { - Name: "under limit", + "under limit": { files: testGenerateFiles(RegistryMaximumNumberOfFiles - 1), }, - { - Name: "at limit", + "at limit": { files: testGenerateFiles(RegistryMaximumNumberOfFiles), ExpectError: true, }, - { - Name: "over limit", + "over limit": { files: testGenerateFiles(RegistryMaximumNumberOfFiles + 1), ExpectError: true, }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() got := NumberOfFilesCheck(testCase.files) @@ -54,25 +51,23 @@ func TestNumberOfFilesCheck(t *testing.T) { func TestMixedDirectoriesCheck(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { BasePath string ExpectError bool }{ - { - Name: "valid mixed directories", + "valid mixed directories": { BasePath: filepath.Join("testdata", "valid-mixed-directories"), }, - { - Name: "invalid mixed directories", + "invalid mixed directories": { BasePath: filepath.Join("testdata", "invalid-mixed-directories"), ExpectError: true, }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() providerFs := os.DirFS(testCase.BasePath) diff --git a/internal/check/file_extension_test.go b/internal/check/file_extension_test.go index 339a3796..15d756c4 100644 --- a/internal/check/file_extension_test.go +++ b/internal/check/file_extension_test.go @@ -9,41 +9,36 @@ import ( func TestTrimFileExtension(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { Path string Expect string }{ - { - Name: "empty path", + "empty path": { Path: "", Expect: "", }, - { - Name: "filename with single extension", + "filename with single extension": { Path: "file.md", Expect: "file", }, - { - Name: "filename with multiple extensions", + "filename with multiple extensions": { Path: "file.html.markdown", Expect: "file", }, - { - Name: "full path with single extensions", + "full path with single extension": { Path: "docs/resource/thing.md", Expect: "thing", }, - { - Name: "full path with multiple extensions", + "full path with multiple extensions": { Path: "website/docs/r/thing.html.markdown", Expect: "thing", }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() got := TrimFileExtension(testCase.Path) want := testCase.Expect diff --git a/internal/check/file_mismatch_test.go b/internal/check/file_mismatch_test.go index 85e3d1c7..50ef8dfc 100644 --- a/internal/check/file_mismatch_test.go +++ b/internal/check/file_mismatch_test.go @@ -14,14 +14,12 @@ import ( func TestFileHasResource(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { File string Resources map[string]*tfjson.Schema Expect bool }{ - { - Name: "found", + "found": { File: "resource1.md", Resources: map[string]*tfjson.Schema{ "test_resource1": {}, @@ -29,8 +27,7 @@ func TestFileHasResource(t *testing.T) { }, Expect: true, }, - { - Name: "not found", + "not found": { File: "resource1.md", Resources: map[string]*tfjson.Schema{ "test_resource2": {}, @@ -40,9 +37,10 @@ func TestFileHasResource(t *testing.T) { }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() got := fileHasResource(testCase.Resources, "test", testCase.File) @@ -57,36 +55,32 @@ func TestFileHasResource(t *testing.T) { func TestFileResourceName(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { File string Expect string }{ - { - Name: "filename with single extension", + "filename with single extension": { File: "file.md", Expect: "test_file", }, - { - Name: "filename with multiple extensions", + "filename with multiple extensions": { File: "file.html.markdown", Expect: "test_file", }, - { - Name: "full path with single extensions", + "full path with single extension": { File: filepath.Join("docs", "resource", "thing.md"), Expect: "test_thing", }, - { - Name: "full path with multiple extensions", + "full path with multiple extensions": { File: filepath.Join("website", "docs", "r", "thing.html.markdown"), Expect: "test_thing", }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() got := fileResourceName("test", testCase.File) want := testCase.Expect @@ -100,15 +94,13 @@ func TestFileResourceName(t *testing.T) { func TestFileMismatchCheck(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { ResourceFiles fstest.MapFS FunctionFiles fstest.MapFS Options *FileMismatchOptions ExpectError bool }{ - { - Name: "all found - resource", + "all found - resource": { ResourceFiles: fstest.MapFS{ "resource1.md": {}, "resource2.md": {}, @@ -123,8 +115,7 @@ func TestFileMismatchCheck(t *testing.T) { }, }, }, - { - Name: "all found - function", + "all found - function": { FunctionFiles: fstest.MapFS{ "function1.md": {}, "function2.md": {}, @@ -139,8 +130,7 @@ func TestFileMismatchCheck(t *testing.T) { }, }, }, - { - Name: "extra file - resource", + "extra file - resource": { ResourceFiles: fstest.MapFS{ "resource1.md": {}, "resource2.md": {}, @@ -157,8 +147,7 @@ func TestFileMismatchCheck(t *testing.T) { }, ExpectError: true, }, - { - Name: "extra file - function", + "extra file - function": { FunctionFiles: fstest.MapFS{ "function1.md": {}, "function2.md": {}, @@ -175,8 +164,7 @@ func TestFileMismatchCheck(t *testing.T) { }, ExpectError: true, }, - { - Name: "ignore extra file - resource", + "ignore extra file - resource": { ResourceFiles: fstest.MapFS{ "resource1.md": {}, "resource2.md": {}, @@ -193,8 +181,7 @@ func TestFileMismatchCheck(t *testing.T) { }, }, }, - { - Name: "ignore extra file - function", + "ignore extra file - function": { FunctionFiles: fstest.MapFS{ "function1.md": {}, "function2.md": {}, @@ -212,8 +199,7 @@ func TestFileMismatchCheck(t *testing.T) { }, }, }, - { - Name: "missing file - resource", + "missing file - resource": { ResourceFiles: fstest.MapFS{ "resource1.md": {}, }, @@ -228,8 +214,7 @@ func TestFileMismatchCheck(t *testing.T) { }, ExpectError: true, }, - { - Name: "missing file - function", + "missing file - function": { FunctionFiles: fstest.MapFS{ "function1.md": {}, }, @@ -244,8 +229,7 @@ func TestFileMismatchCheck(t *testing.T) { }, ExpectError: true, }, - { - Name: "ignore missing file - resource", + "ignore missing file - resource": { ResourceFiles: fstest.MapFS{ "resource1.md": {}, }, @@ -260,8 +244,7 @@ func TestFileMismatchCheck(t *testing.T) { }, }, }, - { - Name: "ignore missing file - function", + "ignore missing file - function": { FunctionFiles: fstest.MapFS{ "function1.md": {}, }, @@ -276,8 +259,7 @@ func TestFileMismatchCheck(t *testing.T) { }, }, }, - { - Name: "no files", + "no files": { Options: &FileMismatchOptions{ ProviderShortName: "test", Schema: &tfjson.ProviderSchema{ @@ -292,8 +274,7 @@ func TestFileMismatchCheck(t *testing.T) { }, }, }, - { - Name: "no schemas", + "no schemas": { ResourceFiles: fstest.MapFS{ "resource1.md": {}, }, @@ -306,9 +287,10 @@ func TestFileMismatchCheck(t *testing.T) { }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() resourceFiles, _ := testCase.ResourceFiles.ReadDir(".") @@ -330,14 +312,12 @@ func TestFileMismatchCheck(t *testing.T) { func TestResourceHasFile(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { FS fstest.MapFS ResourceName string Expect bool }{ - { - Name: "found", + "found": { FS: fstest.MapFS{ "resource1.md": {}, "resource2.md": {}, @@ -345,8 +325,7 @@ func TestResourceHasFile(t *testing.T) { ResourceName: "test_resource1", Expect: true, }, - { - Name: "not found", + "not found": { FS: fstest.MapFS{ "resource1.md": {}, "resource2.md": {}, @@ -356,9 +335,10 @@ func TestResourceHasFile(t *testing.T) { }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() files, _ := testCase.FS.ReadDir(".") @@ -375,14 +355,12 @@ func TestResourceHasFile(t *testing.T) { func TestFunctionHasFile(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { FS fstest.MapFS FunctionName string Expect bool }{ - { - Name: "found", + "found": { FS: fstest.MapFS{ "function1.md": {}, "function2.md": {}, @@ -390,8 +368,7 @@ func TestFunctionHasFile(t *testing.T) { FunctionName: "function1", Expect: true, }, - { - Name: "not found", + "not found": { FS: fstest.MapFS{ "function1.md": {}, "function2.md": {}, @@ -401,9 +378,10 @@ func TestFunctionHasFile(t *testing.T) { }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() files, _ := testCase.FS.ReadDir(".") @@ -420,18 +398,15 @@ func TestFunctionHasFile(t *testing.T) { func TestResourceNames(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { Resources map[string]*tfjson.Schema Expect []string }{ - { - Name: "empty", + "empty": { Resources: map[string]*tfjson.Schema{}, Expect: []string{}, }, - { - Name: "multiple", + "multiple": { Resources: map[string]*tfjson.Schema{ "test_resource1": {}, "test_resource2": {}, @@ -443,9 +418,10 @@ func TestResourceNames(t *testing.T) { }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() got := resourceNames(testCase.Resources) diff --git a/internal/check/file_test.go b/internal/check/file_test.go index badade4b..4aa21fc8 100644 --- a/internal/check/file_test.go +++ b/internal/check/file_test.go @@ -11,30 +11,27 @@ import ( func TestFileSizeCheck(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { Size int64 ExpectError bool }{ - { - Name: "under limit", + "under limit": { Size: RegistryMaximumSizeOfFile - 1, }, - { - Name: "on limit", + "on limit": { Size: RegistryMaximumSizeOfFile, ExpectError: true, }, - { - Name: "over limit", + "over limit": { Size: RegistryMaximumSizeOfFile + 1, ExpectError: true, }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() file, err := os.CreateTemp(os.TempDir(), "TestFileSizeCheck") @@ -64,20 +61,17 @@ func TestFileSizeCheck(t *testing.T) { func TestFullPath(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { FileOptions *FileOptions Path string Expect string }{ - { - Name: "without base path", + "without base path": { FileOptions: &FileOptions{}, Path: filepath.FromSlash("docs/resources/thing.md"), Expect: filepath.FromSlash("docs/resources/thing.md"), }, - { - Name: "with base path", + "with base path": { FileOptions: &FileOptions{ BasePath: filepath.FromSlash("/full/path/to"), }, @@ -86,9 +80,10 @@ func TestFullPath(t *testing.T) { }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() got := testCase.FileOptions.FullPath(testCase.Path) diff --git a/internal/check/frontmatter_test.go b/internal/check/frontmatter_test.go index 9ac935eb..9189b0de 100644 --- a/internal/check/frontmatter_test.go +++ b/internal/check/frontmatter_test.go @@ -9,19 +9,16 @@ import ( func TestFrontMatterCheck(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { Source string Options *FrontMatterOptions ExpectError bool }{ - { - Name: "empty source", + "empty source": { Source: ``, ExpectError: true, }, - { - Name: "valid YAML with default options", + "valid YAML with default options": { Source: ` --- description: |- @@ -32,8 +29,7 @@ subcategory: Example Subcategory --- `, }, - { - Name: "valid YAML section and Markdown with default options", + "valid YAML section and Markdown with default options": { Source: ` --- description: |- @@ -46,8 +42,7 @@ subcategory: Example Subcategory # Markdown here we go! `, }, - { - Name: "invalid YAML", + "invalid YAML": { Source: ` description: |- Example description @@ -55,8 +50,7 @@ Extraneous newline `, ExpectError: true, }, - { - Name: "no layout option", + "no layout option": { Source: ` description: |- Example description @@ -69,8 +63,7 @@ subcategory: Example Subcategory }, ExpectError: true, }, - { - Name: "no page_title option", + "no page_title option": { Source: ` description: |- Example description @@ -83,8 +76,7 @@ subcategory: Example Subcategory }, ExpectError: true, }, - { - Name: "no sidebar_current option", + "no sidebar_current option": { Source: ` description: |- Example description @@ -98,8 +90,7 @@ subcategory: Example Subcategory }, ExpectError: true, }, - { - Name: "no subcategory option", + "no subcategory option": { Source: ` description: |- Example description @@ -112,8 +103,7 @@ subcategory: Example Subcategory }, ExpectError: true, }, - { - Name: "require description option", + "require description option": { Source: ` layout: "example" page_title: Example Page Title @@ -124,8 +114,7 @@ subcategory: Example Subcategory }, ExpectError: true, }, - { - Name: "require layout option", + "require layout option": { Source: ` description: |- Example description @@ -137,8 +126,7 @@ subcategory: Example Subcategory }, ExpectError: true, }, - { - Name: "require page_title option", + "require page_title option": { Source: ` description: |- Example description @@ -152,9 +140,10 @@ subcategory: Example Subcategory }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() got := NewFrontMatterCheck(testCase.Options).Run([]byte(testCase.Source)) diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index 1bfc2f34..4f087b9c 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -13,31 +13,29 @@ import ( func TestValidateStaticDocs(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { BasePath string ExpectError bool ExpectedError string }{ - { - Name: "valid registry directories", + "valid registry directories": { BasePath: filepath.Join("testdata", "valid-registry-directories"), }, - { - Name: "valid registry directories with cdktf docs", + + "valid registry directories with cdktf docs": { BasePath: filepath.Join("testdata", "valid-registry-directories-with-cdktf"), }, - { - Name: "invalid registry directories", + "invalid registry directories": { BasePath: filepath.Join("testdata", "invalid-registry-directories"), ExpectError: true, ExpectedError: "invalid Terraform Provider documentation directory found: " + filepath.Join("docs", "resources", "invalid"), }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() v := &validator{ @@ -66,31 +64,28 @@ func TestValidateStaticDocs(t *testing.T) { func TestValidateLegacyWebsite(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { BasePath string ExpectError bool ExpectedError string }{ - { - Name: "valid legacy directories", + "valid legacy directories": { BasePath: filepath.Join("testdata", "valid-legacy-directories"), }, - { - Name: "valid legacy directories with cdktf docs", + "valid legacy directories with cdktf docs": { BasePath: filepath.Join("testdata", "valid-legacy-directories-with-cdktf"), }, - { - Name: "invalid legacy directories", + "invalid legacy directories": { BasePath: filepath.Join("testdata", "invalid-legacy-directories"), ExpectError: true, ExpectedError: "invalid Terraform Provider documentation directory found: " + filepath.Join("website", "docs", "r", "invalid"), }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() v := &validator{ @@ -119,58 +114,48 @@ func TestValidateLegacyWebsite(t *testing.T) { func TestDocumentationDirGlobPattern(t *testing.T) { t.Parallel() - testCases := []struct { - Name string + testCases := map[string]struct { ExpectMatch bool }{ - { - Name: "docs/data-sources", + "docs/data-sources": { ExpectMatch: true, }, - { - Name: "docs/guides", + "docs/guides": { ExpectMatch: true, }, - { - Name: "docs/resources", + "docs/resources": { ExpectMatch: true, }, - { - Name: "website/docs/r", + "website/docs/r": { ExpectMatch: true, }, - { - Name: "website/docs/r/invalid", + "website/docs/r/invalid": { ExpectMatch: true, }, - { - Name: "website/docs/d", + "website/docs/d": { ExpectMatch: true, }, - { - Name: "website/docs/invalid", + "website/docs/invalid": { ExpectMatch: true, }, - { - Name: "docs/resources/invalid", + "docs/resources/invalid": { ExpectMatch: true, }, - { - Name: "docs/index.md", + "docs/index.md": { ExpectMatch: false, }, - { - Name: "docs/invalid", + "docs/invalid": { ExpectMatch: false, }, } - for _, testCase := range testCases { + for name, testCase := range testCases { + name := name testCase := testCase - t.Run(testCase.Name, func(t *testing.T) { + t.Run(name, func(t *testing.T) { t.Parallel() - match, err := doublestar.Match(DocumentationDirGlobPattern, testCase.Name) + match, err := doublestar.Match(DocumentationDirGlobPattern, name) if err != nil { t.Fatalf("error matching pattern: %q", err) } @@ -180,5 +165,4 @@ func TestDocumentationDirGlobPattern(t *testing.T) { } }) } - } From 7411fa77dd829c4dd59abe1deaa4ec9bbd3e70a6 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 26 Mar 2024 16:47:13 -0400 Subject: [PATCH 27/29] Refactor to use `t.tempDir()` --- internal/check/file_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/check/file_test.go b/internal/check/file_test.go index 4aa21fc8..c8ec7b8b 100644 --- a/internal/check/file_test.go +++ b/internal/check/file_test.go @@ -34,13 +34,9 @@ func TestFileSizeCheck(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - file, err := os.CreateTemp(os.TempDir(), "TestFileSizeCheck") + file, _ := os.CreateTemp(t.TempDir(), "TestFileSizeCheck") - if err != nil { - t.Fatalf("error creating temporary file: %s", err) - } - - defer os.Remove(file.Name()) + defer file.Close() if err := file.Truncate(testCase.Size); err != nil { t.Fatalf("error writing temporary file: %s", err) From 5702e4f3c98c40d97c0e3200b275e8d78eb7725f Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 26 Mar 2024 18:42:19 -0400 Subject: [PATCH 28/29] Fix documentationGlobPattern and add more debug and info logs --- .../framework_provider_success_legacy_docs.txtar | 2 ++ ...ramework_provider_success_registry_docs.txtar | 3 +++ internal/check/directory.go | 5 +++-- internal/check/directory_test.go | 2 ++ internal/check/file_mismatch.go | 4 ++++ internal/provider/validate.go | 16 +++++++++++----- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar index ee8ea9b0..c8452040 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_legacy_docs.txtar @@ -9,6 +9,8 @@ cmp stdout expected-output.txt -- expected-output.txt -- exporting schema from JSON file getting provider schema +running mixed directories check +running number of files check detected legacy website directory, running checks running invalid directories check on website/docs/d running file checks on website/docs/d/example.html.md diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar index e0eaab5b..197fceb1 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_success_registry_docs.txtar @@ -9,6 +9,8 @@ cmpenv stdout expected-output.txt -- expected-output.txt -- exporting schema from JSON file getting provider schema +running mixed directories check +running number of files check detected static docs directory, running checks running invalid directories check on docs/data-sources running file checks on docs/data-sources/example.md @@ -16,6 +18,7 @@ running invalid directories check on docs/functions running file checks on docs/functions/example.md running invalid directories check on docs/guides running file checks on docs/guides/example.md +running file checks on docs/index.md running invalid directories check on docs/resources running file checks on docs/resources/example.md running file mismatch check diff --git a/internal/check/directory.go b/internal/check/directory.go index 18c0d1c4..3aefa6a1 100644 --- a/internal/check/directory.go +++ b/internal/check/directory.go @@ -24,8 +24,6 @@ const ( RegistryResourcesDirectory = `resources` RegistryFunctionsDirectory = `functions` - DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources,functions},website/docs}/**/*` - // Terraform Registry Storage Limits // https://www.terraform.io/docs/registry/providers/docs.html#storage-limits RegistryMaximumNumberOfFiles = 2000 @@ -95,12 +93,14 @@ func MixedDirectoriesCheck(docFiles []string) error { for _, file := range docFiles { directory := filepath.Dir(file) + log.Printf("[DEBUG] Found directory: %s", directory) // Allow docs/ with other files if IsValidRegistryDirectory(directory) && directory != RegistryIndexDirectory { registryDirectoryFound = true if legacyDirectoryFound { + log.Printf("[DEBUG] Found mixed directories") return err } } @@ -109,6 +109,7 @@ func MixedDirectoriesCheck(docFiles []string) error { legacyDirectoryFound = true if registryDirectoryFound { + log.Printf("[DEBUG] Found mixed directories") return err } } diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index 829189d2..dc0c5687 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -12,6 +12,8 @@ import ( "github.com/bmatcuk/doublestar/v4" ) +var DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources,functions}/**/*,website/docs/**/*}` + func TestNumberOfFilesCheck(t *testing.T) { t.Parallel() testCases := map[string]struct { diff --git a/internal/check/file_mismatch.go b/internal/check/file_mismatch.go index 35e6e3a5..d65989fd 100644 --- a/internal/check/file_mismatch.go +++ b/internal/check/file_mismatch.go @@ -93,6 +93,7 @@ func (check *FileMismatchCheck) ResourceFileMismatchCheck(files []os.DirEntry, r var missingFiles []string for _, file := range files { + log.Printf("[DEBUG] Found file %s", file.Name()) if fileHasResource(schemas, check.Options.ProviderShortName, file.Name()) { continue } @@ -101,10 +102,12 @@ func (check *FileMismatchCheck) ResourceFileMismatchCheck(files []os.DirEntry, r continue } + log.Printf("[DEBUG] Found extraneous file %s", file.Name()) extraFiles = append(extraFiles, file.Name()) } for _, resourceName := range resourceNames(schemas) { + log.Printf("[DEBUG] Found %s %s", resourceType, resourceName) if resourceHasFile(files, check.Options.ProviderShortName, resourceName) { continue } @@ -113,6 +116,7 @@ func (check *FileMismatchCheck) ResourceFileMismatchCheck(files []os.DirEntry, r continue } + log.Printf("[DEBUG] Missing file for %s %s", resourceType, resourceName) missingFiles = append(missingFiles, resourceName) } diff --git a/internal/provider/validate.go b/internal/provider/validate.go index 38a4b442..6732091e 100644 --- a/internal/provider/validate.go +++ b/internal/provider/validate.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "log" "os" "path/filepath" @@ -18,11 +19,12 @@ import ( ) const ( - FileExtensionHtmlMarkdown = `.html.markdown` - FileExtensionHtmlMd = `.html.md` - FileExtensionMarkdown = `.markdown` - FileExtensionMd = `.md` - DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources,functions},website/docs}/**/*` + FileExtensionHtmlMarkdown = `.html.markdown` + FileExtensionHtmlMd = `.html.md` + FileExtensionMarkdown = `.markdown` + FileExtensionMd = `.md` + + DocumentationGlobPattern = `{docs/index.md,docs/{,cdktf/}{data-sources,guides,resources,functions}/**/*,website/docs/**/*}` DocumentationDirGlobPattern = `{docs/{,cdktf/}{data-sources,guides,resources,functions}{,/*},website/docs/**/*}` ) @@ -158,9 +160,13 @@ func (v *validator) validate(ctx context.Context) error { return fmt.Errorf("error finding documentation files: %w", err) } + log.Printf("[DEBUG] Found documentation files %v", files) + + v.logger.infof("running mixed directories check") err = check.MixedDirectoriesCheck(files) result = errors.Join(result, err) + v.logger.infof("running number of files check") err = check.NumberOfFilesCheck(files) result = errors.Join(result, err) From 0ed7c0a276e20a421f83a312535ac3e59d6146da Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Tue, 26 Mar 2024 18:42:43 -0400 Subject: [PATCH 29/29] Add negative scenario acceptance tests --- ...amework_provider_error_file_mismatch.txtar | 127 ++++++++++++++++++ ...ork_provider_error_mixed_directories.txtar | 127 ++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_error_file_mismatch.txtar create mode 100644 cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_error_mixed_directories.txtar diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_error_file_mismatch.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_error_file_mismatch.txtar new file mode 100644 index 00000000..8a0d2e19 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_error_file_mismatch.txtar @@ -0,0 +1,127 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Run of tfplugindocs validate command with a misnamed file +[!unix] skip +! exec tfplugindocs validate --provider-name=terraform-provider-scaffolding --providers-schema=schema.json +stderr 'Error executing command: validation errors found:' +stderr 'matching resource for documentation file \(resource2.md\) not found, file is extraneous or incorrectly named' +stderr 'missing documentation file for resource: scaffolding_example' + +-- docs/data-sources/example.md -- +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- docs/resources/resource2.md -- +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- schema.json -- +{ + "format_version": "1.0", + "provider_schemas": { + "registry.terraform.io/hashicorp/scaffolding": { + "provider": { + "version": 0, + "block": { + "attributes": { + "endpoint": { + "type": "string", + "description": "Example provider attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description": "Example provider", + "description_kind": "markdown" + } + }, + "resource_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "defaulted": { + "type": "string", + "description": "Example configurable attribute with default value", + "description_kind": "markdown", + "optional": true, + "computed": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example resource", + "description_kind": "markdown" + } + } + }, + "data_source_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example data source", + "description_kind": "markdown" + } + } + }, + "functions": { + "example": { + "description": "Given a string value, returns the same value.", + "summary": "Echo a string", + "return_type": "string", + "parameters": [ + { + "name": "input", + "description": "Value to echo.", + "type": "string" + } + ], + "variadic_parameter": { + "name": "variadicInput", + "description": "Variadic input to echo.", + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_error_mixed_directories.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_error_mixed_directories.txtar new file mode 100644 index 00000000..77f67ab6 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/validate/framework_provider_error_mixed_directories.txtar @@ -0,0 +1,127 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Run of tfplugindocs validate command with mixed directory structure +[!unix] skip +! exec tfplugindocs validate --provider-name=terraform-provider-scaffolding --providers-schema=schema.json +stderr 'Error executing command: validation errors found:' +stderr 'mixed Terraform Provider documentation directory layouts found, must use only legacy or registry layout' + +-- website/docs/d/example.html.md -- +--- +subcategory: "Example" +layout: "example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- docs/data-sources/example.md -- +--- +subcategory: "Example" +page_title: "Example: example_thing" +description: |- + Example description. +--- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +-- schema.json -- +{ + "format_version": "1.0", + "provider_schemas": { + "registry.terraform.io/hashicorp/scaffolding": { + "provider": { + "version": 0, + "block": { + "attributes": { + "endpoint": { + "type": "string", + "description": "Example provider attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description": "Example provider", + "description_kind": "markdown" + } + }, + "resource_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "defaulted": { + "type": "string", + "description": "Example configurable attribute with default value", + "description_kind": "markdown", + "optional": true, + "computed": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example resource", + "description_kind": "markdown" + } + } + }, + "data_source_schemas": { + "scaffolding_example": { + "version": 0, + "block": { + "attributes": { + "configurable_attribute": { + "type": "string", + "description": "Example configurable attribute", + "description_kind": "markdown", + "optional": true + }, + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + } + }, + "description": "Example data source", + "description_kind": "markdown" + } + } + }, + "functions": { + "example": { + "description": "Given a string value, returns the same value.", + "summary": "Echo a string", + "return_type": "string", + "parameters": [ + { + "name": "input", + "description": "Value to echo.", + "type": "string" + } + ], + "variadic_parameter": { + "name": "variadicInput", + "description": "Variadic input to echo.", + "type": "string" + } + } + } + } + } +} \ No newline at end of file