From 8c5863ac419fa4a438a88548b8934822d7a1a35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20W=C3=BCrbach?= Date: Thu, 13 Jun 2024 09:57:25 +0200 Subject: [PATCH 1/3] chore: mark secret fields as sensitive --- docs/resources/resource_definition.md | 4 +- .../provider/resource_definition_resource.go | 2 + .../resource_definition_resource_test.go | 102 ++++++++++++------ 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/docs/resources/resource_definition.md b/docs/resources/resource_definition.md index e7f0f66..560913e 100644 --- a/docs/resources/resource_definition.md +++ b/docs/resources/resource_definition.md @@ -132,8 +132,8 @@ resource "humanitec_resource_definition" "azure-blob" { Optional: -- `secret_refs` (String) JSON encoded secrets section of the data set. They can hold sensitive information that will be stored in the primary organization secret store and replaced with the secret store paths when sent outside, or secret references stored in a defined secret store. Can't be used together with secrets. -- `secrets_string` (String) JSON encoded secret data set. Passed around as-is. Can't be used together with secret_refs. +- `secret_refs` (String, Sensitive) JSON encoded secrets section of the data set. They can hold sensitive information that will be stored in the primary organization secret store and replaced with the secret store paths when sent outside, or secret references stored in a defined secret store. Can't be used together with secrets. +- `secrets_string` (String, Sensitive) JSON encoded secret data set. Passed around as-is. Can't be used together with secret_refs. - `values_string` (String) JSON encoded input data set. Passed around as-is. diff --git a/internal/provider/resource_definition_resource.go b/internal/provider/resource_definition_resource.go index 0e19716..678e616 100644 --- a/internal/provider/resource_definition_resource.go +++ b/internal/provider/resource_definition_resource.go @@ -129,11 +129,13 @@ func (r *ResourceDefinitionResource) Schema(ctx context.Context, req resource.Sc "secrets_string": schema.StringAttribute{ MarkdownDescription: "JSON encoded secret data set. Passed around as-is. Can't be used together with secret_refs.", Optional: true, + Sensitive: true, }, "secret_refs": schema.StringAttribute{ MarkdownDescription: "JSON encoded secrets section of the data set. They can hold sensitive information that will be stored in the primary organization secret store and replaced with the secret store paths when sent outside, or secret references stored in a defined secret store. Can't be used together with secrets.", Optional: true, Computed: true, + Sensitive: true, Validators: []validator.String{ stringvalidator.ConflictsWith(path.Expressions{ path.MatchRelative().AtParent().AtName("secrets_string"), diff --git a/internal/provider/resource_definition_resource_test.go b/internal/provider/resource_definition_resource_test.go index 32d5d23..920eb26 100644 --- a/internal/provider/resource_definition_resource_test.go +++ b/internal/provider/resource_definition_resource_test.go @@ -10,9 +10,26 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/assert" ) +type stringErrFn func() (string, error) + +func staticString(s string) stringErrFn { + return func() (string, error) { + return s, nil + } +} + +func jsonString(s any) stringErrFn { + return func() (string, error) { + b, err := json.Marshal(s) + return string(b), err + } +} + func TestAccResourceDefinition(t *testing.T) { + timestamp := time.Now().UnixNano() tests := []struct { name string @@ -21,8 +38,8 @@ func TestAccResourceDefinition(t *testing.T) { resourceAttrName string resourceAttrNameIDValue string resourceAttrNameUpdateKey string - resourceAttrNameUpdateValue1 string - resourceAttrNameUpdateValue2 string + resourceAttrNameUpdateValue1 stringErrFn + resourceAttrNameUpdateValue2 stringErrFn importStateVerifyIgnore []string }{ { @@ -32,12 +49,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("s3-test-%d", timestamp), resourceAttrNameUpdateKey: "driver_type", - resourceAttrNameUpdateValue1: "humanitec/s3", + resourceAttrNameUpdateValue1: staticString("humanitec/s3"), resourceAttrName: "humanitec_resource_definition.s3_test", configUpdate: func() string { return testAccResourceDefinitionS3ResourceWithDifferentDriver(fmt.Sprintf("s3-test-%d", timestamp), "us-east-1", "humanitec/terraform") }, - resourceAttrNameUpdateValue2: "humanitec/terraform", + resourceAttrNameUpdateValue2: staticString("humanitec/terraform"), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -47,12 +64,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("s3-test-%d", timestamp), resourceAttrNameUpdateKey: "driver_type", - resourceAttrNameUpdateValue1: "humanitec/s3", + resourceAttrNameUpdateValue1: staticString("humanitec/s3"), resourceAttrName: "humanitec_resource_definition.s3_test", configUpdate: func() string { return testAccResourceDefinitionS3Resource(fmt.Sprintf("s3-test-%d", timestamp), "us-east-1") }, - resourceAttrNameUpdateValue2: "humanitec/s3", + resourceAttrNameUpdateValue2: staticString("humanitec/s3"), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -62,12 +79,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("s3-test-%d", timestamp), resourceAttrNameUpdateKey: "driver_inputs.values_string", - resourceAttrNameUpdateValue1: "{\"region\":\"us-east-1\"}", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{"region": "us-east-1"}), resourceAttrName: "humanitec_resource_definition.s3_test", configUpdate: func() string { return testAccResourceDefinitionS3Resource(fmt.Sprintf("s3-test-%d", timestamp), "us-east-2") }, - resourceAttrNameUpdateValue2: "{\"region\":\"us-east-2\"}", + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{"region": "us-east-2"}), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -77,12 +94,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("postgres-test-%d", timestamp), resourceAttrNameUpdateKey: "driver_inputs.values_string", - resourceAttrNameUpdateValue1: "{\"host\":\"127.0.0.1\",\"instance\":\"test:test:test\",\"name\":\"test-1\",\"port\":5432}", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{"host": "127.0.0.1", "instance": "test:test:test", "name": "test-1", "port": 5432}), resourceAttrName: "humanitec_resource_definition.postgres_test", configUpdate: func() string { return testAccResourceDefinitionPostgresResource(fmt.Sprintf("postgres-test-%d", timestamp), "test-2") }, - resourceAttrNameUpdateValue2: "{\"host\":\"127.0.0.1\",\"instance\":\"test:test:test\",\"name\":\"test-2\",\"port\":5432}", + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{"host": "127.0.0.1", "instance": "test:test:test", "name": "test-2", "port": 5432}), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -92,12 +109,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("gke-test-%d", timestamp), resourceAttrNameUpdateKey: "driver_inputs.values_string", - resourceAttrNameUpdateValue1: "{\"loadbalancer\":\"1.1.1.1\",\"name\":\"test-1\",\"project_id\":\"test\",\"zone\":\"europe-west3\"}", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{"loadbalancer": "1.1.1.1", "name": "test-1", "project_id": "test", "zone": "europe-west3"}), resourceAttrName: "humanitec_resource_definition.gke_test", configUpdate: func() string { return testAccResourceDefinitionGKEResource(fmt.Sprintf("gke-test-%d", timestamp), "test-2") }, - resourceAttrNameUpdateValue2: "{\"loadbalancer\":\"1.1.1.1\",\"name\":\"test-2\",\"project_id\":\"test\",\"zone\":\"europe-west3\"}", + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{"loadbalancer": "1.1.1.1", "name": "test-2", "project_id": "test", "zone": "europe-west3"}), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -107,12 +124,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("dns-test-%d", timestamp), resourceAttrNameUpdateKey: "driver_inputs.values_string", - resourceAttrNameUpdateValue1: "{\"host\":\"test-1\"}", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{"host": "test-1"}), resourceAttrName: "humanitec_resource_definition.dns_test", configUpdate: func() string { return testAccResourceDefinitionDNSStaticResource(fmt.Sprintf("dns-test-%d", timestamp), "test-2") }, - resourceAttrNameUpdateValue2: "{\"host\":\"test-2\"}", + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{"host": "test-2"}), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -122,12 +139,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("ingress-test-%d", timestamp), resourceAttrNameUpdateKey: "driver_inputs.values_string", - resourceAttrNameUpdateValue1: "{\"labels\":{\"name\":\"test-1\"},\"no_tls\":true}", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{"labels": map[string]interface{}{"name": "test-1"}, "no_tls": true}), resourceAttrName: "humanitec_resource_definition.ingress_test", configUpdate: func() string { return testAccResourceDefinitionIngressResource(fmt.Sprintf("ingress-test-%d", timestamp), "test-2") }, - resourceAttrNameUpdateValue2: "{\"labels\":{\"name\":\"test-2\"},\"no_tls\":true}", + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{"labels": map[string]interface{}{"name": "test-2"}, "no_tls": true}), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -137,12 +154,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("provision-test-%d", timestamp), resourceAttrNameUpdateKey: "provision.awspolicy.match_dependents", - resourceAttrNameUpdateValue1: "true", + resourceAttrNameUpdateValue1: staticString("true"), resourceAttrName: "humanitec_resource_definition.provision_test", configUpdate: func() string { return testAccResourceDefinitionProvisionResource(fmt.Sprintf("provision-test-%d", timestamp), "false") }, - resourceAttrNameUpdateValue2: "false", + resourceAttrNameUpdateValue2: staticString("false"), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -152,12 +169,12 @@ func TestAccResourceDefinition(t *testing.T) { }, resourceAttrNameIDValue: fmt.Sprintf("k8s-logging-test-%d", timestamp), resourceAttrNameUpdateKey: "name", - resourceAttrNameUpdateValue1: "test-1", + resourceAttrNameUpdateValue1: staticString("test-1"), resourceAttrName: "humanitec_resource_definition.k8s_logging_test", configUpdate: func() string { return testAccResourceDefinitionK8sLoggingResource(fmt.Sprintf("k8s-logging-test-%d", timestamp), "test-2") }, - resourceAttrNameUpdateValue2: "test-2", + resourceAttrNameUpdateValue2: staticString("test-2"), importStateVerifyIgnore: []string{"driver_inputs.secrets_string", "force_delete"}, }, { @@ -165,35 +182,52 @@ func TestAccResourceDefinition(t *testing.T) { configCreate: func() string { return testAccResourceDefinitionS3taticResourceWithSecretRefs(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyIdPath1", "secretAccessKeyPath1") }, - resourceAttrNameIDValue: fmt.Sprintf("s3-test-with-secrets-%d", timestamp), - resourceAttrNameUpdateKey: "driver_inputs.secret_refs", - resourceAttrNameUpdateValue1: "{\"aws_access_key_id\":{\"ref\":\"accessKeyIdPath1\",\"store\":\"external-secret-store\",\"version\":\"1\"},\"aws_secret_access_key\":{\"ref\":\"secretAccessKeyPath1\",\"store\":\"external-secret-store\",\"version\":\"1\"}}", - resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets", + resourceAttrNameIDValue: fmt.Sprintf("s3-test-with-secrets-%d", timestamp), + resourceAttrNameUpdateKey: "driver_inputs.secret_refs", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"ref": "accessKeyIdPath1", "store": "external-secret-store", "version": "1"}, + "aws_secret_access_key": map[string]interface{}{"ref": "secretAccessKeyPath1", "store": "external-secret-store", "version": "1"}, + }), + resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets", configUpdate: func() string { return testAccResourceDefinitionS3taticResourceWithSecretRefs(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyIdPath2", "secretAccessKeyPath2") }, - resourceAttrNameUpdateValue2: "{\"aws_access_key_id\":{\"ref\":\"accessKeyIdPath2\",\"store\":\"external-secret-store\",\"version\":\"1\"},\"aws_secret_access_key\":{\"ref\":\"secretAccessKeyPath2\",\"store\":\"external-secret-store\",\"version\":\"1\"}}", - importStateVerifyIgnore: []string{"force_delete"}, + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"ref": "accessKeyIdPath2", "store": "external-secret-store", "version": "1"}, + "aws_secret_access_key": map[string]interface{}{"ref": "secretAccessKeyPath2", "store": "external-secret-store", "version": "1"}, + }), + importStateVerifyIgnore: []string{"force_delete"}, }, { name: "S3 static - secret ref set values", configCreate: func() string { return testAccResourceDefinitionS3taticResourceWithSecretRefValues(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyId1", "secretAccessKey1") }, - resourceAttrNameIDValue: fmt.Sprintf("s3-test-with-secrets-%d", timestamp), - resourceAttrNameUpdateKey: "driver_inputs.secret_refs", - resourceAttrNameUpdateValue1: "{\"aws_access_key_id\":{\"value\":\"accessKeyId1\"},\"aws_secret_access_key\":{\"value\":\"secretAccessKey1\"}}", - resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets", + resourceAttrNameIDValue: fmt.Sprintf("s3-test-with-secrets-%d", timestamp), + resourceAttrNameUpdateKey: "driver_inputs.secret_refs", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"value": "accessKeyId1"}, + "aws_secret_access_key": map[string]interface{}{"value": "secretAccessKey1"}, + }), + resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets", configUpdate: func() string { return testAccResourceDefinitionS3taticResourceWithSecretRefValues(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyId2", "secretAccessKey2") }, - resourceAttrNameUpdateValue2: "{\"aws_access_key_id\":{\"value\":\"accessKeyId2\"},\"aws_secret_access_key\":{\"value\":\"secretAccessKey2\"}}", - importStateVerifyIgnore: []string{"driver_inputs.secret_refs", "force_delete"}, + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"value": "accessKeyId2"}, + "aws_secret_access_key": map[string]interface{}{"value": "secretAccessKey2"}, + }), + importStateVerifyIgnore: []string{"driver_inputs.secret_refs", "force_delete"}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + updateValue1, err := tc.resourceAttrNameUpdateValue1() + assert.NoError(t, err) + updateValue2, err := tc.resourceAttrNameUpdateValue2() + assert.NoError(t, err) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -203,7 +237,7 @@ func TestAccResourceDefinition(t *testing.T) { Config: tc.configCreate(), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(tc.resourceAttrName, "id", tc.resourceAttrNameIDValue), - resource.TestCheckResourceAttr(tc.resourceAttrName, tc.resourceAttrNameUpdateKey, tc.resourceAttrNameUpdateValue1), + resource.TestCheckResourceAttr(tc.resourceAttrName, tc.resourceAttrNameUpdateKey, updateValue1), ), }, // ImportState testing @@ -217,7 +251,7 @@ func TestAccResourceDefinition(t *testing.T) { { Config: tc.configUpdate(), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(tc.resourceAttrName, tc.resourceAttrNameUpdateKey, tc.resourceAttrNameUpdateValue2), + resource.TestCheckResourceAttr(tc.resourceAttrName, tc.resourceAttrNameUpdateKey, updateValue2), ), }, // Delete testing automatically occurs in TestCase From f0b09f50615a9ffcb48d70ae9029b7a1aaf38ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20W=C3=BCrbach?= Date: Thu, 13 Jun 2024 10:08:27 +0200 Subject: [PATCH 2/3] fix: resdefs with secret ref null value --- .../provider/resource_definition_resource.go | 114 +++++++- .../resource_definition_resource_test.go | 274 +++++++++++++++++- internal/provider/utils.go | 19 ++ internal/provider/utils_test.go | 53 ++++ 4 files changed, 450 insertions(+), 10 deletions(-) diff --git a/internal/provider/resource_definition_resource.go b/internal/provider/resource_definition_resource.go index 678e616..41cffab 100644 --- a/internal/provider/resource_definition_resource.go +++ b/internal/provider/resource_definition_resource.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "strings" "time" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" @@ -256,18 +255,117 @@ func parseResourceDefinitionResponse(ctx context.Context, driverInputSchema map[ } if data.DriverInputs != nil { - if driverInputs.SecretRefs == nil { - data.DriverInputs.SecretRefs = types.StringNull() + diags.Append(parseResourceDefinitionSecretRefResponse(driverInputs.SecretRefs, data)...) + } + return diags +} + +func parseResourceDefinitionSecretRefResponse(secretRefs *map[string]interface{}, data *DefinitionResourceModel) diag.Diagnostics { + var diags diag.Diagnostics + + if secretRefs == nil { + data.DriverInputs.SecretRefs = types.StringNull() + return diags + } + + existingStateSecretRefs := map[string]interface{}{} + + // unmarshal existing secret_refs + existingRefs := data.DriverInputs.SecretRefs.ValueString() + if existingRefs != "" { + if err := json.Unmarshal([]byte(existingRefs), &existingStateSecretRefs); err != nil { + diags.AddError(HUM_API_ERR, fmt.Sprintf("Failed to unmarshal existing secret_refs: %s, \"%s\"", err.Error(), existingRefs)) + return diags + } + } + + apiSecretRefs := *secretRefs + diags.Append(mergeResourceDefinitionSecretRefResponse(existingStateSecretRefs, apiSecretRefs)...) + if diags.HasError() { + return diags + } + + b, err := json.Marshal(apiSecretRefs) + if err != nil { + diags.AddError(HUM_API_ERR, fmt.Sprintf("Failed to marshal secret_refs: %s", err.Error())) + } + data.DriverInputs.SecretRefs = types.StringValue(string(b)) + + return diags +} + +type ResourceDefinitionSecretReference struct { + Store string `json:"store"` + Ref string `json:"ref"` + Version string `json:"version"` + Value string `json:"value"` +} + +func isResourceDefinitionSecretReference(data any) bool { + secretRefMapJson, err := json.Marshal(data) + if err != nil { + return false + } + + if err := strictUnmarshal(secretRefMapJson, &ResourceDefinitionSecretReference{}); err != nil { + return false + } + return true +} + +// mergeResourceDefinitionSecretRefResponse merges the existing state secret_refs with the new secret_refs. +func mergeResourceDefinitionSecretRefResponse(existingStateSecretRefs, apiSecretRefs map[string]interface{}) diag.Diagnostics { + return updateResourceDefinitionSecretRefResponse([]string{}, apiSecretRefs, existingStateSecretRefs) +} + +func updateResourceDefinitionSecretRefResponse(path []string, apiSecretRefI any, existingSecretRefI any) diag.Diagnostics { + var diags diag.Diagnostics + + switch typed := apiSecretRefI.(type) { + case map[string]interface{}: + if isResourceDefinitionSecretReference(typed) { + // value is never returned from the API, so take the value from the existing state + if existingRef, ok := existingSecretRefI.(map[string]interface{}); ok { + if val, ok := existingRef["value"]; ok { + if val == nil { + typed["value"] = val + } else { + overrideMap(typed, existingRef) + } + } + } } else { - if !strings.Contains(data.DriverInputs.SecretRefs.ValueString(), `{"value":"`) { - b, err := json.Marshal(driverInputs.SecretRefs) - if err != nil { - diags.AddError(HUM_API_ERR, fmt.Sprintf("Failed to marshal secret_refs: %s", err.Error())) + for k, v := range typed { + newPath := append(path, k) + var newExisting interface{} + if existingRef, ok := existingSecretRefI.(map[string]interface{}); ok { + newExisting = existingRef[k] } - data.DriverInputs.SecretRefs = types.StringValue(string(b)) + updateResourceDefinitionSecretRefResponse(newPath, v, newExisting) } } + case []map[string]interface{}: + for idx, v := range typed { + newPath := append(path, fmt.Sprintf("[%d]", idx)) + var newExisting interface{} + if existingRef, ok := existingSecretRefI.([]map[string]interface{}); ok { + newExisting = existingRef[idx] + } + updateResourceDefinitionSecretRefResponse(newPath, v, newExisting) + } + case []interface{}: + for idx, v := range typed { + newPath := append(path, fmt.Sprintf("[%d]", idx)) + var newExisting interface{} + if existingRef, ok := existingSecretRefI.([]interface{}); ok { + newExisting = existingRef[idx] + } + updateResourceDefinitionSecretRefResponse(newPath, v, newExisting) + } + default: + diags.AddError(HUM_API_ERR, fmt.Sprintf("Unknown secret_ref type in %s: %T", path, typed)) } + return diags } diff --git a/internal/provider/resource_definition_resource_test.go b/internal/provider/resource_definition_resource_test.go index 920eb26..4d91571 100644 --- a/internal/provider/resource_definition_resource_test.go +++ b/internal/provider/resource_definition_resource_test.go @@ -185,7 +185,9 @@ func TestAccResourceDefinition(t *testing.T) { resourceAttrNameIDValue: fmt.Sprintf("s3-test-with-secrets-%d", timestamp), resourceAttrNameUpdateKey: "driver_inputs.secret_refs", resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{ - "aws_access_key_id": map[string]interface{}{"ref": "accessKeyIdPath1", "store": "external-secret-store", "version": "1"}, + "nested": map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"ref": "accessKeyIdPath1", "store": "external-secret-store", "version": "1"}, + }, "aws_secret_access_key": map[string]interface{}{"ref": "secretAccessKeyPath1", "store": "external-secret-store", "version": "1"}, }), resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets", @@ -193,11 +195,34 @@ func TestAccResourceDefinition(t *testing.T) { return testAccResourceDefinitionS3taticResourceWithSecretRefs(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyIdPath2", "secretAccessKeyPath2") }, resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{ - "aws_access_key_id": map[string]interface{}{"ref": "accessKeyIdPath2", "store": "external-secret-store", "version": "1"}, + "nested": map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"ref": "accessKeyIdPath2", "store": "external-secret-store", "version": "1"}, + }, "aws_secret_access_key": map[string]interface{}{"ref": "secretAccessKeyPath2", "store": "external-secret-store", "version": "1"}, }), importStateVerifyIgnore: []string{"force_delete"}, }, + { + name: "S3 static - secret refs with null value", // "null" is injected when using a type like object({ .. value = optional(string) }) in the schema + configCreate: func() string { + return testAccResourceDefinitionS3taticResourceWithSecretRefsWithNull(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyIdPath1", "secretAccessKeyPath1") + }, + resourceAttrNameIDValue: fmt.Sprintf("s3-test-with-secrets-%d", timestamp), + resourceAttrNameUpdateKey: "driver_inputs.secret_refs", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"ref": "accessKeyIdPath1", "store": "external-secret-store", "version": "1", "value": nil}, + "aws_secret_access_key": map[string]interface{}{"ref": "secretAccessKeyPath1", "store": "external-secret-store", "version": "1", "value": nil}, + }), + resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets", + configUpdate: func() string { + return testAccResourceDefinitionS3taticResourceWithSecretRefsWithNull(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyIdPath2", "secretAccessKeyPath2") + }, + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"ref": "accessKeyIdPath2", "store": "external-secret-store", "version": "1", "value": nil}, + "aws_secret_access_key": map[string]interface{}{"ref": "secretAccessKeyPath2", "store": "external-secret-store", "version": "1", "value": nil}, + }), + importStateVerifyIgnore: []string{"force_delete", "driver_inputs.secret_refs"}, // refs are ignored as "value: null" is not returned from the API + }, { name: "S3 static - secret ref set values", configCreate: func() string { @@ -219,6 +244,27 @@ func TestAccResourceDefinition(t *testing.T) { }), importStateVerifyIgnore: []string{"driver_inputs.secret_refs", "force_delete"}, }, + { + name: "S3 static - secret ref nested", + configCreate: func() string { + return testAccResourceDefinitionS3taticResourceWithSecretRefValues(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyId1", "secretAccessKey1") + }, + resourceAttrNameIDValue: fmt.Sprintf("s3-test-with-secrets-%d", timestamp), + resourceAttrNameUpdateKey: "driver_inputs.secret_refs", + resourceAttrNameUpdateValue1: jsonString(map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"value": "accessKeyId1"}, + "aws_secret_access_key": map[string]interface{}{"value": "secretAccessKey1"}, + }), + resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets", + configUpdate: func() string { + return testAccResourceDefinitionS3taticResourceWithSecretRefValues(fmt.Sprintf("s3-test-with-secrets-%d", timestamp), "accessKeyId2", "secretAccessKey2") + }, + resourceAttrNameUpdateValue2: jsonString(map[string]interface{}{ + "aws_access_key_id": map[string]interface{}{"value": "accessKeyId2"}, + "aws_secret_access_key": map[string]interface{}{"value": "secretAccessKey2"}, + }), + importStateVerifyIgnore: []string{"driver_inputs.secret_refs", "force_delete"}, + }, } for _, tc := range tests { @@ -487,6 +533,38 @@ resource "humanitec_resource_definition" "s3_test_with_secrets" { type = "s3" driver_type = "humanitec/static" + driver_inputs = { + values_string = jsonencode({ + "bucket" = "test-bucket" + "region" = "us-east-1" + }) + secret_refs = jsonencode({ + "nested" : { + "aws_access_key_id" = { + "ref" = "%s" + "store" = "external-secret-store" + "version" = "1" + } + } + "aws_secret_access_key" = { + "ref" = "%s" + "store" = "external-secret-store" + "version" = "1" + } + }) + } +} +`, id, awsAccessKeyIDPath, awsSecretAccessKeyPath) +} + +func testAccResourceDefinitionS3taticResourceWithSecretRefsWithNull(id, awsAccessKeyIDPath, awsSecretAccessKeyPath string) string { + return fmt.Sprintf(` +resource "humanitec_resource_definition" "s3_test_with_secrets" { + id = "%s" + name = "s3-test-with-secrets" + type = "s3" + driver_type = "humanitec/static" + driver_inputs = { values_string = jsonencode({ "bucket" = "test-bucket" @@ -497,11 +575,13 @@ resource "humanitec_resource_definition" "s3_test_with_secrets" { "ref" = "%s" "store" = "external-secret-store" "version" = "1" + "value" = null } "aws_secret_access_key" = { "ref" = "%s" "store" = "external-secret-store" "version" = "1" + "value" = null } }) } @@ -565,3 +645,193 @@ func getDefinitionSecretPath(defID string) string { func getDefinitionSecretRef(id string, version int) string { return fmt.Sprintf("{\"aws_access_key_id\":{\"ref\":\"%s/aws_access_key_id/.value\",\"store\":\"humanitec\",\"version\":\"%d\"},\"aws_secret_access_key\":{\"ref\":\"%s/aws_secret_access_key/.value\",\"store\":\"humanitec\",\"version\":\"%d\"}}", getDefinitionSecretPath(id), version, getDefinitionSecretPath(id), version) } + +func TestMergeResourceDefinitionSecretRefResponse(t *testing.T) { + testCases := []struct { + name string + existing map[string]interface{} + new map[string]interface{} + expected map[string]interface{} + }{ + { + name: "untouched simple references", + existing: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + new: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + expected: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + }, + { + name: "retains null values", + existing: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + "value": nil, + }, + }, + new: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + expected: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + "value": nil, + }, + }, + }, + { + name: "omits other attributes when only value is defined", + existing: map[string]interface{}{ + "key": map[string]interface{}{ + "value": "a", + }, + }, + new: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + expected: map[string]interface{}{ + "key": map[string]interface{}{ + "value": "a", + }, + }, + }, + { + name: "omits other attributes when only value is defined (nested)", + existing: map[string]interface{}{ + "nested": map[string]interface{}{ + "key": map[string]interface{}{ + "value": "a", + }, + }, + }, + new: map[string]interface{}{ + "nested": map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + }, + expected: map[string]interface{}{ + "nested": map[string]interface{}{ + "key": map[string]interface{}{ + "value": "a", + }, + }, + }, + }, + { + name: "omits other attributes when only value is defined (nested list)", + existing: map[string]interface{}{ + "nested": []interface{}{ + map[string]interface{}{ + "key": map[string]interface{}{ + "value": "a", + }, + }, + }, + }, + new: map[string]interface{}{ + "nested": []interface{}{ + map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + }, + }, + expected: map[string]interface{}{ + "nested": []interface{}{ + map[string]interface{}{ + "key": map[string]interface{}{ + "value": "a", + }, + }, + }, + }, + }, + { + name: "keeps null attributes when only value is defined", + existing: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": nil, + "store": nil, + "version": nil, + "value": "a", + }, + }, + new: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + expected: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": nil, + "store": nil, + "version": nil, + "value": "a", + }, + }, + }, + { + name: "supports importing references", + existing: map[string]interface{}{}, + new: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + expected: map[string]interface{}{ + "key": map[string]interface{}{ + "ref": "path1", + "store": "store1", + "version": "1", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + diags := mergeResourceDefinitionSecretRefResponse(tc.existing, tc.new) + assert.Empty(t, diags) + assert.Equal(t, tc.expected, tc.new) + }) + } +} diff --git a/internal/provider/utils.go b/internal/provider/utils.go index 25e3e6b..a7424a4 100644 --- a/internal/provider/utils.go +++ b/internal/provider/utils.go @@ -1,7 +1,10 @@ package provider import ( + "bytes" + "encoding/json" "errors" + "maps" "os" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -112,3 +115,19 @@ func readConfig(data HumanitecProviderModel) (config Config, diags diag.Diagnost func toPtr[T any](value T) *T { return &value } + +// strictUnmarshal unmarshals the JSON data into the provided value and returns an error if the data contains unknown fields. +func strictUnmarshal(data []byte, v interface{}) error { + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() + return dec.Decode(v) +} + +// overrideMap overrides the base map with the values from the override map. +func overrideMap[M ~map[K]V, K comparable, V any](base M, override M) { + maps.DeleteFunc(base, func(K K, V V) bool { + _, ok := override[K] + return !ok + }) + maps.Copy(base, override) +} diff --git a/internal/provider/utils_test.go b/internal/provider/utils_test.go index 3225872..e0abc23 100644 --- a/internal/provider/utils_test.go +++ b/internal/provider/utils_test.go @@ -73,3 +73,56 @@ func TestReadConfigNonExistentFile(t *testing.T) { assert.Len(diags, 1) assert.Equal("Unable to read config file", diags[0].Summary()) } + +func TestStrictUnmarshal(t *testing.T) { + testCases := []struct { + name string + input string + expectErr bool + }{ + { + name: "valid", + input: `{"field": "value"}`, + expectErr: false, + }, + { + name: "additional fields", + input: `{"field": "value", "a": "b"}`, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + f := struct { + Field string `json:"field"` + }{} + + err := strictUnmarshal([]byte(tc.input), &f) + if tc.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestOverrideMap(t *testing.T) { + assert := assert.New(t) + + original := map[string]interface{}{ + "key": "value", + "another key": "another value", + } + overrides := map[string]interface{}{ + "key": "new value 1", + "new key": "new value 2", + } + + overrideMap(original, overrides) + assert.Equal(map[string]interface{}{ + "key": "new value 1", + "new key": "new value 2", + }, original) +} From 0cbec105f528305d2749204c8eb71c2242df3989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20W=C3=BCrbach?= Date: Fri, 14 Jun 2024 09:23:36 +0200 Subject: [PATCH 3/3] chore: remove unused code and resolve linter warnings --- internal/provider/humanitec_data.go | 135 ------------------ .../provider/resource_application_user.go | 1 - .../resource_definition_criteria_resource.go | 2 +- .../provider/resource_definition_resource.go | 37 ++--- .../resource_environment_type_user.go | 1 - .../provider/resource_pipeline_criteria.go | 2 - 6 files changed, 9 insertions(+), 169 deletions(-) diff --git a/internal/provider/humanitec_data.go b/internal/provider/humanitec_data.go index bcce2c9..c665319 100644 --- a/internal/provider/humanitec_data.go +++ b/internal/provider/humanitec_data.go @@ -1,145 +1,10 @@ package provider import ( - "context" - "fmt" - "sync" - - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/humanitec/humanitec-go-autogen" - "github.com/humanitec/humanitec-go-autogen/client" ) type HumanitecData struct { Client *humanitec.Client OrgID string - - fetchDriversMu sync.Mutex - driversByType map[string]*client.DriverDefinitionResponse - - fetchTypesMu sync.Mutex - typesByType map[string]*client.ResourceTypeResponse -} - -func (h *HumanitecData) fetchResourceDrivers(ctx context.Context) (map[string]*client.DriverDefinitionResponse, diag.Diagnostics) { - var diags diag.Diagnostics - - h.fetchDriversMu.Lock() - defer h.fetchDriversMu.Unlock() - - if h.driversByType != nil { - return h.driversByType, diags - } - - httpResp, err := h.Client.ListResourceDriversWithResponse(ctx, h.OrgID) - if err != nil { - diags.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to get resource drivers, got error: %s", err)) - return nil, diags - } - - if httpResp.StatusCode() != 200 { - diags.AddError(HUM_API_ERR, fmt.Sprintf("Unable to get resource drivers, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) - return nil, diags - } - - if httpResp.JSON200 == nil { - diags.AddError(HUM_API_ERR, fmt.Sprintf("Unable to get resource drivers, missing body, body: %s", httpResp.Body)) - return nil, diags - } - - driversByType := map[string]*client.DriverDefinitionResponse{} - for _, d := range *httpResp.JSON200 { - d := d - driversByType[fmt.Sprintf("%s/%s", d.OrgId, d.Id)] = &d - } - - h.driversByType = driversByType - - return driversByType, diags -} - -func (h *HumanitecData) fetchResourceTypes(ctx context.Context) (map[string]*client.ResourceTypeResponse, diag.Diagnostics) { - var diags diag.Diagnostics - - h.fetchTypesMu.Lock() - defer h.fetchTypesMu.Unlock() - - if h.typesByType != nil { - return h.typesByType, diags - } - - httpResp, err := h.Client.ListResourceTypesWithResponse(ctx, h.OrgID) - if err != nil { - diags.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to get resource types, got error: %s", err)) - return nil, diags - } - - if httpResp.StatusCode() != 200 { - diags.AddError(HUM_API_ERR, fmt.Sprintf("Unable to get resource types, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) - return nil, diags - } - - if httpResp.JSON200 == nil { - diags.AddError(HUM_API_ERR, fmt.Sprintf("Unable to get resource types, missing body, body: %s", httpResp.Body)) - return nil, diags - } - - typesByType := map[string]*client.ResourceTypeResponse{} - for _, d := range *httpResp.JSON200 { - d := d - typesByType[d.Type] = &d - } - - h.typesByType = typesByType - - return typesByType, diags -} - -func (h *HumanitecData) driverByDriverType(ctx context.Context, driverType string) (*client.DriverDefinitionResponse, diag.Diagnostics) { - driversByType, diags := h.fetchResourceDrivers(ctx) - if diags.HasError() { - return nil, diags - } - - driver, ok := driversByType[driverType] - if !ok { - diags.AddError(HUM_INPUT_ERR, fmt.Sprintf("No resource driver found for type: %s", driverType)) - return nil, diags - } - - return driver, diags -} - -func (h *HumanitecData) resourceByType(ctx context.Context, resourceType string) (*client.ResourceTypeResponse, diag.Diagnostics) { - resourcesByType, diags := h.fetchResourceTypes(ctx) - if diags.HasError() { - return nil, diags - } - - resource, ok := resourcesByType[resourceType] - if !ok { - diags.AddError(HUM_INPUT_ERR, fmt.Sprintf("No resource type found for type: %s", resourceType)) - return nil, diags - } - - return resource, diags -} - -func (h *HumanitecData) DriverInputSchemaByDriverTypeOrType(ctx context.Context, driverType, resourceType string) (map[string]interface{}, diag.Diagnostics) { - // The static driver has no input schema and matches the output schema of the resource type - if driverType == "humanitec/static" { - resource, diags := h.resourceByType(ctx, resourceType) - if diags.HasError() { - return nil, diags - } - - return resource.OutputsSchema, diags - } - - driver, diags := h.driverByDriverType(ctx, driverType) - if diags.HasError() { - return nil, diags - } - - return driver.InputsSchema, diags } diff --git a/internal/provider/resource_application_user.go b/internal/provider/resource_application_user.go index 463d0b2..c9ac874 100644 --- a/internal/provider/resource_application_user.go +++ b/internal/provider/resource_application_user.go @@ -25,7 +25,6 @@ var _ resource.ResourceWithImportState = &ResourceApplicationUser{} var ( defaultApplicationUserCreateTimeout = 30 * time.Second - defaultApplicationUserReadTimeout = 30 * time.Second ) func NewResourceApplicationUser() resource.Resource { diff --git a/internal/provider/resource_definition_criteria_resource.go b/internal/provider/resource_definition_criteria_resource.go index 432682d..1178596 100644 --- a/internal/provider/resource_definition_criteria_resource.go +++ b/internal/provider/resource_definition_criteria_resource.go @@ -204,7 +204,7 @@ func (r *ResourceDefinitionCriteriaResource) Read(ctx context.Context, req resou return } - httpResp, err := r.client().GetResourceDefinitionWithResponse(ctx, r.orgId(), data.ResourceDefinitionID.ValueString(), &client.GetResourceDefinitionParams{toPtr(false)}) + httpResp, err := r.client().GetResourceDefinitionWithResponse(ctx, r.orgId(), data.ResourceDefinitionID.ValueString(), &client.GetResourceDefinitionParams{Deleted: toPtr(false)}) if err != nil { resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to read resource definition, got error: %s", err)) return diff --git a/internal/provider/resource_definition_resource.go b/internal/provider/resource_definition_resource.go index 41cffab..2b2f550 100644 --- a/internal/provider/resource_definition_resource.go +++ b/internal/provider/resource_definition_resource.go @@ -227,7 +227,7 @@ func defaultFalseBoolValuePointer(b *bool) types.Bool { return types.BoolValue(*b) } -func parseResourceDefinitionResponse(ctx context.Context, driverInputSchema map[string]interface{}, res *client.ResourceDefinitionResponse, data *DefinitionResourceModel) diag.Diagnostics { +func parseResourceDefinitionResponse(res *client.ResourceDefinitionResponse, data *DefinitionResourceModel) diag.Diagnostics { var diags diag.Diagnostics data.ID = types.StringValue(res.Id) @@ -386,7 +386,7 @@ func provisionFromModel(data *map[string]DefinitionResourceProvisionModel) *map[ return &provision } -func driverInputsFromModel(ctx context.Context, inputSchema map[string]interface{}, data *DefinitionResourceModel) (*client.ValuesSecretsRefsRequest, diag.Diagnostics) { +func driverInputsFromModel(data *DefinitionResourceModel) (*client.ValuesSecretsRefsRequest, diag.Diagnostics) { if data.DriverInputs == nil { return nil, nil } @@ -443,14 +443,7 @@ func (r *ResourceDefinitionResource) Create(ctx context.Context, req resource.Cr } provision := provisionFromModel(data.Provision) - driverType := data.DriverType.ValueString() - driverInputSchema, diag := r.data.DriverInputSchemaByDriverTypeOrType(ctx, driverType, data.Type.ValueString()) - resp.Diagnostics.Append(diag...) - if resp.Diagnostics.HasError() { - return - } - - driverInputs, diag := driverInputsFromModel(ctx, driverInputSchema, data) + driverInputs, diag := driverInputsFromModel(data) resp.Diagnostics.Append(diag...) if resp.Diagnostics.HasError() { return @@ -475,7 +468,7 @@ func (r *ResourceDefinitionResource) Create(ctx context.Context, req resource.Cr return } - resp.Diagnostics.Append(parseResourceDefinitionResponse(ctx, driverInputSchema, httpResp.JSON200, data)...) + resp.Diagnostics.Append(parseResourceDefinitionResponse(httpResp.JSON200, data)...) if resp.Diagnostics.HasError() { return } @@ -511,13 +504,7 @@ func (r *ResourceDefinitionResource) Read(ctx context.Context, req resource.Read return } - driverInputSchema, diag := r.data.DriverInputSchemaByDriverTypeOrType(ctx, httpResp.JSON200.DriverType, httpResp.JSON200.Type) - resp.Diagnostics.Append(diag...) - if resp.Diagnostics.HasError() { - return - } - - resp.Diagnostics.Append(parseResourceDefinitionResponse(ctx, driverInputSchema, httpResp.JSON200, data)...) + resp.Diagnostics.Append(parseResourceDefinitionResponse(httpResp.JSON200, data)...) if resp.Diagnostics.HasError() { return } @@ -537,15 +524,7 @@ func (r *ResourceDefinitionResource) Update(ctx context.Context, req resource.Up return } - name := data.Name.ValueString() - driverType := data.DriverType.ValueString() - driverInputSchema, diag := r.data.DriverInputSchemaByDriverTypeOrType(ctx, driverType, data.Type.ValueString()) - resp.Diagnostics.Append(diag...) - if resp.Diagnostics.HasError() { - return - } - - driverInputs, diag := driverInputsFromModel(ctx, driverInputSchema, data) + driverInputs, diag := driverInputsFromModel(data) resp.Diagnostics.Append(diag...) if resp.Diagnostics.HasError() { return @@ -559,7 +538,7 @@ func (r *ResourceDefinitionResource) Update(ctx context.Context, req resource.Up DriverType: data.DriverType.ValueStringPointer(), DriverAccount: data.DriverAccount.ValueStringPointer(), DriverInputs: driverInputs, - Name: name, + Name: data.Name.ValueString(), Provision: provision, }) if err != nil { @@ -572,7 +551,7 @@ func (r *ResourceDefinitionResource) Update(ctx context.Context, req resource.Up return } - resp.Diagnostics.Append(parseResourceDefinitionResponse(ctx, driverInputSchema, httpResp.JSON200, data)...) + resp.Diagnostics.Append(parseResourceDefinitionResponse(httpResp.JSON200, data)...) if resp.Diagnostics.HasError() { return } diff --git a/internal/provider/resource_environment_type_user.go b/internal/provider/resource_environment_type_user.go index 6ecdbbd..68986fd 100644 --- a/internal/provider/resource_environment_type_user.go +++ b/internal/provider/resource_environment_type_user.go @@ -25,7 +25,6 @@ var _ resource.ResourceWithImportState = &ResourceEnvironmentTypeUser{} var ( defaultEnvironmentTypeUserCreateTimeout = 30 * time.Second - defaultEnvironmentTypeUserReadTimeout = 30 * time.Second ) func NewResourceEnvironmentTypeUser() resource.Resource { diff --git a/internal/provider/resource_pipeline_criteria.go b/internal/provider/resource_pipeline_criteria.go index 6cb9ee2..71c3b7e 100644 --- a/internal/provider/resource_pipeline_criteria.go +++ b/internal/provider/resource_pipeline_criteria.go @@ -236,7 +236,6 @@ func (r *ResourcePipelineCriteria) Read(ctx context.Context, req resource.ReadRe func (r *ResourcePipelineCriteria) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // you can't update criteria in place, all updates should be done with a replacement resp.Diagnostics.AddError(HUM_CLIENT_ERR, "Unable to update pipeline criteria") - return } func (r *ResourcePipelineCriteria) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { @@ -275,5 +274,4 @@ func (r *ResourcePipelineCriteria) ImportState(ctx context.Context, req resource resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("app_id"), appId)...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("pipeline_id"), pipelineId)...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), criteriaId)...) - return }