diff --git a/.changes/unreleased/FEATURES-20250212-165819.yaml b/.changes/unreleased/FEATURES-20250212-165819.yaml new file mode 100644 index 00000000..83a5a601 --- /dev/null +++ b/.changes/unreleased/FEATURES-20250212-165819.yaml @@ -0,0 +1,5 @@ +kind: FEATURES +body: 'generate: Add support for write-only arguments' +time: 2025-02-12T16:58:19.098098-05:00 +custom: + Issue: "434" diff --git a/.golangci.yml b/.golangci.yml index 82a43ddd..1e7c56bf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,13 @@ issues: - max-per-linter: 0 + max-issues-per-linter: 0 max-same-issues: 0 linters: disable-all: true enable: + - copyloopvar - durationcheck - errcheck - - exportloopref - forcetypeassert - gofmt - gosimple @@ -19,10 +19,10 @@ linters: - paralleltest - predeclared - staticcheck - - tenv - unconvert - unparam - unused + - usetesting run: # Prevent false positive timeouts in CI diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_all_framework_types.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_all_framework_types.txtar index b58019ee..e98192bb 100644 --- a/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_all_framework_types.txtar +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/generate/framework_provider_success_all_framework_types.txtar @@ -93,6 +93,7 @@ example resource - `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)) - `string_attribute` (String) example string attribute +- `write_only_string_attribute` (String, Write-only) example write-only string attribute ### Read-Only @@ -418,6 +419,13 @@ scaffolding(stringInput string, boolInput bool, float64Input number, int64Input "description": "example string attribute", "description_kind": "markdown", "optional": true + }, + "write_only_string_attribute": { + "type": "string", + "description": "example write-only string attribute", + "description_kind": "markdown", + "optional": true, + "write_only": true } }, "block_types": { diff --git a/internal/check/directory_test.go b/internal/check/directory_test.go index b2432de5..274705c5 100644 --- a/internal/check/directory_test.go +++ b/internal/check/directory_test.go @@ -111,8 +111,6 @@ func TestMixedDirectoriesCheck(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/check/file_extension_test.go b/internal/check/file_extension_test.go index 15d756c4..e195c4f3 100644 --- a/internal/check/file_extension_test.go +++ b/internal/check/file_extension_test.go @@ -36,8 +36,6 @@ func TestTrimFileExtension(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() got := TrimFileExtension(testCase.Path) diff --git a/internal/check/file_mismatch_test.go b/internal/check/file_mismatch_test.go index 67f7103d..4beecc5f 100644 --- a/internal/check/file_mismatch_test.go +++ b/internal/check/file_mismatch_test.go @@ -38,8 +38,6 @@ func TestFileHasResource(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -78,8 +76,6 @@ func TestFileResourceName(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() got := fileResourceNameWithProvider("test", testCase.File) @@ -333,8 +329,6 @@ func TestFileMismatchCheck(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -381,8 +375,6 @@ func TestResourceHasFile(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -424,8 +416,6 @@ func TestFunctionHasFile(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -464,8 +454,6 @@ func TestResourceNames(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/check/file_test.go b/internal/check/file_test.go index d29a7e7f..8573b22c 100644 --- a/internal/check/file_test.go +++ b/internal/check/file_test.go @@ -43,8 +43,6 @@ func TestFileSizeCheck(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -83,8 +81,6 @@ func TestFullPath(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/check/frontmatter_test.go b/internal/check/frontmatter_test.go index 9189b0de..b915eb98 100644 --- a/internal/check/frontmatter_test.go +++ b/internal/check/frontmatter_test.go @@ -141,8 +141,6 @@ subcategory: Example Subcategory } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/provider/util_test.go b/internal/provider/util_test.go index 0e04f13e..8a1bc80c 100644 --- a/internal/provider/util_test.go +++ b/internal/provider/util_test.go @@ -68,8 +68,6 @@ func Test_resourceSchema(t *testing.T) { } for name, c := range cases { - name := name - c := c t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go index 03de43a3..3e092c51 100644 --- a/internal/provider/validate_test.go +++ b/internal/provider/validate_test.go @@ -183,8 +183,6 @@ func TestValidateStaticDocs_DirectoryChecks(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -409,8 +407,6 @@ func TestValidateStaticDocs_FileChecks(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -557,8 +553,6 @@ func TestValidateStaticDocs_FileMismatchCheck(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -671,8 +665,6 @@ func TestValidateLegacyWebsite_DirectoryChecks(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -905,8 +897,6 @@ func TestValidateLegacyWebsite_FileChecks(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1053,8 +1043,6 @@ func TestValidateLegacyWebsite_FileMismatchCheck(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() @@ -1116,8 +1104,6 @@ func TestDocumentationDirGlobPattern(t *testing.T) { } for name, testCase := range testCases { - name := name - testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/internal/schemamd/behaviors.go b/internal/schemamd/behaviors.go index f0006ef2..561f34c1 100644 --- a/internal/schemamd/behaviors.go +++ b/internal/schemamd/behaviors.go @@ -19,7 +19,7 @@ func childAttributeIsOptional(att *tfjson.SchemaAttribute) bool { return att.Optional } -// childBlockIsOptional returns true for blocks with with min items 0 +// childBlockIsOptional returns true for blocks with min items 0 // which are either empty or have any required or optional children. func childBlockIsOptional(block *tfjson.SchemaBlockType) bool { if block.MinItems > 0 { diff --git a/internal/schemamd/behaviors_test.go b/internal/schemamd/behaviors_test.go index 48e03336..ed8bb333 100644 --- a/internal/schemamd/behaviors_test.go +++ b/internal/schemamd/behaviors_test.go @@ -37,7 +37,7 @@ func TestChildAttributeIsRequired(t *testing.T) { false, }, } { - c := c + t.Run(c.name, func(t *testing.T) { t.Parallel() @@ -76,7 +76,7 @@ func TestChildAttributeIsOptional(t *testing.T) { true, }, } { - c := c + t.Run(c.name, func(t *testing.T) { t.Parallel() @@ -133,7 +133,7 @@ func TestChildAttributeIsReadOnly(t *testing.T) { true, }, } { - c := c + t.Run(c.name, func(t *testing.T) { t.Parallel() @@ -189,7 +189,7 @@ func TestChildBlockIsRequired(t *testing.T) { false, }, } { - c := c + t.Run(c.name, func(t *testing.T) { t.Parallel() @@ -359,7 +359,7 @@ func TestChildBlockIsOptional(t *testing.T) { true, }, } { - c := c + t.Run(c.name, func(t *testing.T) { t.Parallel() @@ -519,7 +519,7 @@ func TestChildBlockIsReadOnly(t *testing.T) { true, }, } { - c := c + t.Run(c.name, func(t *testing.T) { t.Parallel() diff --git a/internal/schemamd/render_test.go b/internal/schemamd/render_test.go index 1770be42..2cf09eb1 100644 --- a/internal/schemamd/render_test.go +++ b/internal/schemamd/render_test.go @@ -54,8 +54,12 @@ func TestRender(t *testing.T) { "testdata/deep_nested_attributes.schema.json", "testdata/deep_nested_attributes.md", }, + { + "deep_nested_write_only_attributes", + "testdata/deep_nested_write_only_attributes.schema.json", + "testdata/deep_nested_write_only_attributes.md", + }, } { - c := c t.Run(c.name, func(t *testing.T) { t.Parallel() diff --git a/internal/schemamd/testdata/deep_nested_write_only_attributes.md b/internal/schemamd/testdata/deep_nested_write_only_attributes.md new file mode 100644 index 00000000..b421fad2 --- /dev/null +++ b/internal/schemamd/testdata/deep_nested_write_only_attributes.md @@ -0,0 +1,46 @@ +## Schema + +### Required + +- `level_one` (Attributes) (see [below for nested schema](#nestedatt--level_one)) + +### Read-Only + +- `id` (String) Example identifier + + +### Nested Schema for `level_one` + +Optional: + +- `level_two` (Attributes, Write-only) (see [below for nested schema](#nestedatt--level_one--level_two)) + + +### Nested Schema for `level_one.level_two` + +Optional: + +- `level_three` (Attributes, Write-only) (see [below for nested schema](#nestedatt--level_one--level_two--level_three)) + + +### Nested Schema for `level_one.level_two.level_three` + +Optional: + +- `level_four_primary` (Attributes, Write-only) (see [below for nested schema](#nestedatt--level_one--level_two--level_three--level_four_primary)) +- `level_four_secondary` (String, Write-only) + + +### Nested Schema for `level_one.level_two.level_three.level_four_primary` + +Optional: + +- `level_five` (Attributes, Write-only) 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, Write-only) 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: + +- `level_five_string` (String, Write-only) Parent should be level_one.level_two.level_three.level_four_primary.level_five. diff --git a/internal/schemamd/testdata/deep_nested_write_only_attributes.schema.json b/internal/schemamd/testdata/deep_nested_write_only_attributes.schema.json new file mode 100644 index 00000000..5ba4fe27 --- /dev/null +++ b/internal/schemamd/testdata/deep_nested_write_only_attributes.schema.json @@ -0,0 +1,85 @@ +{ + "version": 0, + "block": { + "attributes": { + "id": { + "type": "string", + "description": "Example identifier", + "description_kind": "markdown", + "computed": true + }, + "level_one": { + "nested_type": { + "attributes": { + "level_two": { + "nested_type": { + "attributes": { + "level_three": { + "nested_type": { + "attributes": { + "level_four_primary": { + "nested_type": { + "attributes": { + "level_five": { + "nested_type": { + "attributes": { + "level_five_string": { + "type": "string", + "description": "Parent should be level_one.level_two.level_three.level_four_primary.level_five.", + "description_kind": "plain", + "optional": true, + "write_only": true + } + }, + "nesting_mode": "single" + }, + "description": "Parent should be level_one.level_two.level_three.level_four_primary.", + "description_kind": "plain", + "optional": true, + "write_only": true + }, + "level_four_primary_string": { + "type": "string", + "description": "Parent should be level_one.level_two.level_three.level_four_primary.", + "description_kind": "plain", + "optional": true, + "write_only": true + } + }, + "nesting_mode": "single" + }, + "description_kind": "plain", + "optional": true, + "write_only": true + }, + "level_four_secondary": { + "type": "string", + "description_kind": "plain", + "optional": true, + "write_only": true + } + }, + "nesting_mode": "single" + }, + "description_kind": "plain", + "optional": true, + "write_only": true + } + }, + "nesting_mode": "single" + }, + "description_kind": "plain", + "optional": true, + "write_only": true + } + }, + "nesting_mode": "single" + }, + "description_kind": "plain", + "required": true + } + }, + "description": "Example resource", + "description_kind": "markdown" + } +} \ No newline at end of file diff --git a/internal/schemamd/testdata/framework_types.md b/internal/schemamd/testdata/framework_types.md index 06c04261..781b5bd2 100644 --- a/internal/schemamd/testdata/framework_types.md +++ b/internal/schemamd/testdata/framework_types.md @@ -26,6 +26,7 @@ - `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)) - `string_attribute` (String) example string attribute +- `write_only_string_attribute` (String, Write-only) example write only string attribute ### Read-Only @@ -39,6 +40,7 @@ Optional: - `list_nested_block_attribute` (String) example list nested block attribute - `list_nested_block_attribute_with_default` (String) example list nested block attribute with default +- `list_nested_block_write_only_attribute` (String, Write-only) example list nested block write-only attribute - `nested_list_block` (Block List) (see [below for nested schema](#nestedblock--list_nested_block--nested_list_block)) @@ -98,6 +100,7 @@ Optional: Optional: - `set_nested_block_attribute` (String) example set nested block attribute +- `set_nested_block_write_only_attribute` (String, Write-only) example set nested block write-only attribute @@ -123,4 +126,4 @@ Optional: 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 +- `set_nested_block_sensitive_attribute` (String, Sensitive) example sensitive set nested block attribute \ No newline at end of file diff --git a/internal/schemamd/testdata/framework_types.schema.json b/internal/schemamd/testdata/framework_types.schema.json index 9aac7817..eb7d810a 100644 --- a/internal/schemamd/testdata/framework_types.schema.json +++ b/internal/schemamd/testdata/framework_types.schema.json @@ -168,6 +168,13 @@ "description": "example string attribute", "description_kind": "markdown", "optional": true + }, + "write_only_string_attribute": { + "type": "string", + "description": "example write only string attribute", + "description_kind": "markdown", + "optional": true, + "write_only": true } }, "block_types": { @@ -181,6 +188,13 @@ "description_kind": "markdown", "optional": true }, + "list_nested_block_write_only_attribute": { + "type": "string", + "description": "example list nested block write-only attribute", + "description_kind": "markdown", + "optional": true, + "write_only": true + }, "list_nested_block_attribute_with_default": { "type": "string", "description": "example list nested block attribute with default", @@ -239,6 +253,13 @@ "description": "example set nested block attribute", "description_kind": "markdown", "optional": true + }, + "set_nested_block_write_only_attribute": { + "type": "string", + "description": "example set nested block write-only attribute", + "description_kind": "markdown", + "optional": true, + "write_only": true } }, "description": "example set nested block", diff --git a/internal/schemamd/write_attribute_description.go b/internal/schemamd/write_attribute_description.go index a8aa45b9..f31cccfe 100644 --- a/internal/schemamd/write_attribute_description.go +++ b/internal/schemamd/write_attribute_description.go @@ -58,6 +58,13 @@ func WriteAttributeDescription(w io.Writer, att *tfjson.SchemaAttribute, include } } + if att.WriteOnly { + _, err := io.WriteString(w, ", Write-only") + if err != nil { + return err + } + } + _, err = io.WriteString(w, ")") if err != nil { return err diff --git a/internal/schemamd/write_attribute_description_test.go b/internal/schemamd/write_attribute_description_test.go index c1318425..ee09d4ca 100644 --- a/internal/schemamd/write_attribute_description_test.go +++ b/internal/schemamd/write_attribute_description_test.go @@ -30,6 +30,15 @@ func TestWriteAttributeDescription(t *testing.T) { Description: "This is an attribute.", }, }, + { + "(String, Required, Write-only) This is an attribute.", + &tfjson.SchemaAttribute{ + AttributeType: cty.String, + Required: true, + Description: "This is an attribute.", + WriteOnly: true, + }, + }, { "(String, Required, Deprecated) This is an attribute.", &tfjson.SchemaAttribute{ @@ -154,7 +163,6 @@ func TestWriteAttributeDescription(t *testing.T) { }, }, } { - c := c t.Run(c.expected, func(t *testing.T) { t.Parallel() diff --git a/internal/schemamd/write_block_type_description_test.go b/internal/schemamd/write_block_type_description_test.go index 0ee14a26..e6fa5023 100644 --- a/internal/schemamd/write_block_type_description_test.go +++ b/internal/schemamd/write_block_type_description_test.go @@ -219,7 +219,6 @@ func TestWriteBlockTypeDescription(t *testing.T) { }, }, } { - c := c t.Run(c.expected, func(t *testing.T) { t.Parallel() diff --git a/internal/schemamd/write_nested_attribute_type_description.go b/internal/schemamd/write_nested_attribute_type_description.go index c51be83c..c2f0c117 100644 --- a/internal/schemamd/write_nested_attribute_type_description.go +++ b/internal/schemamd/write_nested_attribute_type_description.go @@ -97,6 +97,13 @@ func WriteNestedAttributeTypeDescription(w io.Writer, att *tfjson.SchemaAttribut } } + if att.WriteOnly { + _, err := io.WriteString(w, ", Write-only") + if err != nil { + return err + } + } + _, err = io.WriteString(w, ")") if err != nil { return err diff --git a/internal/schemamd/write_nested_attribute_type_description_test.go b/internal/schemamd/write_nested_attribute_type_description_test.go index 170284c1..81a12bf4 100644 --- a/internal/schemamd/write_nested_attribute_type_description_test.go +++ b/internal/schemamd/write_nested_attribute_type_description_test.go @@ -38,6 +38,24 @@ func TestWriteNestedAttributeTypeDescription(t *testing.T) { Optional: true, }, }, + { + "(Attributes, Optional, Write-only) This is an attribute.", + &tfjson.SchemaAttribute{ + Description: "This is an attribute.", + AttributeNestedType: &tfjson.SchemaNestedAttributeType{ + NestingMode: tfjson.SchemaNestingModeSingle, + Attributes: map[string]*tfjson.SchemaAttribute{ + "foo": { + AttributeType: cty.String, + Required: true, + Description: "This is a nested attribute.", + }, + }, + }, + Optional: true, + WriteOnly: true, + }, + }, { "(Attributes List, Min: 2, Max: 3) This is an attribute.", &tfjson.SchemaAttribute{ @@ -91,7 +109,6 @@ func TestWriteNestedAttributeTypeDescription(t *testing.T) { }, }, } { - c := c t.Run(c.expected, func(t *testing.T) { t.Parallel() diff --git a/internal/schemamd/write_type_test.go b/internal/schemamd/write_type_test.go index 3def2586..62340ad3 100644 --- a/internal/schemamd/write_type_test.go +++ b/internal/schemamd/write_type_test.go @@ -49,7 +49,6 @@ func TestWriteType(t *testing.T) { "bool": cty.Bool, }))))}, } { - c := c t.Run(fmt.Sprintf("%s %s", c.ty.FriendlyName(), c.expected), func(t *testing.T) { t.Parallel()