Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/1684.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Add `"json` as a supported `type` for the `set` block
```
2 changes: 1 addition & 1 deletion docs/resources/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ The `set`, `set_list`, and `set_sensitive` blocks support:

* `name` - (Required) full name of the variable to be set.
* `value` - (Required; Optional for `set`) value of the variable to be set.
* `type` - (Optional) type of the variable to be set. Valid options are `auto` and `string`.
* `type` - (Optional) type of the variable to be set. Valid options are `auto`, `string` and `json`.

Since Terraform Utilizes HCL as well as Helm using the Helm Template Language, it's necessary to escape the `{}`, `[]`, `.`, and `,` characters twice in order for it to be parsed. `name` should also be set to the `value path`, and `value` is the desired value that will be set.

Expand Down
35 changes: 33 additions & 2 deletions helm/data_helm_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func (d *HelmTemplate) Schema(ctx context.Context, req datasource.SchemaRequest,
Optional: true,
Computed: true,
Validators: []validator.String{
stringvalidator.OneOf("auto", "string", "literal"),
stringvalidator.OneOf("auto", "string", "literal", "json"),
},
},
},
Expand Down Expand Up @@ -335,7 +335,7 @@ func (d *HelmTemplate) Schema(ctx context.Context, req datasource.SchemaRequest,
"type": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf("auto", "string", "literal"),
stringvalidator.OneOf("auto", "string", "literal", "json"),
},
},
},
Expand Down Expand Up @@ -943,6 +943,37 @@ func applySetValue(base map[string]interface{}, set SetValue) diag.Diagnostics {
} else {
base[name] = literal
}
case "json":
var jsonValue interface{}
if err := json.Unmarshal([]byte(value), &jsonValue); err != nil {
diags.AddError(
"Failed parsing JSON value",
fmt.Sprintf("Key %q with json value %s: %s", name, value, err),
)
return diags
}

pathKeys := strings.Split(name, ".")
m := base
for i, k := range pathKeys {
if i == len(pathKeys)-1 {
m[k] = jsonValue
} else {
if _, exists := m[k]; !exists {
m[k] = map[string]interface{}{}
}
if nested, ok := m[k].(map[string]interface{}); ok {
m = nested
} else {
diags.AddError(
"JSON merge error",
fmt.Sprintf("Cannot merge JSON into an non-object path %q", strings.Join(pathKeys[:i+1], ".")),
)
return diags
}
}
}

default:
diags.AddError("Unexpected type", fmt.Sprintf("Unexpected type: %s", valueType))
}
Expand Down
35 changes: 33 additions & 2 deletions helm/resource_helm_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ func (r *HelmRelease) Schema(ctx context.Context, req resource.SchemaRequest, re
Computed: true,
Default: stringdefault.StaticString(""),
Validators: []validator.String{
stringvalidator.OneOf("auto", "string", "literal"),
stringvalidator.OneOf("auto", "string", "literal", "json"),
},
},
},
Expand Down Expand Up @@ -614,7 +614,7 @@ func (r *HelmRelease) Schema(ctx context.Context, req resource.SchemaRequest, re
"type": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf("auto", "string", "literal"),
stringvalidator.OneOf("auto", "string", "literal", "json"),
},
},
},
Expand Down Expand Up @@ -1508,6 +1508,37 @@ func getValue(base map[string]interface{}, set setResourceModel) diag.Diagnostic
} else {
base[name] = literal
}
case "json":
var jsonValue interface{}
if err := json.Unmarshal([]byte(value), &jsonValue); err != nil {
diags.AddError(
"Failed parsing JSON value",
fmt.Sprintf("Key %q with json value %s: %s", name, value, err),
)
return diags
}

pathKeys := strings.Split(name, ".")
m := base
for i, k := range pathKeys {
if i == len(pathKeys)-1 {
m[k] = jsonValue
} else {
if _, exists := m[k]; !exists {
m[k] = map[string]interface{}{}
}
if nested, ok := m[k].(map[string]interface{}); ok {
m = nested
} else {
diags.AddError(
"JSON merge error",
fmt.Sprintf("Cannot merge JSON into an non-object path %q", strings.Join(pathKeys[:i+1], ".")),
)
return diags
}
}
}

default:
diags.AddError("Unexpected type", fmt.Sprintf("Unexpected type: %s", valueType))
return diags
Expand Down
123 changes: 123 additions & 0 deletions helm/resource_helm_release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2082,6 +2082,129 @@ func TestAccResourceRelease_literalSet(t *testing.T) {
})
}

func TestAccResourceRelease_jsonSet(t *testing.T) {
name := randName("json-set")
namespace := createRandomNamespace(t)
defer deleteNamespace(t, namespace)

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{
{
Config: testAccHelmReleaseConfigSetJSON(testResourceName, namespace, name, "1.2.3"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("helm_release.test", "metadata.name", name),
resource.TestCheckResourceAttr("helm_release.test", "metadata.namespace", namespace),
resource.TestCheckResourceAttr("helm_release.test", "metadata.revision", "1"),
resource.TestCheckResourceAttr("helm_release.test", "status", release.StatusDeployed.String()),
resource.TestCheckResourceAttr("helm_release.test", "metadata.values", "{\"nested\":{\"a\":true,\"b\":[1,2,3]}}"),
),
},
},
})
}

func testAccHelmReleaseConfigSetJSON(resource, ns, name, version string) string {
return fmt.Sprintf(`
resource "helm_release" "%s" {
name = %q
namespace = %q
description = "Test JSON set"
repository = %q
chart = "test-chart"
version = %q

set = [
{
name = "nested"
value = jsonencode({ a = true, b = [1, 2, 3] })
type = "json"
}
]
}
`, resource, name, ns, testRepositoryURL, version)
}

// Testing missing closing bracket json input
func TestAccResourceRelease_jsonSetInvalid(t *testing.T) {
name := randName("json-set-invalid")
namespace := createRandomNamespace(t)
defer deleteNamespace(t, namespace)

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{
{
Config: testAccHelmReleaseConfigSetJSONInvalid(testResourceName, namespace, name, "1.2.3"),
ExpectError: regexp.MustCompile(`Failed parsing JSON value`),
},
},
})
}

func testAccHelmReleaseConfigSetJSONInvalid(resource, ns, name, version string) string {
return fmt.Sprintf(`
resource "helm_release" "%s" {
name = %q
namespace = %q
description = "Test Invalid JSON Set"
repository = %q
chart = "test-chart"
version = %q
set = [
{
name = "nested"
value = "{ \"a\": true, \"b\": [1, 2 "
type = "json"
}
]
}
`, resource, name, ns, testRepositoryURL, version)
}

func TestAccResourceRelease_jsonSetMergeError(t *testing.T) {
name := randName("json-set-merge-error")
namespace := createRandomNamespace(t)
defer deleteNamespace(t, namespace)

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{
{
Config: testAccHelmReleaseConfigSetJSONMergeError(testResourceName, namespace, name, "1.2.3"),
ExpectError: regexp.MustCompile(`Cannot merge JSON into an non-object path`),
},
},
})
}

func testAccHelmReleaseConfigSetJSONMergeError(resource, ns, name, version string) string {
return fmt.Sprintf(`
resource "helm_release" "%s" {
name = %q
namespace = %q
description = "Test JSON Merge Error"
repository = %q
chart = "test-chart"
version = %q
set = [
// set "nested" as a primitive string in this example
{
name = "nested"
value = "\"not-an-object\""
type = "json"
},
// Then, attempting to set the "nested.key" which expects "nested" to be an object
{
name = "nested.key"
value = "{ \"x\": 42 }"
type = "json"
}
]
}
`, resource, name, ns, testRepositoryURL, version)
}

func setupOCIRegistry(t *testing.T, usepassword bool) (string, func()) {
dockerPath, err := exec.LookPath("docker")
if err != nil {
Expand Down
Loading