Skip to content

Commit

Permalink
feat(resource): support update of subaccount entitlements (#304)
Browse files Browse the repository at this point in the history
Also: fix validation of "amount" and corresponding test to prevent
      quota being actively set to 0, which results in deletion and
      an inconsistent state.

Resolves #101
  • Loading branch information
kuntzed authored Jul 20, 2023
1 parent 750b4fd commit f01e359
Show file tree
Hide file tree
Showing 3 changed files with 1,552 additions and 33 deletions.
1,463 changes: 1,463 additions & 0 deletions internal/provider/fixtures/resource_subaccount_entitlement.update.yaml

Large diffs are not rendered by default.

66 changes: 33 additions & 33 deletions internal/provider/resource_subaccount_entitlement.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"strings"
"time"

Expand Down Expand Up @@ -51,21 +55,33 @@ __Further documentation:__
"subaccount_id": schema.StringAttribute{
MarkdownDescription: "The ID of the subaccount.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
uuidvalidator.ValidUUID(),
},
},
"id": schema.StringAttribute{
MarkdownDescription: "The ID of the entitled service plan.",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"service_name": schema.StringAttribute{
MarkdownDescription: "The name of the entitled service.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"plan_name": schema.StringAttribute{
MarkdownDescription: "The name of the entitled service plan.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"plan_id": schema.StringAttribute{
MarkdownDescription: "The ID of the entitled service plan.",
Expand All @@ -75,7 +91,7 @@ __Further documentation:__
MarkdownDescription: "The quota assigned to the subaccount.",
Optional: true,
Validators: []validator.Int64{
int64validator.Between(0, 2000000000),
int64validator.Between(1, 2000000000),
},
},
"state": schema.StringAttribute{
Expand Down Expand Up @@ -125,10 +141,18 @@ func (rs *subaccountEntitlementResource) Read(ctx context.Context, req resource.
}

func (rs *subaccountEntitlementResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
rs.createOrUpdate(ctx, req.Plan, &resp.Diagnostics, &resp.State, "Creating")
}

func (rs *subaccountEntitlementResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
rs.createOrUpdate(ctx, req.Plan, &resp.Diagnostics, &resp.State, "Updating")
}

func (rs *subaccountEntitlementResource) createOrUpdate(ctx context.Context, requestPlan tfsdk.Plan, responseDiagnostics *diag.Diagnostics, responseState *tfsdk.State, action string) {
var plan subaccountEntitlementType
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
diags := requestPlan.Get(ctx, &plan)
responseDiagnostics.Append(diags...)
if responseDiagnostics.HasError() {
return
}

Expand All @@ -140,7 +164,7 @@ func (rs *subaccountEntitlementResource) Create(ctx context.Context, req resourc
}

if err != nil {
resp.Diagnostics.AddError("API Error Creating Resource Entitlement (Subaccount)", fmt.Sprintf("%s", err))
responseDiagnostics.AddError(fmt.Sprintf("API Error %s Resource Entitlement (Subaccount)", action), fmt.Sprintf("%s", err))
return
}

Expand Down Expand Up @@ -168,40 +192,16 @@ func (rs *subaccountEntitlementResource) Create(ctx context.Context, req resourc

entitlement, err := createStateConf.WaitForStateContext(ctx)
if err != nil {
resp.Diagnostics.AddError("API Error Creating Resource Entitlement (Subaccount)", fmt.Sprintf("%s", err))
responseDiagnostics.AddError(fmt.Sprintf("API Error %s Resource Entitlement (Subaccount)", action), fmt.Sprintf("%s", err))
return
}

updatedState, diags := subaccountEntitlementValueFrom(ctx, entitlement.(btpcli.UnfoldedEntitlement))
updatedState.Amount = plan.Amount
resp.Diagnostics.Append(diags...)

diags = resp.State.Set(ctx, &updatedState)
resp.Diagnostics.Append(diags...)
}
responseDiagnostics.Append(diags...)

func (rs *subaccountEntitlementResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan subaccountEntitlementType
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.AddError("API Error Updating Resource Entitlement (Subaccount)", "Update is not yet implemented.")

/* TODO: implementation of UPDATE operation
cliRes, err := gen.client.Execute(ctx, btpcli.Update, gen.command, plan)
if err != nil {
resp.Diagnostics.AddError("API Error Updating Resource Entitlement (Subaccount)", fmt.Sprintf("%s", err))
return
}*/

diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = responseState.Set(ctx, &updatedState)
responseDiagnostics.Append(diags...)
}

func (rs *subaccountEntitlementResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
Expand Down
56 changes: 56 additions & 0 deletions internal/provider/resource_subaccount_entitlement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package provider

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
Expand All @@ -27,6 +28,7 @@ func TestResourceSubaccountEntitlement(t *testing.T) {
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_name", "hana"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_id", "hana-cloud-hana"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "service_name", "hana-cloud"),
resource.TestCheckNoResourceAttr("btp_subaccount_entitlement.uut", "amount"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "state", "OK"),
),
},
Expand Down Expand Up @@ -59,12 +61,66 @@ func TestResourceSubaccountEntitlement(t *testing.T) {
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_name", "standard"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_id", "data-privacy-integration-service-standard"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "service_name", "data-privacy-integration-service"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "amount", "3"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "state", "OK"),
),
},
},
})
})

