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()