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 edcf9b0
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 33 deletions.
98 changes: 76 additions & 22 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,38 @@ 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)
data.SecretRef = basetypes.NewObjectNull(SecretRefAttributeTypes())
} else {
data.SecretRef = &SecretRef{
Ref: types.StringValue(*res.SecretKey),
Store: types.StringValue(*res.SecretStoreId),
var secretRef SecretRef
if data.SecretRef.IsUnknown() {
secretRef = SecretRef{}
} else {
diags := data.SecretRef.As(ctx, &secretRef, basetypes.ObjectAsOptions{})
if diags.HasError() {
tflog.Debug(ctx, "can't populate secretRef from model", map[string]interface{}{"err": diags.Errors()})
return
}
}

secretRef.Ref = types.StringValue(*res.SecretKey)
secretRef.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 +227,25 @@ func (r *ResourceValue) Create(ctx context.Context, req resource.CreateRequest,
Description: data.Description.ValueStringPointer(),
IsSecret: data.IsSecret.ValueBoolPointer(),
}
if data.SecretRef == nil {
if !data.Value.IsNull() {
createPayload.Value = data.Value.ValueStringPointer()
} else {
createPayload.SecretRef = &client.SecretReference{
Ref: data.SecretRef.Ref.ValueStringPointer(),
Store: data.SecretRef.Store.ValueStringPointer(),
Version: data.SecretRef.Version.ValueStringPointer(),
Value: data.SecretRef.Value.ValueStringPointer(),
var secretRef SecretRef
diags := data.SecretRef.As(ctx, &secretRef, basetypes.ObjectAsOptions{})
if diags.HasError() {
tflog.Debug(ctx, "can't populate secretRef from model", map[string]interface{}{"err": diags.Errors()})
return
}
if !secretRef.Value.IsNull() {
createPayload.SecretRef = &client.SecretReference{
Value: secretRef.Value.ValueStringPointer(),
}
} else {
createPayload.SecretRef = &client.SecretReference{
Ref: secretRef.Ref.ValueStringPointer(),
Store: secretRef.Store.ValueStringPointer(),
Version: secretRef.Version.ValueStringPointer(),
}
}
}

Expand Down Expand Up @@ -236,7 +280,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 +343,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 +365,27 @@ func (r *ResourceValue) Update(ctx context.Context, req resource.UpdateRequest,
Description: data.Description.ValueStringPointer(),
IsSecret: data.IsSecret.ValueBoolPointer(),
}
if data.SecretRef == nil {
if !data.Value.IsNull() {
editPayload.Value = data.Value.ValueStringPointer()
} else {
editPayload.SecretRef = &client.SecretReference{
Ref: data.SecretRef.Ref.ValueStringPointer(),
Store: data.SecretRef.Store.ValueStringPointer(),
Version: data.SecretRef.Version.ValueStringPointer(),
Value: data.SecretRef.Value.ValueStringPointer(),
var secretRef SecretRef
diags := data.SecretRef.As(ctx, &secretRef, basetypes.ObjectAsOptions{})
if diags.HasError() {
tflog.Debug(ctx, "can't populate secretRef from model", map[string]interface{}{"err": diags.Errors()})
return
}
if !secretRef.Value.IsNull() {
editPayload.SecretRef = &client.SecretReference{
Value: secretRef.Value.ValueStringPointer(),
}
} else {
editPayload.SecretRef = &client.SecretReference{
Ref: secretRef.Ref.ValueStringPointer(),
Store: secretRef.Store.ValueStringPointer(),
Version: secretRef.Version.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 +417,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
138 changes: 127 additions & 11 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{"secret_ref", "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_REF_VALUE_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{"secret_ref"},
},
// 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 All @@ -62,26 +129,35 @@ func TestAccResourceValueWithSecretRef(t *testing.T) {
{
Config: testAccResourceVALUETestAccResourceValueSecretRef(appID, key, "path/to/secret", "Example value with secret reference"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret_ref1", "key", key),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret_ref1", "description", "Example value with secret reference"),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret_ref1", "secret_ref.ref", "path/to/secret"),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "key", key),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "description", "Example value with secret reference"),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "secret_ref.ref", "path/to/secret"),
),
},
// ImportState testing
{
ResourceName: "humanitec_value.app_val_with_secret_ref1",
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,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"secret_ref"},
},
// Update and Read testing
{
Config: testAccResourceVALUETestAccResourceValueSecretRef(appID, key, "path/to/secret/changed", "Example value with secret reference changed"),
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", "path/to/secret/changed"),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "description", "Example value with secret reference changed"),
resource.TestCheckResourceAttr("humanitec_value.app_val_with_secret", "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", "description", "Example value with secret reference updated with plain 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)),
),
},
// Delete testing automatically occurs in TestCase
Expand All @@ -93,6 +169,7 @@ func TestAccResourceValueDeletedOutManually(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()
appID := fmt.Sprintf("val-test-app-%d", time.Now().UnixNano())

key := "VAL_1"

orgID := os.Getenv("HUMANITEC_ORG_ID")
Expand Down Expand Up @@ -139,6 +216,7 @@ func TestAccResourceValueDeletedOutManually(t *testing.T) {

func TestAccResourceValueWithEnv(t *testing.T) {
appID := fmt.Sprintf("val-test-app-env-%d", time.Now().UnixNano())

envID := "dev"
key := "VAL_1"

Expand Down Expand Up @@ -239,17 +317,55 @@ resource "humanitec_application" "val_test" {
name = "val-test"
}
resource "humanitec_value" "app_val_with_secret_ref1" {
resource "humanitec_value" "app_val_with_secret" {
app_id = humanitec_application.val_test.id
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 edcf9b0

Please sign in to comment.