t.Run("happy path - update", func(t *testing.T) {
rec := setupVCR(t, "fixtures/resource_subaccount_entitlement.update")
defer stopQuietly(rec)

resource.Test(t, resource.TestCase{
IsUnitTest: true,
ProtoV6ProviderFactories: getProviders(rec.GetDefaultClient()),
Steps: []resource.TestStep{
{
Config: hclProvider() + hclResourceSubaccountEntitlementWithAmount("uut", "ef23ace8-6ade-4d78-9c1f-8df729548bbf", "data-privacy-integration-service", "standard", "1"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestMatchResourceAttr("btp_subaccount_entitlement.uut", "subaccount_id", regexpValidUUID),
resource.TestMatchResourceAttr("btp_subaccount_entitlement.uut", "created_date", regexpValidRFC3999Format),
resource.TestMatchResourceAttr("btp_subaccount_entitlement.uut", "last_modified", regexpValidRFC3999Format),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "id", "data-privacy-integration-service-standard"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_name", "standard"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_id", "data-privacy-integration-service-standard"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "service_name", "data-privacy-integration-service"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "amount", "1"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "state", "OK"),
),
},
{
Config: hclProvider() + hclResourceSubaccountEntitlementWithAmount("uut", "ef23ace8-6ade-4d78-9c1f-8df729548bbf", "data-privacy-integration-service", "standard", "2"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestMatchResourceAttr("btp_subaccount_entitlement.uut", "subaccount_id", regexpValidUUID),
resource.TestMatchResourceAttr("btp_subaccount_entitlement.uut", "created_date", regexpValidRFC3999Format),
resource.TestMatchResourceAttr("btp_subaccount_entitlement.uut", "last_modified", regexpValidRFC3999Format),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "id", "data-privacy-integration-service-standard"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_name", "standard"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_id", "data-privacy-integration-service-standard"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "service_name", "data-privacy-integration-service"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "amount", "2"),
resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "state", "OK"),
),
},
},
})
})

t.Run("error path - zero amount", func(t *testing.T) {
resource.Test(t, resource.TestCase{
IsUnitTest: true,
ProtoV6ProviderFactories: getProviders(nil),
Steps: []resource.TestStep{
{
Config: hclProvider() + hclResourceSubaccountEntitlementWithAmount("uut", "ef23ace8-6ade-4d78-9c1f-8df729548bbf", "data-privacy-integration-service", "standard", "0"),
ExpectError: regexp.MustCompile(`Attribute amount value must be between 1 and 2000000000, got: 0`),
},
},
})
})
}

func hclResourceSubaccountEntitlement(resourceName string, subaccountId string, serviceName string, planName string) string {
Expand Down

0 comments on commit f01e359

Please sign in to comment.