diff --git a/.changes/unreleased/FEATURES-20250819-133400.yaml b/.changes/unreleased/FEATURES-20250819-133400.yaml new file mode 100644 index 00000000..0896a503 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250819-133400.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'generate: Support multiple configuration example files in default templates' +time: 2025-08-19T13:34:00.429551-04:00 +custom: + Issue: "508" diff --git a/README.md b/README.md index d6ed83ce..cb42511a 100644 --- a/README.md +++ b/README.md @@ -226,18 +226,18 @@ For examples: > **NOTE:** In the following conventional paths for examples, `` and `` include the provider prefix as well, but the provider prefix is **NOT** included in``. > For example, the data source [`caller_identity`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) in the `aws` provider would have an "example" conventional path of: `examples/data-sources/aws_caller_identity/data-source.tf` -| Path | Description | -|---------------------------------------------------------------------------|--------------------------------------------| -| `examples/` | Root of examples | -| `examples/provider/provider.tf` | Provider example config | -| `examples/actions//action.tf` | Action example config | -| `examples/data-sources//data-source.tf` | Data source example config | -| `examples/ephemeral-resources//ephemeral-resource.tf` | Ephemeral resource example config | -| `examples/functions//function.tf` | Function example config | -| `examples/resources//resource.tf` | Resource example config | -| `examples/resources//import.sh` | Resource example import command | -| `examples/resources//import-by-string-id.tf` | Resource example import by id config | -| `examples/resources//import-by-identity.tf` | Resource example import by identity config | +| Path | Description | +|------------------------------------------------------------------------------|--------------------------------------------| +| `examples/` | Root of examples | +| `examples/provider/provider<*>.tf` | Provider example config(s) | +| `examples/actions//action.tf` | Action example config | +| `examples/data-sources//data-source<*>.tf` | Data source example config(s) | +| `examples/ephemeral-resources//ephemeral-resource<*>.tf` | Ephemeral resource example config(s) | +| `examples/functions//function<*>.tf` | Function example config(s) | +| `examples/resources//resource<*>.tf` | Resource example config(s) | +| `examples/resources//import.sh` | Resource example import command | +| `examples/resources//import-by-string-id.tf` | Resource example import by id config | +| `examples/resources//import-by-identity.tf` | Resource example import by identity config | #### Migration @@ -291,8 +291,10 @@ using the following data fields and functions: | Field | Type | Description | |-------------------------|--------|-------------------------------------------------------------------------------------------| | `.Description` | string | Provider description | -| `.HasExample` | bool | Is there an example file? | -| `.ExampleFile` | string | Path to the file with the terraform configuration example | +| `.HasExample` | bool | (Legacy) Is there an example file? | +| `.HasExamples` | bool | Are there example files? Always true if HasExample is true. | +| `.ExampleFile` | string | (Legacy) Path to the file with the Terraform configuration example. | +| `.ExampleFiles` | string | Paths to the files with the Terraform configuration example. Includes ExampleFile. | | `.ProviderName` | string | Canonical provider name (ex. `terraform-provider-random`) | | `.ProviderShortName` | string | Short version of the rendered provider name (ex. `random`) | | `.RenderedProviderName` | string | Value provided via argument `--rendered-provider-name`, otherwise same as `.ProviderName` | @@ -305,8 +307,10 @@ using the following data fields and functions: | `.Name` | string | Name of the resource/data-source (ex. `tls_certificate`) | | `.Type` | string | Either `Resource` or `Data Source` | | `.Description` | string | Resource / Data Source description | -| `.HasExample` | bool | Is there an example file? | -| `.ExampleFile` | string | Path to the file with the terraform configuration example | +| `.HasExample` | bool | (Legacy) Is there an example file? | +| `.HasExamples` | bool | Are there example files? Always true if HasExample is true. | +| `.ExampleFile` | string | (Legacy) Path to the file with the Terraform configuration example. | +| `.ExampleFiles` | string | Paths to the files with Terraform configuration examples. Includes ExampleFile. | | `.HasImport` | bool | Is there an import shell file? (`terraform import` shell example) | | `.ImportFile` | string | Path to the file with the command for importing the resource | | `.HasImportIDConfig` | bool | Is there an import terraform config file? (`import` block example with `id`) | @@ -327,8 +331,10 @@ using the following data fields and functions: | `.Type` | string | Returns `Function` | | `.Description` | string | Function description | | `.Summary` | string | Function summary | -| `.HasExample` | bool | Is there an example file? | -| `.ExampleFile` | string | Path to the file with the terraform configuration example | +| `.HasExample` | bool | (Legacy) Is there an example file? | +| `.HasExamples` | bool | Are there example files? Always true if HasExample is true. | +| `.ExampleFile` | string | (Legacy) Path to the file with the Terraform configuration example | +| `.ExampleFiles` | string | Paths to the files with Terraform configuration examples. Includes ExampleFile. | | `.ProviderName` | string | Canonical provider name (ex. `terraform-provider-random`) | | `.ProviderShortName` | string | Short version of the rendered provider name (ex. `random`) | | `.RenderedProviderName` | string | Value provided via argument `--rendered-provider-name`, otherwise same as `.ProviderName` | diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates_multiple_examples.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates_multiple_examples.txtar new file mode 100644 index 00000000..40da7337 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates_multiple_examples.txtar @@ -0,0 +1,548 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs on a Framework provider with multiple configuration examples but no templates or pre-exiting docs. +[!unix] skip +exec tfplugindocs --provider-name=terraform-provider-scaffolding --providers-schema=schema.json --rendered-provider-name=Scaffolding +cmp stdout expected-output.txt +cmp docs/index.md expected-index.md +cmp docs/data-sources/example.md expected-datasource.md +cmp docs/resources/example.md expected-resource.md +cmp docs/functions/example.md expected-function.md +cmp docs/functions/no-variadic.md expected-no-variadic-function.md +cmp docs/ephemeral-resources/example.md expected-ephemeral-resource.md + +-- expected-output.txt -- +rendering website for provider "terraform-provider-scaffolding" (as "Scaffolding") +exporting schema from JSON file +getting provider schema +generating missing templates +generating missing resource content +generating new template for "scaffolding_example" +generating missing data source content +generating new template for data-source "scaffolding_example" +generating missing function content +generating new template for function "example" +generating new template for function "no-variadic" +generating missing ephemeral resource content +generating new template for "scaffolding_example" +generating missing action content +generating missing provider content +generating new template for "terraform-provider-scaffolding" +rendering static website +cleaning rendered website dir +rendering templated website to static markdown +rendering "data-sources/example.md.tmpl" +rendering "ephemeral-resources/example.md.tmpl" +rendering "functions/example.md.tmpl" +rendering "functions/no-variadic.md.tmpl" +rendering "index.md.tmpl" +rendering "resources/example.md.tmpl" +-- expected-datasource.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding_example Data Source - Scaffolding" +subcategory: "" +description: |- + Example data source +--- + +# scaffolding_example (Data Source) + +Example data source + +## Example Usage + +```terraform +data "scaffolding_example" "example" { + configurable_attribute = "some-value1" +} +``` + +```terraform +data "scaffolding_example" "example" { + configurable_attribute = "some-value2" +} +``` + + +## Schema + +### Optional + +- `configurable_attribute` (String) Example configurable attribute + +### Read-Only + +- `id` (String) Example identifier +-- expected-function.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "example function - Scaffolding" +subcategory: "" +description: |- + Echo a string +--- + +# function: example + +Given a string value, returns the same value. + +## Example Usage + +```terraform +output "test" { + value = provider::scaffolding::example("testvalue1", "testvalue2") +} +``` + +```terraform +output "test" { + value = provider::scaffolding::example("testvalue3", "testvalue4") +} +``` + +## Signature + + +```text +example(input string, variadicInput string...) string +``` + +## Arguments + + +1. `input` (String) Value to echo. + +1. `variadicInput` (Variadic, String) Variadic input to echo. +-- expected-no-variadic-function.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "no-variadic function - Scaffolding" +subcategory: "" +description: |- + Echo a string +--- + +# function: no-variadic + +Given a string value, returns the same value. + +## Example Usage + +```terraform +output "test" { + value = provider::scaffolding::no-variadic("testvalue1") +} +``` + +```terraform +output "test" { + value = provider::scaffolding::no-variadic("testvalue2") +} +``` + +## Signature + + +```text +no-variadic(input string) string +``` + +## Arguments + + +1. `input` (String) Value to echo. +-- expected-index.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "Scaffolding Provider" +description: |- + Example provider +--- + +# Scaffolding Provider + +Example provider + +## Example Usage + +```terraform +provider "scaffolding" { + # first example configuration here +} +``` + +```terraform +provider "scaffolding" { + # second example configuration here +} +``` + + +## Schema + +### Optional + +- `endpoint` (String) Example provider attribute +-- expected-resource.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding_example Resource - Scaffolding" +subcategory: "" +description: |- + Example resource +--- + +# scaffolding_example (Resource) + +Example resource + +## Example Usage + +```terraform +resource "scaffolding_example" "example" { + configurable_attribute = "some-value1" +} +``` + +```terraform +resource "scaffolding_example" "example" { + configurable_attribute = "some-value2" +} +``` + + +## Schema + +### Optional + +- `configurable_attribute` (String) Example configurable attribute +- `defaulted` (String) Example configurable attribute with default value + +### Read-Only + +- `id` (String) Example identifier + +## Import + +Import is supported using the following syntax: + +In Terraform v1.12.0 and later, the [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used with the `identity` attribute, for example: + +```terraform +import { + to = scaffolding_example.example + identity = { + name = "resource-abc" + project = "test-project-123" # can also be read via SCAFFOLDING_PROJECT environment variable + region = "us-east-2" # can also be read via SCAFFOLDING_REGION environment variable + } +} + +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +``` + + +### Identity Schema + +#### Required + +- `name` (String) Name of resource. + +#### Optional + +- `project` (String) Project of resource, can also be sourced via `SCAFFOLDING_PROJECT` environment variable. +- `region` (String) Region of resource, can also be sourced via `SCAFFOLDING_REGION` environment variable. + +In Terraform v1.5.0 and later, the [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used with the `id` attribute, for example: + +```terraform +import { + to = scaffolding_example.example + id = "id-123" +} + +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +``` +-- expected-ephemeral-resource.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding_example Ephemeral Resource - Scaffolding" +subcategory: "" +description: |- + Example ephemeral resource +--- + +# scaffolding_example (Ephemeral Resource) + +Example ephemeral resource + +## Example Usage + +```terraform +resource "scaffolding_example" "example" { + configurable_attribute = "some-value1" +} +``` + +```terraform +resource "scaffolding_example" "example" { + configurable_attribute = "some-value2" +} +``` + + +## Schema + +### Optional + +- `configurable_attribute` (String) Example configurable attribute +- `defaulted` (String) Example configurable attribute with default value + +### Read-Only + +- `id` (String) Example identifier +-- examples/README.md -- +# Examples + +This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. + +The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. + +* **provider/provider.tf** example file for the provider index page +* **data-sources/`full data source name`/data-source.tf** example file for the named data source page +* **resources/`full resource name`/resource.tf** example file for the named data source page +-- examples/data-sources/scaffolding_example/data-source.tf -- +data "scaffolding_example" "example" { + configurable_attribute = "some-value1" +} +-- examples/data-sources/scaffolding_example/data-source2.tf -- +data "scaffolding_example" "example" { + configurable_attribute = "some-value2" +} +-- examples/functions/example/function.tf -- +output "test" { + value = provider::scaffolding::example("testvalue1", "testvalue2") +} +-- examples/functions/example/function2.tf -- +output "test" { + value = provider::scaffolding::example("testvalue3", "testvalue4") +} +-- examples/functions/no-variadic/function.tf -- +output "test" { + value = provider::scaffolding::no-variadic("testvalue1") +} +-- examples/functions/no-variadic/function2.tf -- +output "test" { + value = provider::scaffolding::no-variadic("testvalue2") +} +-- examples/provider/provider.tf -- +provider "scaffolding" { + # first example configuration here +} +-- examples/provider/provider2.tf -- +provider "scaffolding" { + # second example configuration here +} +-- examples/resources/scaffolding_example/import-by-string-id.tf -- +import { + to = scaffolding_example.example + id = "id-123" +} + +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/resources/scaffolding_example/import-by-identity.tf -- +import { + to = scaffolding_example.example + identity = { + name = "resource-abc" + project = "test-project-123" # can also be read via SCAFFOLDING_PROJECT environment variable + region = "us-east-2" # can also be read via SCAFFOLDING_REGION environment variable + } +} + +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/resources/scaffolding_example/resource.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value1" +} +-- examples/resources/scaffolding_example/resource2.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value2" +} +-- examples/ephemeral-resources/scaffolding_example/ephemeral-resource.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value1" +} +-- examples/ephemeral-resources/scaffolding_example/ephemeral-resource2.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value2" +} +-- 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" + } + } + }, + "resource_identity_schemas": { + "scaffolding_example": { + "version": 0, + "attributes": { + "name": { + "type": "string", + "description": "Name of resource.", + "required_for_import": true + }, + "project": { + "type": "string", + "description": "Project of resource, can also be sourced via `SCAFFOLDING_PROJECT` environment variable.", + "optional_for_import": true + }, + "region": { + "type": "string", + "description": "Region of resource, can also be sourced via `SCAFFOLDING_REGION` environment variable.", + "optional_for_import": true + } + } + } + }, + "ephemeral_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 ephemeral 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-variadic": { + "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" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates_single_example.txtar similarity index 100% rename from cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates.txtar rename to cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_no_templates_single_example.txtar diff --git a/internal/provider/generate.go b/internal/provider/generate.go index ee9a099e..dffa8016 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -692,11 +692,20 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e switch relDir { case "data-sources/": resSchema, resName := resourceSchema(providerSchema.DataSourceSchemas, shortName, relFile) - exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "data-sources", resName, "data-source.tf") if resSchema != nil { + exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "data-sources", resName, "data-source.tf") + exampleFilesPattern := filepath.Join(g.ProviderExamplesDir(), "data-sources", resName, "data-source*.tf") + exampleFiles, err := filepath.Glob(exampleFilesPattern) + + if err != nil { + return fmt.Errorf("unable to glob example files with pattern %q: %w", exampleFilesPattern, err) + } + + slices.Sort(exampleFiles) + tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Data Source", exampleFilePath, "", "", "", resSchema, nil) + render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Data Source", exampleFilePath, exampleFiles, "", "", "", resSchema, nil) if err != nil { return fmt.Errorf("unable to render data source template %q: %w", rel, err) } @@ -710,14 +719,24 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e case "resources/": resSchema, resName := resourceSchema(providerSchema.ResourceSchemas, shortName, relFile) resIdentitySchema := resourceIdentitySchema(providerSchema.ResourceIdentitySchemas, shortName, relFile) - exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "resource.tf") - importFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "import.sh") - importIDConfigFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "import-by-string-id.tf") - importIdentityConfigFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "import-by-identity.tf") if resSchema != nil { + exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "resource.tf") + exampleFilesPattern := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "resource*.tf") + exampleFiles, err := filepath.Glob(exampleFilesPattern) + + if err != nil { + return fmt.Errorf("unable to glob example files with pattern %q: %w", exampleFilesPattern, err) + } + + slices.Sort(exampleFiles) + + importFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "import.sh") + importIDConfigFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "import-by-string-id.tf") + importIdentityConfigFilePath := filepath.Join(g.ProviderExamplesDir(), "resources", resName, "import-by-identity.tf") + tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Resource", exampleFilePath, importIDConfigFilePath, importIdentityConfigFilePath, importFilePath, resSchema, resIdentitySchema) + render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Resource", exampleFilePath, exampleFiles, importIDConfigFilePath, importIdentityConfigFilePath, importFilePath, resSchema, resIdentitySchema) if err != nil { return fmt.Errorf("unable to render resource template %q: %w", rel, err) } @@ -732,9 +751,17 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e funcName := removeAllExt(relFile) if signature, ok := providerSchema.Functions[funcName]; ok { exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "functions", funcName, "function.tf") + exampleFilesPattern := filepath.Join(g.ProviderExamplesDir(), "functions", funcName, "function*.tf") + exampleFiles, err := filepath.Glob(exampleFilesPattern) + + if err != nil { + return fmt.Errorf("unable to glob example files with pattern %q: %w", exampleFilesPattern, err) + } + + slices.Sort(exampleFiles) tmpl := functionTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, funcName, g.providerName, g.renderedProviderName, "function", exampleFilePath, signature) + render, err := tmpl.Render(g.providerDir, funcName, g.providerName, g.renderedProviderName, "function", exampleFilePath, exampleFiles, signature) if err != nil { return fmt.Errorf("unable to render function template %q: %w", rel, err) } @@ -748,11 +775,20 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e g.warnf("function entitled %q does not exist", funcName) case "ephemeral-resources/": resSchema, resName := resourceSchema(providerSchema.EphemeralResourceSchemas, shortName, relFile) - exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "ephemeral-resources", resName, "ephemeral-resource.tf") if resSchema != nil { + exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "ephemeral-resources", resName, "ephemeral-resource.tf") + exampleFilesPattern := filepath.Join(g.ProviderExamplesDir(), "ephemeral-resources", resName, "ephemeral-resource*.tf") + exampleFiles, err := filepath.Glob(exampleFilesPattern) + + if err != nil { + return fmt.Errorf("unable to glob example files with pattern %q: %w", exampleFilesPattern, err) + } + + slices.Sort(exampleFiles) + tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Ephemeral Resource", exampleFilePath, "", "", "", resSchema, nil) + render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Ephemeral Resource", exampleFilePath, exampleFiles, "", "", "", resSchema, nil) if err != nil { return fmt.Errorf("unable to render ephemeral resource template %q: %w", rel, err) } @@ -782,9 +818,18 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e g.warnf("action entitled %q, or %q does not exist", shortName, resName) case "": // provider if relFile == "index.md.tmpl" { - tmpl := providerTemplate(tmplData) exampleFilePath := filepath.Join(g.ProviderExamplesDir(), "provider", "provider.tf") - render, err := tmpl.Render(g.providerDir, g.providerName, g.renderedProviderName, exampleFilePath, providerSchema.ConfigSchema) + exampleFilesPattern := filepath.Join(g.ProviderExamplesDir(), "provider", "provider*.tf") + exampleFiles, err := filepath.Glob(exampleFilesPattern) + + if err != nil { + return fmt.Errorf("unable to glob example files with pattern %q: %w", exampleFilesPattern, err) + } + + slices.Sort(exampleFiles) + + tmpl := providerTemplate(tmplData) + render, err := tmpl.Render(g.providerDir, g.providerName, g.renderedProviderName, exampleFilePath, exampleFiles, providerSchema.ConfigSchema) if err != nil { return fmt.Errorf("unable to render provider template %q: %w", rel, err) } diff --git a/internal/provider/template.go b/internal/provider/template.go index add995e8..2d1bfc8a 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -45,8 +45,10 @@ type ResourceTemplateType struct { Name string Description string - HasExample bool - ExampleFile string + HasExample bool + HasExamples bool + ExampleFile string + ExampleFiles []string HasImport bool ImportFile string @@ -69,8 +71,10 @@ type ResourceTemplateType struct { type ProviderTemplateType struct { Description string - HasExample bool - ExampleFile string + HasExample bool + HasExamples bool + ExampleFile string + ExampleFiles []string ProviderName string ProviderShortName string @@ -85,8 +89,10 @@ type FunctionTemplateType struct { Description string Summary string - HasExample bool - ExampleFile string + HasExample bool + HasExamples bool + ExampleFile string + ExampleFiles []string ProviderName string ProviderShortName string @@ -180,7 +186,7 @@ func (t docTemplate) Render(providerDir string, out io.Writer) error { return renderTemplate(providerDir, "docTemplate", s, out, nil) } -func (t providerTemplate) Render(providerDir, providerName, renderedProviderName, exampleFile string, schema *tfjson.Schema) (string, error) { +func (t providerTemplate) Render(providerDir, providerName, renderedProviderName, exampleFile string, exampleFiles []string, schema *tfjson.Schema) (string, error) { schemaBuffer := bytes.NewBuffer(nil) err := schemamd.Render(schema, schemaBuffer) if err != nil { @@ -195,8 +201,10 @@ func (t providerTemplate) Render(providerDir, providerName, renderedProviderName return renderStringTemplate(providerDir, "providerTemplate", s, ProviderTemplateType{ Description: schema.Block.Description, - HasExample: exampleFile != "" && fileExists(exampleFile), - ExampleFile: exampleFile, + HasExample: exampleFile != "" && fileExists(exampleFile), + HasExamples: len(exampleFiles) > 0, + ExampleFile: exampleFile, + ExampleFiles: exampleFiles, ProviderName: providerName, ProviderShortName: providerShortName(renderedProviderName), @@ -207,7 +215,7 @@ func (t providerTemplate) Render(providerDir, providerName, renderedProviderName }) } -func (t resourceTemplate) Render(providerDir, name, providerName, renderedProviderName, typeName, exampleFile, importIDConfigFile, importIdentityConfigFile, importCmdFile string, schema *tfjson.Schema, identitySchema *tfjson.IdentitySchema) (string, error) { +func (t resourceTemplate) Render(providerDir, name, providerName, renderedProviderName, typeName, exampleFile string, exampleFiles []string, importIDConfigFile, importIdentityConfigFile, importCmdFile string, schema *tfjson.Schema, identitySchema *tfjson.IdentitySchema) (string, error) { schemaBuffer := bytes.NewBuffer(nil) err := schemamd.Render(schema, schemaBuffer) if err != nil { @@ -243,8 +251,10 @@ func (t resourceTemplate) Render(providerDir, name, providerName, renderedProvid Name: name, Description: schema.Block.Description, - HasExample: exampleFile != "" && fileExists(exampleFile), - ExampleFile: exampleFile, + HasExample: exampleFile != "" && fileExists(exampleFile), + HasExamples: len(exampleFiles) > 0, + ExampleFile: exampleFile, + ExampleFiles: exampleFiles, HasImport: importCmdFile != "" && fileExists(importCmdFile), ImportFile: importCmdFile, @@ -265,7 +275,7 @@ func (t resourceTemplate) Render(providerDir, name, providerName, renderedProvid }) } -func (t functionTemplate) Render(providerDir, name, providerName, renderedProviderName, typeName, exampleFile string, signature *tfjson.FunctionSignature) (string, error) { +func (t functionTemplate) Render(providerDir, name, providerName, renderedProviderName, typeName, exampleFile string, exampleFiles []string, signature *tfjson.FunctionSignature) (string, error) { funcSig, err := functionmd.RenderSignature(name, signature) if err != nil { return "", fmt.Errorf("unable to render function signature: %w", err) @@ -292,8 +302,10 @@ func (t functionTemplate) Render(providerDir, name, providerName, renderedProvid Description: signature.Description, Summary: signature.Summary, - HasExample: exampleFile != "" && fileExists(exampleFile), - ExampleFile: exampleFile, + HasExample: exampleFile != "" && fileExists(exampleFile), + HasExamples: len(exampleFiles) > 0, + ExampleFile: exampleFile, + ExampleFiles: exampleFiles, ProviderName: providerName, ProviderShortName: providerShortName(renderedProviderName), @@ -320,10 +332,13 @@ description: |- {{ .Description | trimspace }} -{{ if .HasExample -}} +{{ if .HasExamples -}} ## Example Usage -{{tffile .ExampleFile }} +{{- range .ExampleFiles }} + +{{ tffile . }} +{{- end }} {{- end }} {{ .SchemaMarkdown | trimspace }} @@ -367,10 +382,13 @@ description: |- {{ .Description | trimspace }} -{{ if .HasExample -}} +{{ if .HasExamples -}} ## Example Usage -{{tffile .ExampleFile }} +{{- range .ExampleFiles }} + +{{ tffile . }} +{{- end }} {{- end }} ## Signature @@ -396,10 +414,13 @@ description: |- {{ .Description | trimspace }} -{{ if .HasExample -}} +{{ if .HasExamples -}} ## Example Usage -{{tffile .ExampleFile }} +{{- range .ExampleFiles }} + +{{ tffile . }} +{{- end }} {{- end }} {{ .SchemaMarkdown | trimspace }} diff --git a/internal/provider/template_test.go b/internal/provider/template_test.go index d60e3992..426e4ff2 100644 --- a/internal/provider/template_test.go +++ b/internal/provider/template_test.go @@ -93,7 +93,7 @@ provider "scaffolding" { }, } - result, err := tpl.Render("testdata/test-provider-dir", "testTemplate", "test-provider", "test-provider", "Resource", "provider.tf", "", "", "", &schema, nil) + result, err := tpl.Render("testdata/test-provider-dir", "testTemplate", "test-provider", "test-provider", "Resource", "provider.tf", []string{"provider.tf"}, "", "", "", &schema, nil) if err != nil { t.Error(err) } @@ -133,7 +133,7 @@ provider "scaffolding" { }, } - result, err := tpl.Render("testdata/test-provider-dir", "testTemplate", "test-provider", "provider.tf", &schema) + result, err := tpl.Render("testdata/test-provider-dir", "testTemplate", "test-provider", "provider.tf", []string{"provider.tf"}, &schema) if err != nil { t.Error(err) }