Skip to content

Commit

Permalink
use framework type instead of custom one
Browse files Browse the repository at this point in the history
  • Loading branch information
delca85 committed Oct 11, 2023
1 parent b760d08 commit 5af4827
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 21 deletions.
69 changes: 51 additions & 18 deletions internal/provider/resource_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import (
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"

"github.com/humanitec/humanitec-go-autogen"
"github.com/humanitec/humanitec-go-autogen/client"
Expand Down Expand Up @@ -41,7 +44,7 @@ type ValueModel struct {
Description types.String `tfsdk:"description"`
IsSecret types.Bool `tfsdk:"is_secret"`
Value types.String `tfsdk:"value"`
SecretRef *SecretRef `tfsdk:"secret_ref"`
SecretRef types.Object `tfsdk:"secret_ref"`
}

// SecretRef describes a secret reference that might contain a secret value or a reference to an already stored secret.
Expand All @@ -52,6 +55,15 @@ type SecretRef struct {
Value types.String `tfsdk:"value"`
}

func SecretRefAttributeTypes() map[string]attr.Type {
return map[string]attr.Type{
"ref": types.StringType,
"store": types.StringType,
"version": types.StringType,
"value": types.StringType,
}
}

func (r *ResourceValue) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_value"
}
Expand Down Expand Up @@ -107,18 +119,22 @@ func (r *ResourceValue) Schema(ctx context.Context, req resource.SchemaRequest,
"secret_ref": schema.SingleNestedAttribute{
MarkdownDescription: "The sensitive value that will be stored in the primary organization store or a reference to a sensitive value already stored in one of the registered stores. It can't be defined if is_secret is false or value is defined.",
Optional: true,
Computed: true,
Attributes: map[string]schema.Attribute{
"ref": schema.StringAttribute{
MarkdownDescription: "Secret reference in the format of the target store. It can't be defined if value is defined.",
Optional: true,
Computed: true,
},
"store": schema.StringAttribute{
MarkdownDescription: "Secret Store id. This can't be humanitec (our internal Secret Store). It's mandatory if ref is defined and can't be used if value is defined.",
Optional: true,
Computed: true,
},
"version": schema.StringAttribute{
MarkdownDescription: "Only valid if ref is defined. It's the version of the secret as defined in the target store.",
Optional: true,
Computed: true,
},
"value": schema.StringAttribute{
MarkdownDescription: "Value to store in the secret store. It can't be defined if ref is defined.",
Expand Down Expand Up @@ -156,21 +172,27 @@ func envValueIdPrefix(appID, envID string) string {
return strings.Join([]string{appID, envID}, "/")
}

func parseValueResponse(res *client.ValueResponse, data *ValueModel, idPrefix string) {
func parseValueResponse(ctx context.Context, res *client.ValueResponse, data *ValueModel, idPrefix string) {
data.ID = types.StringValue(strings.Join([]string{idPrefix, res.Key}, "/"))
data.Key = types.StringValue(res.Key)
data.Description = types.StringValue(res.Description)
data.IsSecret = types.BoolValue(res.IsSecret)
if !res.IsSecret {
data.Value = types.StringValue(res.Value)
} else {
data.SecretRef = &SecretRef{
secretRef := SecretRef{
Ref: types.StringValue(*res.SecretKey),
Store: types.StringValue(*res.SecretStoreId),
}
if res.SecretVersion != nil {
data.SecretRef.Version = types.StringValue(*res.SecretVersion)
secretRef.Version = types.StringValue(*res.SecretVersion)
}
objectValue, diags := types.ObjectValueFrom(ctx, SecretRefAttributeTypes(), secretRef)
if diags.HasError() {
tflog.Debug(ctx, "can't decode object from secret ref", map[string]interface{}{"err": diags})
return
}
data.SecretRef = objectValue
}
}

Expand All @@ -194,14 +216,20 @@ func (r *ResourceValue) Create(ctx context.Context, req resource.CreateRequest,
Description: data.Description.ValueStringPointer(),
IsSecret: data.IsSecret.ValueBoolPointer(),
}
if data.SecretRef == nil {
if data.SecretRef.IsNull() || data.SecretRef.IsUnknown() {
createPayload.Value = data.Value.ValueStringPointer()
} else {
var secretRef SecretRef
diags := data.SecretRef.As(ctx, secretRef, basetypes.ObjectAsOptions{})
if diags.HasError() {
tflog.Debug(ctx, "something went wrong", map[string]interface{}{"err": diags.Errors()})
return
}
createPayload.SecretRef = &client.SecretReference{
Ref: data.SecretRef.Ref.ValueStringPointer(),
Store: data.SecretRef.Store.ValueStringPointer(),
Version: data.SecretRef.Version.ValueStringPointer(),
Value: data.SecretRef.Value.ValueStringPointer(),
Ref: secretRef.Ref.ValueStringPointer(),
Store: secretRef.Store.ValueStringPointer(),
Version: secretRef.Version.ValueStringPointer(),
Value: secretRef.Value.ValueStringPointer(),
}
}

Expand Down Expand Up @@ -236,7 +264,7 @@ func (r *ResourceValue) Create(ctx context.Context, req resource.CreateRequest,
idPrefix = envValueIdPrefix(appID, envID)
}

parseValueResponse(res, data, idPrefix)
parseValueResponse(ctx, res, data, idPrefix)

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
Expand Down Expand Up @@ -299,7 +327,7 @@ func (r *ResourceValue) Read(ctx context.Context, req resource.ReadRequest, resp
return
}

parseValueResponse(&value, data, idPrefix)
parseValueResponse(ctx, &value, data, idPrefix)

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
Expand All @@ -321,17 +349,22 @@ func (r *ResourceValue) Update(ctx context.Context, req resource.UpdateRequest,
Description: data.Description.ValueStringPointer(),
IsSecret: data.IsSecret.ValueBoolPointer(),
}
if data.SecretRef == nil {
if data.SecretRef.IsNull() || data.SecretRef.IsUnknown() {
editPayload.Value = data.Value.ValueStringPointer()
} else {
var secretRef SecretRef
diags := data.SecretRef.As(ctx, secretRef, basetypes.ObjectAsOptions{})
if diags.HasError() {
tflog.Debug(ctx, "something went wrong", map[string]interface{}{"err": diags.Errors()})
return
}
editPayload.SecretRef = &client.SecretReference{
Ref: data.SecretRef.Ref.ValueStringPointer(),
Store: data.SecretRef.Store.ValueStringPointer(),
Version: data.SecretRef.Version.ValueStringPointer(),
Value: data.SecretRef.Value.ValueStringPointer(),
Ref: secretRef.Ref.ValueStringPointer(),
Store: secretRef.Store.ValueStringPointer(),
Version: secretRef.Version.ValueStringPointer(),
Value: secretRef.Value.ValueStringPointer(),
}
}

if data.EnvID.IsNull() {
httpResp, err := r.client.PutOrgsOrgIdAppsAppIdValuesKeyWithResponse(ctx, r.orgId, appID, data.Key.ValueString(), editPayload)
if err != nil {
Expand Down Expand Up @@ -363,7 +396,7 @@ func (r *ResourceValue) Update(ctx context.Context, req resource.UpdateRequest,
idPrefix = envValueIdPrefix(appID, envID)
}

parseValueResponse(res, data, idPrefix)
parseValueResponse(ctx, res, data, idPrefix)

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
Expand Down
119 changes: 116 additions & 3 deletions internal/provider/resource_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,76 @@ func TestAccResourceValue(t *testing.T) {
})
}

func TestAccResourceValueWithSecretValue(t *testing.T) {
appID := fmt.Sprintf("val-test-app-%d", time.Now().UnixNano())
key := "VAL_SECRET_1"
orgID := os.Getenv("HUMANITEC_ORG_ID")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: testAccResourceVALUETestAccResourceValueSecret(appID, key, "Example value with secret"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "key", key),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "description", "Example value with secret"),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "secret_ref.ref", fmt.Sprintf("orgs/%s/apps/%s/secret_values/%s/.value", orgID, appID, key)),
),
},
// ImportState testing
{
ResourceName: "humanitec_value.app_val_with_secret",
ImportState: true,
ImportStateIdFunc: func(s *terraform.State) (string, error) {
return fmt.Sprintf("%s/%s", appID, key), nil
},
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"value"},
},
// Delete testing automatically occurs in TestCase
},
})
}

