diff --git a/docs/resources/subaccount_entitlement.md b/docs/resources/subaccount_entitlement.md index 56291d2a..744ad2d9 100644 --- a/docs/resources/subaccount_entitlement.md +++ b/docs/resources/subaccount_entitlement.md @@ -50,6 +50,7 @@ resource "btp_subaccount_entitlement" "uas_reporting" { ### Optional - `amount` (Number) The quota assigned to the subaccount. +- `plan_unique_identifier` (String) The name of the unique identifier for the plan ### Read-Only diff --git a/internal/btpcli/facade_accounts_entitlement.go b/internal/btpcli/facade_accounts_entitlement.go index 7fe6699e..5d4bc2f0 100644 --- a/internal/btpcli/facade_accounts_entitlement.go +++ b/internal/btpcli/facade_accounts_entitlement.go @@ -81,7 +81,7 @@ func (f *accountsEntitlementFacade) AssignToSubaccount(ctx context.Context, dire return res, err } -func (f *accountsEntitlementFacade) EnableInSubaccount(ctx context.Context, directoryId string, subaccountId string, serviceName string, servicePlanName string) (CommandResponse, error) { +func (f *accountsEntitlementFacade) EnableInSubaccount(ctx context.Context, directoryId string, subaccountId string, serviceName string, servicePlanName string, planUniqueIdentifier string, enable bool) (CommandResponse, error) { params := map[string]string{ "globalAccount": f.cliClient.GetGlobalAccountSubdomain(), @@ -94,8 +94,12 @@ func (f *accountsEntitlementFacade) EnableInSubaccount(ctx context.Context, dire if len(directoryId) > 0 { params["directoryID"] = directoryId } - _, res, err := doExecute[cis_entitlements.EntitlementAssignmentResponseObject](f.cliClient, ctx, NewAssignRequest(f.getCommand(), params)) + if planUniqueIdentifier != "" { + params["planUniqueIdentifier"] = planUniqueIdentifier + } + + _, res, err := doExecute[cis_entitlements.EntitlementAssignmentResponseObject](f.cliClient, ctx, NewAssignRequest(f.getCommand(), params)) return res, err } diff --git a/internal/btpcli/facade_accounts_entitlement_test.go b/internal/btpcli/facade_accounts_entitlement_test.go index 21f726ea..f9076fb1 100644 --- a/internal/btpcli/facade_accounts_entitlement_test.go +++ b/internal/btpcli/facade_accounts_entitlement_test.go @@ -146,6 +146,7 @@ func TestAccountsEntitlementFacade_EnableInSubaccount(t *testing.T) { subaccountId := "6aa64c2f-38c1-49a9-b2e8-cf9fea769b7f" serviceName := "alert-notification" planName := "free" + planUniqueIdentifier := "my-unique-id" t.Run("constructs the CLI params correctly", func(t *testing.T) { var srvCalled bool @@ -154,17 +155,18 @@ func TestAccountsEntitlementFacade_EnableInSubaccount(t *testing.T) { srvCalled = true assertCall(t, r, command, ActionAssign, map[string]string{ - "globalAccount": "795b53bb-a3f0-4769-adf0-26173282a975", - "directoryID": directoryId, - "subaccount": subaccountId, - "serviceName": serviceName, - "servicePlanName": planName, - "enable": "true", + "globalAccount": "795b53bb-a3f0-4769-adf0-26173282a975", + "directoryID": directoryId, + "subaccount": subaccountId, + "serviceName": serviceName, + "servicePlanName": planName, + "planUniqueIdentifier": planUniqueIdentifier, + "enable": "true", }) })) defer srv.Close() - res, err := uut.Accounts.Entitlement.EnableInSubaccount(context.TODO(), directoryId, subaccountId, serviceName, planName) + res, err := uut.Accounts.Entitlement.EnableInSubaccount(context.TODO(), directoryId, subaccountId, serviceName, planName, planUniqueIdentifier, true) if assert.True(t, srvCalled) && assert.NoError(t, err) { assert.Equal(t, 200, res.StatusCode) diff --git a/internal/provider/resource_subaccount_entitlement.go b/internal/provider/resource_subaccount_entitlement.go index 22590f12..a9e43297 100644 --- a/internal/provider/resource_subaccount_entitlement.go +++ b/internal/provider/resource_subaccount_entitlement.go @@ -122,6 +122,14 @@ __Further documentation:__ int64planmodifier.UseStateForUnknown(), }, }, + "plan_unique_identifier": schema.StringAttribute{ + MarkdownDescription: "The name of the unique identifier for the plan", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, "state": schema.StringAttribute{ MarkdownDescription: "The current state of the entitlement. Possible values are: \n " + getFormattedValueAsTableRow("state", "description") + @@ -227,7 +235,7 @@ func (rs *subaccountEntitlementResource) createOrUpdate(ctx context.Context, req // Determine the parent of the subaccount subaccountData, _, _ := rs.cli.Accounts.Subaccount.Get(ctx, plan.SubaccountId.ValueString()) - //Determine if the parent of the subaccount is a directory and if it has authoization enabled + // Determine if the parent of the subaccount is a directory and if it has authorization enabled parentId, isParentGlobalAccount := determineParentIdForAuthorization(rs.cli, ctx, subaccountData.ParentGUID) var directoryId string @@ -240,12 +248,11 @@ func (rs *subaccountEntitlementResource) createOrUpdate(ctx context.Context, req Pending: []string{entitlementCallRetryPending}, Target: []string{entitlementCallRetryFailed, entitlementCallRetrySucceeded}, Refresh: func() (interface{}, string, error) { - var callResult btpcli.CommandResponse var err error if !hasPlanQuota(plan) { - callResult, err = rs.cli.Accounts.Entitlement.EnableInSubaccount(ctx, directoryId, plan.SubaccountId.ValueString(), plan.ServiceName.ValueString(), plan.PlanName.ValueString()) + callResult, err = rs.cli.Accounts.Entitlement.EnableInSubaccount(ctx, directoryId, plan.SubaccountId.ValueString(), plan.ServiceName.ValueString(), plan.PlanName.ValueString(), plan.PlanUniqueIdentifier.ValueString(), true) } else { callResult, err = rs.cli.Accounts.Entitlement.AssignToSubaccount(ctx, directoryId, plan.SubaccountId.ValueString(), plan.ServiceName.ValueString(), plan.PlanName.ValueString(), int(plan.Amount.ValueInt64())) } @@ -284,7 +291,6 @@ func (rs *subaccountEntitlementResource) createOrUpdate(ctx context.Context, req Pending: []string{cis_entitlements.StateStarted, cis_entitlements.StateProcessing}, Target: []string{cis_entitlements.StateOK}, Refresh: func() (interface{}, string, error) { - entitlement, _, err := rs.cli.Accounts.Entitlement.GetAssignedBySubaccount(ctx, plan.SubaccountId.ValueString(), plan.ServiceName.ValueString(), plan.PlanName.ValueString(), isParentGlobalAccount, parentId) if isRetriableError(err) { @@ -298,6 +304,7 @@ func (rs *subaccountEntitlementResource) createOrUpdate(ctx context.Context, req if entitlement == nil { return nil, cis_entitlements.StateProcessing, nil } + // No error returned even if operation failed if entitlement.Assignment.EntityState == cis_entitlements.StateProcessingFailed { return *entitlement, entitlement.Assignment.EntityState, errors.New("undefined API error during entitlement processing") diff --git a/internal/provider/resource_subaccount_entitlement_test.go b/internal/provider/resource_subaccount_entitlement_test.go index 03c2a6fa..348be89d 100644 --- a/internal/provider/resource_subaccount_entitlement_test.go +++ b/internal/provider/resource_subaccount_entitlement_test.go @@ -85,25 +85,19 @@ func TestResourceSubaccountEntitlement(t *testing.T) { ProtoV6ProviderFactories: getProviders(rec.GetDefaultClient()), Steps: []resource.TestStep{ { - Config: hclProviderFor(user) + hclResourceSubaccountEntitlementWithAmountBySubaccount("uut", "integration-test-acc-static", "data-privacy-integration-service", "standard", "3"), + Config: hclProviderFor(user) + hclResourceSubaccountEntitlementWithAmountBySubaccount("uut", "integration-test-acc-static", "uas", "reporting-directory", "3"), 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", "id", "uas-reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_name", "reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_id", "uas-reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "service_name", "uas"), resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "amount", "3"), resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "state", "OK"), ), }, - { - ResourceName: "btp_subaccount_entitlement.uut", - ImportStateIdFunc: getImportStateIdForSubaccountEntitlement("btp_subaccount_entitlement.uut", "data-privacy-integration-service", "standard"), - ImportState: true, - ImportStateVerify: true, - }, }, }) }) @@ -117,43 +111,43 @@ func TestResourceSubaccountEntitlement(t *testing.T) { ProtoV6ProviderFactories: getProviders(rec.GetDefaultClient()), Steps: []resource.TestStep{ { - Config: hclProviderFor(user) + hclResourceSubaccountEntitlementWithAmountBySubaccount("uut", "integration-test-acc-static", "data-privacy-integration-service", "standard", "1"), + Config: hclProviderFor(user) + hclResourceSubaccountEntitlementWithAmountBySubaccount("uut", "integration-test-acc-static", "uas", "reporting-directory", "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", "id", "uas-reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_name", "reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_id", "uas-reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "service_name", "uas"), resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "amount", "1"), resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "state", "OK"), ), }, { - Config: hclProviderFor(user) + hclResourceSubaccountEntitlementWithAmountBySubaccount("uut", "integration-test-acc-static", "data-privacy-integration-service", "standard", "2"), + Config: hclProviderFor(user) + hclResourceSubaccountEntitlementWithAmountBySubaccount("uut", "integration-test-acc-static", "uas", "reporting-directory", "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", "id", "uas-reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_name", "reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_id", "uas-reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "service_name", "uas"), resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "amount", "2"), resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "state", "OK"), ), }, { - Config: hclProviderFor(user) + hclResourceSubaccountEntitlementBySubaccount("uut", "integration-test-acc-static", "data-privacy-integration-service", "standard"), + Config: hclProviderFor(user) + hclResourceSubaccountEntitlementBySubaccount("uut", "integration-test-acc-static", "uas", "reporting-directory"), 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", "id", "uas-reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_name", "reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_id", "uas-reporting-directory"), + resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "service_name", "uas"), resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "amount", "2"), resource.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "state", "OK"), ), @@ -162,13 +156,39 @@ func TestResourceSubaccountEntitlement(t *testing.T) { }) }) + t.Run("happy path - plan unique identifier", func(t *testing.T) { + rec, _ := setupVCR(t, "fixtures/resource_subaccount_entitlement.plan_unique_identifier") + defer stopQuietly(rec) + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: getProviders(rec.GetDefaultClient()), + Steps: []resource.TestStep{ + { + Config: hclResourceSubaccountEntitlementWithPlanUniqueIdentifierBySubaccount("uut", "integration-test-acc-static", "hana-cloud", "hana", "unique-id-hana"), + 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", "hana-cloud-hana"), + 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.TestCheckResourceAttr("btp_subaccount_entitlement.uut", "plan_unique_identifier", "unique-id-hana"), + 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: hclResourceSubaccountEntitlementWithAmountBySubaccount("uut", "integration-test-acc-static", "data-privacy-integration-service", "standard", "0"), + Config: hclResourceSubaccountEntitlementWithAmountBySubaccount("uut", "integration-test-acc-static", "uas", "reporting-directory", "0"), ExpectError: regexp.MustCompile(`Attribute amount value must be between 1 and 2000000000, got: 0`), }, }, @@ -208,3 +228,14 @@ func getImportStateIdForSubaccountEntitlement(resourceName string, serviceName s return fmt.Sprintf("%s,%s,%s", rs.Primary.Attributes["subaccount_id"], serviceName, planName), nil } } + +func hclResourceSubaccountEntitlementWithPlanUniqueIdentifierBySubaccount(resourceName string, subaccountName string, serviceName string, planName string, planUniqueIdentifier string) string { + return fmt.Sprintf(` +data "btp_subaccounts" "all" {} +resource "btp_subaccount_entitlement" "%s" { + subaccount_id = [for sa in data.btp_subaccounts.all.values : sa.id if sa.name == "%s"][0] + service_name = "%s" + plan_name = "%s" + plan_unique_identifier = "%s" +}`, resourceName, subaccountName, serviceName, planName, planUniqueIdentifier) +} diff --git a/internal/provider/type_subaccount_entitlement.go b/internal/provider/type_subaccount_entitlement.go index 5220d0ec..058c0827 100644 --- a/internal/provider/type_subaccount_entitlement.go +++ b/internal/provider/type_subaccount_entitlement.go @@ -10,29 +10,31 @@ import ( ) type subaccountEntitlementType struct { - SubaccountId types.String `tfsdk:"subaccount_id"` - Id types.String `tfsdk:"id"` - ServiceName types.String `tfsdk:"service_name"` - PlanName types.String `tfsdk:"plan_name"` - Category types.String `tfsdk:"category"` - PlanId types.String `tfsdk:"plan_id"` - Amount types.Int64 `tfsdk:"amount"` - State types.String `tfsdk:"state"` - CreatedDate types.String `tfsdk:"created_date"` - LastModified types.String `tfsdk:"last_modified"` + SubaccountId types.String `tfsdk:"subaccount_id"` + Id types.String `tfsdk:"id"` + ServiceName types.String `tfsdk:"service_name"` + PlanName types.String `tfsdk:"plan_name"` + Category types.String `tfsdk:"category"` + PlanId types.String `tfsdk:"plan_id"` + Amount types.Int64 `tfsdk:"amount"` + PlanUniqueIdentifier types.String `tfsdk:"plan_unique_identifier"` + State types.String `tfsdk:"state"` + CreatedDate types.String `tfsdk:"created_date"` + LastModified types.String `tfsdk:"last_modified"` } func subaccountEntitlementValueFrom(ctx context.Context, value btpcli.UnfoldedAssignment) (subaccountEntitlementType, diag.Diagnostics) { return subaccountEntitlementType{ - SubaccountId: types.StringValue(value.Assignment.EntityId), - Id: types.StringValue(value.Plan.UniqueIdentifier), - ServiceName: types.StringValue(value.Service.Name), - PlanName: types.StringValue(value.Plan.Name), - Category: types.StringValue(value.Plan.Category), - PlanId: types.StringValue(value.Plan.UniqueIdentifier), - Amount: types.Int64Value(int64(value.Assignment.Amount)), - State: types.StringValue(value.Assignment.EntityState), - LastModified: timeToValue(value.Assignment.ModifiedDate.Time()), - CreatedDate: timeToValue(value.Assignment.CreatedDate.Time()), + SubaccountId: types.StringValue(value.Assignment.EntityId), + Id: types.StringValue(value.Plan.UniqueIdentifier), + ServiceName: types.StringValue(value.Service.Name), + PlanName: types.StringValue(value.Plan.Name), + Category: types.StringValue(value.Plan.Category), + PlanId: types.StringValue(value.Plan.UniqueIdentifier), + PlanUniqueIdentifier: types.StringValue(value.Plan.UniqueIdentifier), + Amount: types.Int64Value(int64(value.Assignment.Amount)), + State: types.StringValue(value.Assignment.EntityState), + LastModified: timeToValue(value.Assignment.ModifiedDate.Time()), + CreatedDate: timeToValue(value.Assignment.CreatedDate.Time()), }, diag.Diagnostics{} }