Skip to content

Commit 79a3fee

Browse files
authored
Fixed handling of unknown values in part blocks (#103)
* update doc link * add failing tests * update model to use List type to handle unknowns * added additional comment to test * added changelog
1 parent 1b3ce76 commit 79a3fee

File tree

8 files changed

+162
-18
lines changed

8 files changed

+162
-18
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: 'cloudinit_config: Fixed handling of unknown values in `part` blocks'
3+
time: 2023-02-22T15:23:08.11379-05:00
4+
custom:
5+
Issue: "103"

docs/resources/config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ description: |-
88

99
# cloudinit_config (Resource)
1010

11-
~> **This resource is deprecated** Please use the [cloudinit_config](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/cloudinit_config)
11+
~> **This resource is deprecated** Please use the [cloudinit_config](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/config)
1212
data source instead.
1313

1414
Renders a [multi-part MIME configuration](https://cloudinit.readthedocs.io/en/latest/explanation/format.html#mime-multi-part-archive) for use with [cloud-init](https://cloudinit.readthedocs.io/en/latest/).

internal/provider/cloudinit_config.go

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import (
2020

2121
// Model and functionality of data source and resource are equivalent.
2222
type configModel struct {
23-
ID types.String `tfsdk:"id"`
24-
Parts []configPartModel `tfsdk:"part"`
25-
Gzip types.Bool `tfsdk:"gzip"`
26-
Base64Encode types.Bool `tfsdk:"base64_encode"`
27-
Boundary types.String `tfsdk:"boundary"`
28-
Rendered types.String `tfsdk:"rendered"`
23+
ID types.String `tfsdk:"id"`
24+
Parts types.List `tfsdk:"part"` // configPartModel
25+
Gzip types.Bool `tfsdk:"gzip"`
26+
Base64Encode types.Bool `tfsdk:"base64_encode"`
27+
Boundary types.String `tfsdk:"boundary"`
28+
Rendered types.String `tfsdk:"rendered"`
2929
}
3030

3131
type configPartModel struct {
@@ -35,7 +35,9 @@ type configPartModel struct {
3535
MergeType types.String `tfsdk:"merge_type"`
3636
}
3737

38-
func (c *configModel) setDefaults() {
38+
func (c *configModel) setDefaults(ctx context.Context) diag.Diagnostics {
39+
var diags diag.Diagnostics
40+
3941
if c.Gzip.IsNull() {
4042
c.Gzip = types.BoolValue(true)
4143
}
@@ -46,20 +48,43 @@ func (c *configModel) setDefaults() {
4648
c.Boundary = types.StringValue("MIMEBOUNDARY")
4749
}
4850

49-
for i, part := range c.Parts {
51+
if c.Parts.IsNull() || c.Parts.IsUnknown() {
52+
return diags
53+
}
54+
55+
var configParts []configPartModel
56+
diags.Append(c.Parts.ElementsAs(ctx, &configParts, false)...)
57+
if diags.HasError() {
58+
return diags
59+
}
60+
61+
for i, part := range configParts {
5062
if part.ContentType.IsNull() || part.ContentType.ValueString() == "" {
51-
c.Parts[i].ContentType = types.StringValue("text/plain")
63+
configParts[i].ContentType = types.StringValue("text/plain")
5264
}
5365
}
66+
67+
partsList, convertDiags := types.ListValueFrom(ctx, c.Parts.ElementType(ctx), configParts)
68+
diags.Append(convertDiags...)
69+
if diags.HasError() {
70+
return diags
71+
}
72+
73+
c.Parts = partsList
74+
75+
return diags
5476
}
5577

56-
func (c configModel) validate() diag.Diagnostics {
78+
func (c configModel) validate(ctx context.Context) diag.Diagnostics {
5779
var diags diag.Diagnostics
5880

5981
if c.Gzip.IsUnknown() || c.Base64Encode.IsUnknown() {
6082
return diags
6183
}
62-
c.setDefaults()
84+
diags.Append(c.setDefaults(ctx)...)
85+
if diags.HasError() {
86+
return diags
87+
}
6388

6489
if c.Gzip.ValueBool() && !c.Base64Encode.ValueBool() {
6590
diags.AddAttributeError(
@@ -79,15 +104,25 @@ func (c *configModel) update(ctx context.Context) diag.Diagnostics {
79104

80105
// cloudinit Provider 'v2.2.0' doesn't actually set default values in state properly, so we need to make sure
81106
// that we don't use any known empty values from previous versions of state
82-
c.setDefaults()
107+
diags.Append(c.setDefaults(ctx)...)
108+
if diags.HasError() {
109+
return diags
110+
}
111+
112+
var configParts []configPartModel
113+
diags.Append(c.Parts.ElementsAs(ctx, &configParts, false)...)
114+
if diags.HasError() {
115+
return diags
116+
}
83117

84118
if c.Gzip.ValueBool() {
85119
gzipWriter := gzip.NewWriter(&buffer)
86-
err = renderPartsToWriter(ctx, c.Boundary.ValueString(), c.Parts, gzipWriter)
120+
121+
err = renderPartsToWriter(ctx, c.Boundary.ValueString(), configParts, gzipWriter)
87122

88123
gzipWriter.Close()
89124
} else {
90-
err = renderPartsToWriter(ctx, c.Boundary.ValueString(), c.Parts, &buffer)
125+
err = renderPartsToWriter(ctx, c.Boundary.ValueString(), configParts, &buffer)
91126
}
92127

93128
if err != nil {

internal/provider/data_source_cloudinit_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (d *configDataSource) ValidateConfig(ctx context.Context, req datasource.Va
2828
return
2929
}
3030

31-
resp.Diagnostics.Append(cloudinitConfig.validate()...)
31+
resp.Diagnostics.Append(cloudinitConfig.validate(ctx)...)
3232
}
3333

3434
func (d *configDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {

internal/provider/data_source_cloudinit_config_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,55 @@ func TestConfigDataSource_UpgradeFromVersion2_2_0(t *testing.T) {
259259
})
260260
}
261261
}
262+
263+
// This test ensures that unknown values are being handled properly in the `part` block
264+
// https://github.com/hashicorp/terraform-provider-cloudinit/issues/102
265+
func TestConfigDataSource_HandleUnknown(t *testing.T) {
266+
testCases := []struct {
267+
Name string
268+
DataSourceBlock string
269+
Expected string
270+
}{
271+
{
272+
"issue 102 - handle unknown in validate caused by variable",
273+
`
274+
variable "config_types" {
275+
default = ["text/cloud-config", "text/cloud-config"]
276+
}
277+
278+
data "cloudinit_config" "foo" {
279+
gzip = true
280+
base64_encode = true
281+
282+
dynamic "part" {
283+
for_each = var.config_types
284+
content {
285+
content_type = part.value
286+
content = <<-EOT
287+
#cloud-config
288+
test: ${part.value}
289+
EOT
290+
}
291+
}
292+
}
293+
`,
294+
"H4sIAAAAAAAA/3LOzytJzSvRDaksSLVSyC3NKcksSCwq0c/NrEhNsVZIyi/NS0ksqrRV8vX0dXXyD/VzcQyKVOIC8XTDUouKM/PzrBQM9Qx4uXi5dHWRFfFywc0uSswrTkst0nXNS85PycxLt1IwT8osQVIAtrwktaJEPzknvzRFNzk/Ly0znZfLNzM3FcMaZWQ1XCWpxSVY9A525+jq8nIBAgAA//821u5zfAEAAA==",
295+
},
296+
}
297+
298+
for _, tt := range testCases {
299+
t.Run(tt.Name, func(t *testing.T) {
300+
r.UnitTest(t, r.TestCase{
301+
Steps: []r.TestStep{
302+
{
303+
ProtoV5ProviderFactories: testProtoV5ProviderFactories,
304+
Config: tt.DataSourceBlock,
305+
Check: r.ComposeTestCheckFunc(
306+
r.TestCheckResourceAttr("data.cloudinit_config.foo", "rendered", tt.Expected),
307+
),
308+
},
309+
},
310+
})
311+
})
312+
}
313+
}

internal/provider/resource_cloudinit_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (r *configResource) ValidateConfig(ctx context.Context, req resource.Valida
3434
return
3535
}
3636

37-
resp.Diagnostics.Append(cloudinitConfig.validate()...)
37+
resp.Diagnostics.Append(cloudinitConfig.validate(ctx)...)
3838
}
3939

4040
func (r *configResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {

internal/provider/resource_cloudinit_config_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,55 @@ func TestConfigResource_UpgradeFromVersion2_2_0(t *testing.T) {
259259
})
260260
}
261261
}
262+
263+
// This test ensures that unknown values are being handled properly in the `part` block
264+
// https://github.com/hashicorp/terraform-provider-cloudinit/issues/102
265+
func TestConfigResource_HandleUnknown(t *testing.T) {
266+
testCases := []struct {
267+
Name string
268+
ResourceBlock string
269+
Expected string
270+
}{
271+
{
272+
"issue 102 - handle unknown in validate caused by variable",
273+
`
274+
variable "config_types" {
275+
default = ["text/cloud-config", "text/cloud-config"]
276+
}
277+
278+
resource "cloudinit_config" "foo" {
279+
gzip = true
280+
base64_encode = true
281+
282+
dynamic "part" {
283+
for_each = var.config_types
284+
content {
285+
content_type = part.value
286+
content = <<-EOT
287+
#cloud-config
288+
test: ${part.value}
289+
EOT
290+
}
291+
}
292+
}
293+
`,
294+
"H4sIAAAAAAAA/3LOzytJzSvRDaksSLVSyC3NKcksSCwq0c/NrEhNsVZIyi/NS0ksqrRV8vX0dXXyD/VzcQyKVOIC8XTDUouKM/PzrBQM9Qx4uXi5dHWRFfFywc0uSswrTkst0nXNS85PycxLt1IwT8osQVIAtrwktaJEPzknvzRFNzk/Ly0znZfLNzM3FcMaZWQ1XCWpxSVY9A525+jq8nIBAgAA//821u5zfAEAAA==",
295+
},
296+
}
297+
298+
for _, tt := range testCases {
299+
t.Run(tt.Name, func(t *testing.T) {
300+
r.UnitTest(t, r.TestCase{
301+
Steps: []r.TestStep{
302+
{
303+
ProtoV5ProviderFactories: testProtoV5ProviderFactories,
304+
Config: tt.ResourceBlock,
305+
Check: r.ComposeTestCheckFunc(
306+
r.TestCheckResourceAttr("cloudinit_config.foo", "rendered", tt.Expected),
307+
),
308+
},
309+
},
310+
})
311+
})
312+
}
313+
}

templates/resources/config.md.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description: |-
66

77
# {{.Name}} ({{.Type}})
88

9-
~> **This resource is deprecated** Please use the [cloudinit_config](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/cloudinit_config)
9+
~> **This resource is deprecated** Please use the [cloudinit_config](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/config)
1010
data source instead.
1111

1212
{{ .Description }}

0 commit comments

Comments
 (0)