func TestAccResourceValueWithSecretValueSecretRefValue(t *testing.T) {
appID := fmt.Sprintf("val-test-app-%d", time.Now().UnixNano())
key := "VAL_SECRET_1"
orgID := os.Getenv("HUMANITEC_ORG_ID")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: testAccResourceVALUETestAccResourceValueSecretRefValue(appID, key, "Example value with secret set via secret reference value"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "key", key),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "description", "Example value with secret set via secret reference value"),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "secret_ref.ref", fmt.Sprintf("orgs/%s/apps/%s/secret_values/%s/.value", orgID, appID, key)),
),
},
// ImportState testing
{
ResourceName: "humanitec_value.app_val_with_secret",
ImportState: true,
ImportStateIdFunc: func(s *terraform.State) (string, error) {
return fmt.Sprintf("%s/%s", appID, key), nil
},
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"value"},
},
// Delete testing automatically occurs in TestCase
},
})
}

func TestAccResourceValueWithSecretRef(t *testing.T) {
appID := fmt.Sprintf("val-test-app-%d", time.Now().UnixNano())
key := "VAL_SECRET_REF_1"
orgID := os.Getenv("HUMANITEC_ORG_ID")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -84,6 +151,14 @@ func TestAccResourceValueWithSecretRef(t *testing.T) {
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret_ref1", "secret_ref.ref", "path/to/secret/changed"),
),
},
// Update and Read testing
{
Config: testAccResourceVALUETestAccResourceValueSecret(appID, key, "Example value with secret reference updated with plain value"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret_ref1", "description", "Example value with secret reference changed"),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret_ref1", "secret_ref.ref", fmt.Sprintf("%s/apps/%s/secret_values/%s/.value", orgID, appID, key)),
),
},
// Delete testing automatically occurs in TestCase
},
})
Expand Down Expand Up @@ -245,11 +320,49 @@ resource "humanitec_value" "app_val_with_secret_ref1" {
key = "%s"
description = "%s"
is_secret = true
secret_ref = {
ref = "%s"
store = "external-store-id"
secret_ref = {
ref = "%s"
store = "external-store-id"
version = "1"
}
}
`, appID, key, description, secretPath)
}

func testAccResourceVALUETestAccResourceValueSecret(appID, key, description string) string {
return fmt.Sprintf(`
resource "humanitec_application" "val_test" {
id = "%s"
name = "val-test"
}
resource "humanitec_value" "app_val_with_secret" {
app_id = humanitec_application.val_test.id
key = "%s"
description = "%s"
is_secret = true
value = "secret"
}
`, appID, key, description)
}

func testAccResourceVALUETestAccResourceValueSecretRefValue(appID, key, description string) string {
return fmt.Sprintf(`
resource "humanitec_application" "val_test" {
id = "%s"
name = "val-test"
}
resource "humanitec_value" "app_val_with_secret" {
app_id = humanitec_application.val_test.id
key = "%s"
description = "%s"
is_secret = true
secret_ref = {
value = "secret"
}
}
`, appID, key, description)
}

0 comments on commit 5af4827

Please sign in to comment.