diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_all_framework_types_blocks_section.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_all_framework_types_blocks_section.txtar new file mode 100644 index 0000000..f74165a --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_all_framework_types_blocks_section.txtar @@ -0,0 +1,644 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs on a Framework provider with a resource and function schema containing all framework types +[!unix] skip +exec tfplugindocs --provider-name=terraform-provider-scaffolding --providers-schema=schema.json --blocks-section +cmp stdout expected-output.txt +cmp docs/index.md expected-index.md +cmp docs/resources/example.md expected-resource.md +cmp docs/functions/scaffolding.md expected-function.md + +-- expected-output.txt -- +rendering website for provider "terraform-provider-scaffolding" (as "terraform-provider-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 missing function content +generating new template for function "scaffolding" +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 "functions/scaffolding.md.tmpl" +rendering "index.md.tmpl" +rendering "resources/example.md.tmpl" +-- expected-index.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding Provider" +subcategory: "" +description: |- + Example provider +--- + +# scaffolding Provider + +Example provider + + + + +## Schema + +### Optional Attributes + +- `endpoint` (String) Example provider attribute +-- expected-resource.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding_example Resource - terraform-provider-scaffolding" +subcategory: "" +description: |- + example resource +--- + +# scaffolding_example (Resource) + +example resource + + + + +## Schema + +### Optional Attributes + +- `bool_attribute` (Boolean) example bool attribute +- `float64_attribute` (Number) example float64 attribute +- `int64_attribute` (Number) example int64 attribute +- `list_attribute` (List of String) example list attribute +- `map_attribute` (Map of String) example map attribute +- `number_attribute` (Number) example number attribute +- `object_attribute` (Object) example object attribute (see [below for nested schema](#nestedatt--object_attribute)) +- `object_attribute_with_nested_object_attribute` (Object) example object attribute with nested object attribute (see [below for nested schema](#nestedatt--object_attribute_with_nested_object_attribute)) +- `sensitive_bool_attribute` (Boolean, Sensitive) example sensitive bool attribute +- `sensitive_float64_attribute` (Number, Sensitive) example sensitive float64 attribute +- `sensitive_int64_attribute` (Number, Sensitive) example sensitive int64 attribute +- `sensitive_list_attribute` (List of String, Sensitive) example sensitive list attribute +- `sensitive_map_attribute` (Map of String, Sensitive) example sensitive map attribute +- `sensitive_number_attribute` (Number, Sensitive) example sensitive number attribute +- `sensitive_object_attribute` (Object, Sensitive) example sensitive object attribute (see [below for nested schema](#nestedatt--sensitive_object_attribute)) +- `sensitive_set_attribute` (Set of String, Sensitive) example sensitive set attribute +- `sensitive_string_attribute` (String, Sensitive) example sensitive string attribute +- `set_attribute` (Set of String) example set attribute +- `string_attribute` (String) example string attribute + +### Blocks + +- `list_nested_block` (Block List) example list nested block (see [below for nested schema](#nestedblock--list_nested_block)) +- `list_nested_block_sensitive_nested_attribute` (Block List) (see [below for nested schema](#nestedblock--list_nested_block_sensitive_nested_attribute)) +- `set_nested_block` (Block Set) example set nested block (see [below for nested schema](#nestedblock--set_nested_block)) +- `single_nested_block` (Block, Optional) example single nested block (see [below for nested schema](#nestedblock--single_nested_block)) +- `single_nested_block_sensitive_nested_attribute` (Block, Optional) example sensitive single nested block (see [below for nested schema](#nestedblock--single_nested_block_sensitive_nested_attribute)) + +### Read-Only + +- `id` (String) The ID of this resource. +- `set_nested_block_sensitive_nested_attribute` (Block Set) example sensitive set nested block (see [below for nested schema](#nestedblock--set_nested_block_sensitive_nested_attribute)) + + +### Nested Schema for `object_attribute` + +Optional Attributes: + +- `object_attribute_attribute` (String) + + + +### Nested Schema for `object_attribute_with_nested_object_attribute` + +Optional Attributes: + +- `nested_object` (Object) (see [below for nested schema](#nestedobjatt--object_attribute_with_nested_object_attribute--nested_object)) +- `object_attribute_attribute` (String) + + +### Nested Schema for `object_attribute_with_nested_object_attribute.nested_object` + +Optional Attributes: + +- `nested_object_attribute` (String) + + + + +### Nested Schema for `sensitive_object_attribute` + +Optional Attributes: + +- `object_attribute_attribute` (String) + + + +### Nested Schema for `list_nested_block` + +Optional Attributes: + +- `list_nested_block_attribute` (String) example list nested block attribute +- `list_nested_block_attribute_with_default` (String) example list nested block attribute with default + +Blocks: + +- `nested_list_block` (Block List) (see [below for nested schema](#nestedblock--list_nested_block--nested_list_block)) + + +### Nested Schema for `list_nested_block.nested_list_block` + +Optional Attributes: + +- `nested_block_string_attribute` (String) example nested block string attribute + + + + +### Nested Schema for `list_nested_block_sensitive_nested_attribute` + +Optional Attributes: + +- `list_nested_block_attribute` (String) example list nested block attribute +- `list_nested_block_sensitive_attribute` (String, Sensitive) example sensitive list nested block attribute + + + +### Nested Schema for `set_nested_block` + +Optional Attributes: + +- `set_nested_block_attribute` (String) example set nested block attribute + + + +### Nested Schema for `single_nested_block` + +Optional Attributes: + +- `single_nested_block_attribute` (String) example single nested block attribute + + + +### Nested Schema for `single_nested_block_sensitive_nested_attribute` + +Optional Attributes: + +- `single_nested_block_attribute` (String) example single nested block attribute +- `single_nested_block_sensitive_attribute` (String, Sensitive) example sensitive single nested block attribute + + + +### Nested Schema for `set_nested_block_sensitive_nested_attribute` + +Read-Only: + +- `set_nested_block_attribute` (String) example set nested block attribute +- `set_nested_block_sensitive_attribute` (String, Sensitive) example sensitive set nested block attribute +-- expected-function.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding function - terraform-provider-scaffolding" +subcategory: "" +description: |- + Echo a string test test +--- + +# function: scaffolding + +Given a string value, returns the same value. + + + +## Signature + + +```text +scaffolding(stringInput string, boolInput bool, float64Input number, int64Input number, listStringInput list of string, mapStringInput map of string, numberInput number, objectInput object, setStringInput set of string, variadicParam string...) string +``` + +## Arguments + + +1. `stringInput` (String) Value to echo testing +1. `boolInput` (Boolean) Value to echo testing +1. `float64Input` (Number) Float64 Value to echo +1. `int64Input` (Number) Int64 Value to echo +1. `listStringInput` (List of String) List of strings to echo +1. `mapStringInput` (Map of String) Map of strings to echo +1. `numberInput` (Number) Number to echo +1. `objectInput` (Object) Object to echo +1. `setStringInput` (Set of String) Set of strings to echo + +1. `variadicParam` (Variadic, String) Value to echo +-- 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": { + "bool_attribute": { + "type": "bool", + "description": "example bool attribute", + "description_kind": "markdown", + "optional": true + }, + "float64_attribute": { + "type": "number", + "description": "example float64 attribute", + "description_kind": "markdown", + "optional": true + }, + "id": { + "type": "string", + "description_kind": "plain", + "computed": true + }, + "int64_attribute": { + "type": "number", + "description": "example int64 attribute", + "description_kind": "markdown", + "optional": true + }, + "list_attribute": { + "type": [ + "list", + "string" + ], + "description": "example list attribute", + "description_kind": "markdown", + "optional": true + }, + "map_attribute": { + "type": [ + "map", + "string" + ], + "description": "example map attribute", + "description_kind": "markdown", + "optional": true + }, + "number_attribute": { + "type": "number", + "description": "example number attribute", + "description_kind": "markdown", + "optional": true + }, + "object_attribute": { + "type": [ + "object", + { + "object_attribute_attribute": "string" + } + ], + "description": "example object attribute", + "description_kind": "markdown", + "optional": true + }, + "object_attribute_with_nested_object_attribute": { + "type": [ + "object", + { + "nested_object": [ + "object", + { + "nested_object_attribute": "string" + } + ], + "object_attribute_attribute": "string" + } + ], + "description": "example object attribute with nested object attribute", + "description_kind": "markdown", + "optional": true + }, + "sensitive_bool_attribute": { + "type": "bool", + "description": "example sensitive bool attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "sensitive_float64_attribute": { + "type": "number", + "description": "example sensitive float64 attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "sensitive_int64_attribute": { + "type": "number", + "description": "example sensitive int64 attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "sensitive_list_attribute": { + "type": [ + "list", + "string" + ], + "description": "example sensitive list attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "sensitive_map_attribute": { + "type": [ + "map", + "string" + ], + "description": "example sensitive map attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "sensitive_number_attribute": { + "type": "number", + "description": "example sensitive number attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "sensitive_object_attribute": { + "type": [ + "object", + { + "object_attribute_attribute": "string" + } + ], + "description": "example sensitive object attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "sensitive_set_attribute": { + "type": [ + "set", + "string" + ], + "description": "example sensitive set attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "sensitive_string_attribute": { + "type": "string", + "description": "example sensitive string attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + }, + "set_attribute": { + "type": [ + "set", + "string" + ], + "description": "example set attribute", + "description_kind": "markdown", + "optional": true + }, + "string_attribute": { + "type": "string", + "description": "example string attribute", + "description_kind": "markdown", + "optional": true + } + }, + "block_types": { + "list_nested_block": { + "nesting_mode": "list", + "block": { + "attributes": { + "list_nested_block_attribute": { + "type": "string", + "description": "example list nested block attribute", + "description_kind": "markdown", + "optional": true + }, + "list_nested_block_attribute_with_default": { + "type": "string", + "description": "example list nested block attribute with default", + "description_kind": "markdown", + "optional": true, + "computed": true + } + }, + "block_types": { + "nested_list_block": { + "nesting_mode": "list", + "block": { + "attributes": { + "nested_block_string_attribute": { + "type": "string", + "description": "example nested block string attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description_kind": "plain" + } + } + }, + "description": "example list nested block", + "description_kind": "markdown" + } + }, + "list_nested_block_sensitive_nested_attribute": { + "nesting_mode": "list", + "block": { + "attributes": { + "list_nested_block_attribute": { + "type": "string", + "description": "example list nested block attribute", + "description_kind": "markdown", + "optional": true + }, + "list_nested_block_sensitive_attribute": { + "type": "string", + "description": "example sensitive list nested block attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + } + }, + "description_kind": "plain" + } + }, + "set_nested_block": { + "nesting_mode": "set", + "block": { + "attributes": { + "set_nested_block_attribute": { + "type": "string", + "description": "example set nested block attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description": "example set nested block", + "description_kind": "markdown" + } + }, + "set_nested_block_sensitive_nested_attribute": { + "nesting_mode": "set", + "block": { + "attributes": { + "set_nested_block_attribute": { + "type": "string", + "description": "example set nested block attribute", + "description_kind": "markdown", + "computed": true + }, + "set_nested_block_sensitive_attribute": { + "type": "string", + "description": "example sensitive set nested block attribute", + "description_kind": "markdown", + "computed": true, + "sensitive": true + } + }, + "description": "example sensitive set nested block", + "description_kind": "markdown" + } + }, + "single_nested_block": { + "nesting_mode": "single", + "block": { + "attributes": { + "single_nested_block_attribute": { + "type": "string", + "description": "example single nested block attribute", + "description_kind": "markdown", + "optional": true + } + }, + "description": "example single nested block", + "description_kind": "markdown" + } + }, + "single_nested_block_sensitive_nested_attribute": { + "nesting_mode": "single", + "block": { + "attributes": { + "single_nested_block_attribute": { + "type": "string", + "description": "example single nested block attribute", + "description_kind": "markdown", + "optional": true + }, + "single_nested_block_sensitive_attribute": { + "type": "string", + "description": "example sensitive single nested block attribute", + "description_kind": "markdown", + "optional": true, + "sensitive": true + } + }, + "description": "example sensitive single nested block", + "description_kind": "markdown" + } + } + }, + "description": "example resource", + "description_kind": "plain" + } + } + }, + "functions": { + "scaffolding": { + "description": "Given a string value, returns the same value.", + "summary": "Echo a string test test", + "return_type": "string", + "parameters": [ + { + "name": "stringInput", + "description": "Value to echo testing", + "type": "string" + }, + { + "name": "boolInput", + "description": "Value to echo testing", + "type": "bool" + }, + { + "name": "float64Input", + "description": "Float64 Value to echo", + "type": "number" + }, + { + "name": "int64Input", + "description": "Int64 Value to echo", + "type": "number" + }, + { + "name": "listStringInput", + "description": "List of strings to echo", + "type": [ + "list", + "string" + ] + }, + { + "name": "mapStringInput", + "description": "Map of strings to echo", + "type": [ + "map", + "string" + ] + }, + { + "name": "numberInput", + "description": "Number to echo", + "type": "number" + }, + { + "name": "objectInput", + "description": "Object to echo", + "type": [ + "object", + { + "attr1": "string", + "attr2": "number" + } + ] + }, + { + "name": "setStringInput", + "description": "Set of strings to echo", + "type": [ + "set", + "string" + ] + } + ], + "variadic_parameter": { + "name": "variadicParam", + "description": "Value to echo", + "type": "string" + } + } + } + } + } +} diff --git a/internal/cmd/generate.go b/internal/cmd/generate.go index 77dbc96..c7ba937 100644 --- a/internal/cmd/generate.go +++ b/internal/cmd/generate.go @@ -19,6 +19,7 @@ type generateCmd struct { flagProviderName string flagRenderedProviderName string + flagBlocksSection bool flagProviderDir string flagProvidersSchema string flagRenderedWebsiteDir string @@ -72,6 +73,7 @@ func (cmd *generateCmd) Help() string { func (cmd *generateCmd) Flags() *flag.FlagSet { fs := flag.NewFlagSet("generate", flag.ExitOnError) + fs.BoolVar(&cmd.flagBlocksSection, "blocks-section", false, "render blocks in a separate section instead of including them with attributes in the required and optional sections.") fs.StringVar(&cmd.flagProviderName, "provider-name", "", "provider name, as used in Terraform configurations; defaults to the --provider-dir short name (after removing `terraform-provider-` prefix)") 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") @@ -109,6 +111,7 @@ func (cmd *generateCmd) runInternal() error { cmd.flagWebsiteSourceDir, cmd.tfVersion, cmd.flagIgnoreDeprecated, + cmd.flagBlocksSection, ) if err != nil { return fmt.Errorf("unable to generate website: %w", err) diff --git a/internal/provider/generate.go b/internal/provider/generate.go index d0c3c96..1325d78 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -79,6 +79,7 @@ var ( ) type generator struct { + blocksSection bool ignoreDeprecated bool tfVersion string @@ -104,7 +105,7 @@ func (g *generator) warnf(format string, a ...interface{}) { g.ui.Warn(fmt.Sprintf(format, a...)) } -func Generate(ui cli.Ui, providerDir, providerName, providersSchemaPath, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, templatesDir, tfVersion string, ignoreDeprecated bool) error { +func Generate(ui cli.Ui, providerDir, providerName, providersSchemaPath, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, templatesDir, tfVersion string, ignoreDeprecated, blocksSection bool) error { // Ensure provider directory is resolved absolute path if providerDir == "" { wd, err := os.Getwd() @@ -136,6 +137,7 @@ func Generate(ui cli.Ui, providerDir, providerName, providersSchemaPath, rendere } g := &generator{ + blocksSection: blocksSection, ignoreDeprecated: ignoreDeprecated, tfVersion: tfVersion, @@ -231,7 +233,7 @@ func (g *generator) Generate(ctx context.Context) error { } g.infof("rendering static website") - err = g.renderStaticWebsite(providerSchema) + err = g.renderStaticWebsite(providerSchema, g.blocksSection) if err != nil { return fmt.Errorf("error rendering static website: %w", err) } @@ -442,7 +444,7 @@ func (g *generator) generateMissingTemplates(providerSchema *tfjson.ProviderSche return nil } -func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) error { +func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema, blocksSection bool) error { g.infof("cleaning rendered website dir") dirEntry, err := os.ReadDir(g.ProviderDocsDir()) if err != nil && !os.IsNotExist(err) { @@ -532,7 +534,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e if resSchema != nil { tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Data Source", exampleFilePath, "", resSchema) + render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Data Source", exampleFilePath, "", resSchema, blocksSection) if err != nil { return fmt.Errorf("unable to render data source template %q: %w", rel, err) } @@ -550,7 +552,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e if resSchema != nil { tmpl := resourceTemplate(tmplData) - render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Resource", exampleFilePath, importFilePath, resSchema) + render, err := tmpl.Render(g.providerDir, resName, g.providerName, g.renderedProviderName, "Resource", exampleFilePath, importFilePath, resSchema, blocksSection) if err != nil { return fmt.Errorf("unable to render resource template %q: %w", rel, err) } @@ -583,7 +585,7 @@ func (g *generator) renderStaticWebsite(providerSchema *tfjson.ProviderSchema) e 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) + render, err := tmpl.Render(g.providerDir, g.providerName, g.renderedProviderName, exampleFilePath, providerSchema.ConfigSchema, blocksSection) 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 5876648..63baa59 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -120,9 +120,9 @@ 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, schema *tfjson.Schema, blocksSection bool) (string, error) { schemaBuffer := bytes.NewBuffer(nil) - err := schemamd.Render(schema, schemaBuffer) + err := schemamd.Render(schema, schemaBuffer, blocksSection) if err != nil { return "", fmt.Errorf("unable to render schema: %w", err) } @@ -158,9 +158,9 @@ func (t providerTemplate) Render(providerDir, providerName, renderedProviderName }) } -func (t resourceTemplate) Render(providerDir, name, providerName, renderedProviderName, typeName, exampleFile, importFile string, schema *tfjson.Schema) (string, error) { +func (t resourceTemplate) Render(providerDir, name, providerName, renderedProviderName, typeName, exampleFile, importFile string, schema *tfjson.Schema, blocksSection bool) (string, error) { schemaBuffer := bytes.NewBuffer(nil) - err := schemamd.Render(schema, schemaBuffer) + err := schemamd.Render(schema, schemaBuffer, blocksSection) if err != nil { return "", fmt.Errorf("unable to render schema: %w", err) } diff --git a/internal/provider/template_test.go b/internal/provider/template_test.go index 82bcc3b..cdd0af3 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", "provider.tf", &schema) + result, err := tpl.Render("testdata/test-provider-dir", "testTemplate", "test-provider", "test-provider", "Resource", "provider.tf", "provider.tf", &schema, false) 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", &schema, false) if err != nil { t.Error(err) } diff --git a/internal/schemamd/behaviors.go b/internal/schemamd/behaviors.go index f0006ef..f56403d 100644 --- a/internal/schemamd/behaviors.go +++ b/internal/schemamd/behaviors.go @@ -77,3 +77,11 @@ func childBlockIsReadOnly(block *tfjson.SchemaBlockType) bool { return true } + +func childBlockIsWritable(block *tfjson.SchemaBlockType) bool { + return !childBlockIsReadOnly(block) +} + +func omitChild[T *tfjson.SchemaAttribute | *tfjson.SchemaBlockType](_ T) bool { + return false +} diff --git a/internal/schemamd/render.go b/internal/schemamd/render.go index a459d0b..3cf20ec 100644 --- a/internal/schemamd/render.go +++ b/internal/schemamd/render.go @@ -21,13 +21,13 @@ import ( // }, // "version": 0 // }, -func Render(schema *tfjson.Schema, w io.Writer) error { +func Render(schema *tfjson.Schema, w io.Writer, blocksSection bool) error { _, err := io.WriteString(w, "## Schema\n\n") if err != nil { return err } - err = writeRootBlock(w, schema.Block) + err = writeRootBlock(w, schema.Block, blocksSection) if err != nil { return fmt.Errorf("unable to render schema: %w", err) } @@ -49,13 +49,29 @@ var ( // * Required // * Optional // * Read-Only - groupFilters = []groupFilter{ + defaultGroupFilters = []groupFilter{ {"### Required", "Required:", childAttributeIsRequired, childBlockIsRequired}, {"### Optional", "Optional:", childAttributeIsOptional, childBlockIsOptional}, {"### Read-Only", "Read-Only:", childAttributeIsReadOnly, childBlockIsReadOnly}, } + + // When --blocks-section is enabled, blocks are rendered in a separate section regardless of their optional or required characteristics. + blocksSectionGroupFilters = []groupFilter{ + {"### Required Attributes", "Required Attributes:", childAttributeIsRequired, omitChild[*tfjson.SchemaBlockType]}, + {"### Optional Attributes", "Optional Attributes:", childAttributeIsOptional, omitChild[*tfjson.SchemaBlockType]}, + {"### Blocks", "Blocks:", omitChild[*tfjson.SchemaAttribute], childBlockIsWritable}, + {"### Read-Only", "Read-Only:", childAttributeIsReadOnly, childBlockIsReadOnly}, + } ) +func getGroupFilters(blocksSection bool) []groupFilter { + if blocksSection { + return blocksSectionGroupFilters + } + + return defaultGroupFilters +} + type nestedType struct { anchorID string pathTitle string @@ -179,8 +195,8 @@ func writeBlockType(w io.Writer, path []string, block *tfjson.SchemaBlockType) ( return []nestedType{nt}, nil } -func writeRootBlock(w io.Writer, block *tfjson.SchemaBlock) error { - return writeBlockChildren(w, nil, block, true) +func writeRootBlock(w io.Writer, block *tfjson.SchemaBlock, blocksSection bool) error { + return writeBlockChildren(w, nil, block, true, blocksSection) } // A Block contains: @@ -210,7 +226,7 @@ func writeRootBlock(w io.Writer, block *tfjson.SchemaBlock) error { // }, // "description_kind": "plain" // }, -func writeBlockChildren(w io.Writer, parents []string, block *tfjson.SchemaBlock, root bool) error { +func writeBlockChildren(w io.Writer, parents []string, block *tfjson.SchemaBlock, root bool, blocksSection bool) error { names := []string{} for n := range block.Attributes { names = append(names, n) @@ -221,6 +237,7 @@ func writeBlockChildren(w io.Writer, parents []string, block *tfjson.SchemaBlock groups := map[int][]string{} + groupFilters := getGroupFilters(blocksSection) // Group Attributes/Blocks by characteristics. nameLoop: for _, n := range names { @@ -336,7 +353,7 @@ nameLoop: } } - err := writeNestedTypes(w, nestedTypes) + err := writeNestedTypes(w, nestedTypes, blocksSection) if err != nil { return err } @@ -344,7 +361,7 @@ nameLoop: return nil } -func writeNestedTypes(w io.Writer, nestedTypes []nestedType) error { +func writeNestedTypes(w io.Writer, nestedTypes []nestedType, blocksSection bool) error { for _, nt := range nestedTypes { _, err := io.WriteString(w, "\n") if err != nil { @@ -358,17 +375,17 @@ func writeNestedTypes(w io.Writer, nestedTypes []nestedType) error { switch { case nt.block != nil: - err = writeBlockChildren(w, nt.path, nt.block, false) + err = writeBlockChildren(w, nt.path, nt.block, false, blocksSection) if err != nil { return err } case nt.object != nil: - err = writeObjectChildren(w, nt.path, *nt.object, nt.group) + err = writeObjectChildren(w, nt.path, *nt.object, nt.group, blocksSection) if err != nil { return err } case nt.attrs != nil: - err = writeNestedAttributeChildren(w, nt.path, nt.attrs, nt.group) + err = writeNestedAttributeChildren(w, nt.path, nt.attrs, nt.group, blocksSection) if err != nil { return err } @@ -450,7 +467,7 @@ func writeObjectAttribute(w io.Writer, path []string, att cty.Type, group groupF return nestedTypes, nil } -func writeObjectChildren(w io.Writer, parents []string, ty cty.Type, group groupFilter) error { +func writeObjectChildren(w io.Writer, parents []string, ty cty.Type, group groupFilter, blocksSection bool) error { _, err := io.WriteString(w, group.nestedTitle+"\n\n") if err != nil { return err @@ -483,7 +500,7 @@ func writeObjectChildren(w io.Writer, parents []string, ty cty.Type, group group return err } - err = writeNestedTypes(w, nestedTypes) + err = writeNestedTypes(w, nestedTypes, blocksSection) if err != nil { return err } @@ -491,13 +508,14 @@ func writeObjectChildren(w io.Writer, parents []string, ty cty.Type, group group return nil } -func writeNestedAttributeChildren(w io.Writer, parents []string, nestedAttributes *tfjson.SchemaNestedAttributeType, group groupFilter) error { +func writeNestedAttributeChildren(w io.Writer, parents []string, nestedAttributes *tfjson.SchemaNestedAttributeType, group groupFilter, blocksSection bool) error { sortedNames := []string{} for n := range nestedAttributes.Attributes { sortedNames = append(sortedNames, n) } sort.Strings(sortedNames) + groupFilters := getGroupFilters(blocksSection) groups := map[int][]string{} for _, name := range sortedNames { att := nestedAttributes.Attributes[name] @@ -542,7 +560,7 @@ func writeNestedAttributeChildren(w io.Writer, parents []string, nestedAttribute } } - err := writeNestedTypes(w, nestedTypes) + err := writeNestedTypes(w, nestedTypes, blocksSection) if err != nil { return err } diff --git a/internal/schemamd/render_test.go b/internal/schemamd/render_test.go index 1770be4..f698ea3 100644 --- a/internal/schemamd/render_test.go +++ b/internal/schemamd/render_test.go @@ -19,40 +19,60 @@ func TestRender(t *testing.T) { t.Parallel() for _, c := range []struct { - name string - inputFile string - expectedFile string + name string + inputFile string + expectedFile string + blocksSection bool }{ { "aws_route_table_association", "testdata/aws_route_table_association.schema.json", "testdata/aws_route_table_association.md", + false, }, { "aws_acm_certificate", "testdata/aws_acm_certificate.schema.json", "testdata/aws_acm_certificate.md", + false, }, { "awscc_logs_log_group", "testdata/awscc_logs_log_group.schema.json", "testdata/awscc_logs_log_group.md", + false, }, { "awscc_acmpca_certificate", "testdata/awscc_acmpca_certificate.schema.json", "testdata/awscc_acmpca_certificate.md", + false, }, { "framework_types", "testdata/framework_types.schema.json", "testdata/framework_types.md", + false, + }, + { + "framework_types_blocks_section", + "testdata/framework_types.schema.json", + "testdata/framework_types_blocks_section.md", + true, }, { // Reference: https://github.com/hashicorp/terraform-plugin-docs/issues/380 "deep_nested_attributes", "testdata/deep_nested_attributes.schema.json", "testdata/deep_nested_attributes.md", + false, + }, + { + // Reference: https://github.com/hashicorp/terraform-plugin-docs/issues/380 + "deep_nested_attributes_blocks_section", + "testdata/deep_nested_attributes.schema.json", + "testdata/deep_nested_attributes_blocks_section.md", + true, }, } { c := c @@ -77,7 +97,7 @@ func TestRender(t *testing.T) { } b := &strings.Builder{} - err = schemamd.Render(&schema, b) + err = schemamd.Render(&schema, b, c.blocksSection) if err != nil { t.Fatal(err) } diff --git a/internal/schemamd/testdata/deep_nested_attributes_blocks_section.md b/internal/schemamd/testdata/deep_nested_attributes_blocks_section.md new file mode 100644 index 0000000..74641a1 --- /dev/null +++ b/internal/schemamd/testdata/deep_nested_attributes_blocks_section.md @@ -0,0 +1,46 @@ +## Schema + +### Required Attributes + +- `level_one` (Attributes) (see [below for nested schema](#nestedatt--level_one)) + +### Read-Only + +- `id` (String) Example identifier + + +### Nested Schema for `level_one` + +Optional Attributes: + +- `level_two` (Attributes) (see [below for nested schema](#nestedatt--level_one--level_two)) + + +### Nested Schema for `level_one.level_two` + +Optional Attributes: + +- `level_three` (Attributes) (see [below for nested schema](#nestedatt--level_one--level_two--level_three)) + + +### Nested Schema for `level_one.level_two.level_three` + +Optional Attributes: + +- `level_four_primary` (Attributes) (see [below for nested schema](#nestedatt--level_one--level_two--level_three--level_four_primary)) +- `level_four_secondary` (String) + + +### Nested Schema for `level_one.level_two.level_three.level_four_primary` + +Optional Attributes: + +- `level_five` (Attributes) Parent should be level_one.level_two.level_three.level_four_primary. (see [below for nested schema](#nestedatt--level_one--level_two--level_three--level_four_primary--level_five)) +- `level_four_primary_string` (String) Parent should be level_one.level_two.level_three.level_four_primary. + + +### Nested Schema for `level_one.level_two.level_three.level_four_primary.level_five` + +Optional Attributes: + +- `level_five_string` (String) Parent should be level_one.level_two.level_three.level_four_primary.level_five. diff --git a/internal/schemamd/testdata/framework_types_blocks_section.md b/internal/schemamd/testdata/framework_types_blocks_section.md new file mode 100644 index 0000000..6601fab --- /dev/null +++ b/internal/schemamd/testdata/framework_types_blocks_section.md @@ -0,0 +1,132 @@ +## Schema + +### Optional Attributes + +- `bool_attribute` (Boolean) example bool attribute +- `float64_attribute` (Number) example float64 attribute +- `int64_attribute` (Number) example int64 attribute +- `list_attribute` (List of String) example list attribute +- `map_attribute` (Map of String) example map attribute +- `number_attribute` (Number) example number attribute +- `object_attribute` (Object) example object attribute (see [below for nested schema](#nestedatt--object_attribute)) +- `object_attribute_with_nested_object_attribute` (Object) example object attribute with nested object attribute (see [below for nested schema](#nestedatt--object_attribute_with_nested_object_attribute)) +- `sensitive_bool_attribute` (Boolean, Sensitive) example sensitive bool attribute +- `sensitive_float64_attribute` (Number, Sensitive) example sensitive float64 attribute +- `sensitive_int64_attribute` (Number, Sensitive) example sensitive int64 attribute +- `sensitive_list_attribute` (List of String, Sensitive) example sensitive list attribute +- `sensitive_map_attribute` (Map of String, Sensitive) example sensitive map attribute +- `sensitive_number_attribute` (Number, Sensitive) example sensitive number attribute +- `sensitive_object_attribute` (Object, Sensitive) example sensitive object attribute (see [below for nested schema](#nestedatt--sensitive_object_attribute)) +- `sensitive_set_attribute` (Set of String, Sensitive) example sensitive set attribute +- `sensitive_string_attribute` (String, Sensitive) example sensitive string attribute +- `set_attribute` (Set of String) example set attribute +- `string_attribute` (String) example string attribute + +### Blocks + +- `list_nested_block` (Block List) example list nested block (see [below for nested schema](#nestedblock--list_nested_block)) +- `list_nested_block_sensitive_nested_attribute` (Block List) (see [below for nested schema](#nestedblock--list_nested_block_sensitive_nested_attribute)) +- `set_nested_block` (Block Set) example set nested block (see [below for nested schema](#nestedblock--set_nested_block)) +- `single_nested_block` (Block, Optional) example single nested block (see [below for nested schema](#nestedblock--single_nested_block)) +- `single_nested_block_sensitive_nested_attribute` (Block, Optional) example sensitive single nested block (see [below for nested schema](#nestedblock--single_nested_block_sensitive_nested_attribute)) + +### Read-Only + +- `id` (String) The ID of this resource. +- `set_nested_block_sensitive_nested_attribute` (Block Set) example sensitive set nested block (see [below for nested schema](#nestedblock--set_nested_block_sensitive_nested_attribute)) + + +### Nested Schema for `object_attribute` + +Optional Attributes: + +- `object_attribute_attribute` (String) + + + +### Nested Schema for `object_attribute_with_nested_object_attribute` + +Optional Attributes: + +- `nested_object` (Object) (see [below for nested schema](#nestedobjatt--object_attribute_with_nested_object_attribute--nested_object)) +- `object_attribute_attribute` (String) + + +### Nested Schema for `object_attribute_with_nested_object_attribute.nested_object` + +Optional Attributes: + +- `nested_object_attribute` (String) + + + + +### Nested Schema for `sensitive_object_attribute` + +Optional Attributes: + +- `object_attribute_attribute` (String) + + + +### Nested Schema for `list_nested_block` + +Optional Attributes: + +- `list_nested_block_attribute` (String) example list nested block attribute +- `list_nested_block_attribute_with_default` (String) example list nested block attribute with default + +Blocks: + +- `nested_list_block` (Block List) (see [below for nested schema](#nestedblock--list_nested_block--nested_list_block)) + + +### Nested Schema for `list_nested_block.nested_list_block` + +Optional Attributes: + +- `nested_block_string_attribute` (String) example nested block string attribute + + + + +### Nested Schema for `list_nested_block_sensitive_nested_attribute` + +Optional Attributes: + +- `list_nested_block_attribute` (String) example list nested block attribute +- `list_nested_block_sensitive_attribute` (String, Sensitive) example sensitive list nested block attribute + + + +### Nested Schema for `set_nested_block` + +Optional Attributes: + +- `set_nested_block_attribute` (String) example set nested block attribute + + + +### Nested Schema for `single_nested_block` + +Optional Attributes: + +- `single_nested_block_attribute` (String) example single nested block attribute + + + +### Nested Schema for `single_nested_block_sensitive_nested_attribute` + +Optional Attributes: + +- `single_nested_block_attribute` (String) example single nested block attribute +- `single_nested_block_sensitive_attribute` (String, Sensitive) example sensitive single nested block attribute + + + +### Nested Schema for `set_nested_block_sensitive_nested_attribute` + +Read-Only: + +- `set_nested_block_attribute` (String) example set nested block attribute +- `set_nested_block_sensitive_attribute` (String, Sensitive) example sensitive set nested block attribute