diff --git a/docs/resources/rms_organizational_assignment_package.md b/docs/resources/rms_organizational_assignment_package.md index eb490c7493..572f8a6cae 100644 --- a/docs/resources/rms_organizational_assignment_package.md +++ b/docs/resources/rms_organizational_assignment_package.md @@ -39,38 +39,33 @@ The following arguments are supported: Changing this parameter will create a new resource. -* `name` - (Required, String, ForceNew) Specifies the assignment package name. It contains 1 to 64 characters. +* `name` - (Required, String) Specifies the assignment package name. It contains `1` to `64` characters. - Changing this parameter will create a new resource. +* `excluded_accounts` - (Optional, List) Specifies the excluded accounts for conformance package deployment. -* `excluded_accounts` - (Optional, List, ForceNew) Specifies the excluded accounts for conformance package deployment. +* `template_key` - (Optional, String, ForceNew) Specifies the name of a predefined conformance package. It contains `1` to + `128` characters. Changing this parameter will create a new resource. -* `template_key` - (Optional, String, ForceNew) Specifies the name of a predefined conformance package. It contains 1 to - 128 characters. +* `template_body` - (Optional, String, ForceNew) Specifies the content of a custom assignment package. It contains `1` to + `51200` characters. Changing this parameter will create a new resource. -* `template_body` - (Optional, String, ForceNew) Specifies the content of a custom assignment package. It contains 1 to - 51200 characters. +* `template_uri` - (Optional, String, ForceNew) Specifies the OBS address of a conformance package. It contains `1` to + `1024` characters. Changing this parameter will create a new resource. -* `template_uri` - (Optional, String, ForceNew) Specifies the OBS address of a conformance package. It contains 1 to - 1024 characters. - - Changing this parameter will create a new resource. +* `vars_structure` - (Optional, List) Specifies the parameters of a conformance package. -* `vars_structure` - (Optional, List, ForceNew) Specifies the parameters of a conformance package. - - Changing this parameter will create a new resource. The [vars_structure](#OrgAssignmentPackage_VarStructure) structure is documented below. The `vars_structure` block supports: -* `var_key` - (Optional, String) Specifies the name of a parameter. It contains 1 to 64 characters. +* `var_key` - (Optional, String) Specifies the name of a parameter. It contains `1` to `64` characters. * `var_value` - (Optional, String) Specifies the value of a parameter. It's a json string. diff --git a/huaweicloud/services/acceptance/acceptance.go b/huaweicloud/services/acceptance/acceptance.go index 70935f34b6..411f45d686 100644 --- a/huaweicloud/services/acceptance/acceptance.go +++ b/huaweicloud/services/acceptance/acceptance.go @@ -111,7 +111,9 @@ var ( HW_RAM_SHARE_INVITATION_ID = os.Getenv("HW_RAM_SHARE_INVITATION_ID") HW_RAM_SHARE_ID = os.Getenv("HW_RAM_SHARE_ID") - HW_RMS_TARGET_ID = os.Getenv("HW_RMS_TARGET_ID") + HW_RMS_TARGET_ID = os.Getenv("HW_RMS_TARGET_ID") + HW_RMS_EXCLUDED_ACCOUNT_1 = os.Getenv("HW_RMS_EXCLUDED_ACCOUNT_1") + HW_RMS_EXCLUDED_ACCOUNT_2 = os.Getenv("HW_RMS_EXCLUDED_ACCOUNT_2") HW_CDN_DOMAIN_NAME = os.Getenv("HW_CDN_DOMAIN_NAME") // `HW_CDN_CERT_DOMAIN_NAME` Configure the domain name environment variable of the certificate type. @@ -1135,6 +1137,13 @@ func TestAccPreCheckRMSTargetID(t *testing.T) { } } +// lintignore:AT003 +func TestAccPreCheckRMSExcludedAccounts(t *testing.T) { + if HW_RMS_EXCLUDED_ACCOUNT_1 == "" || HW_RMS_EXCLUDED_ACCOUNT_2 == "" { + t.Skip("HW_RMS_EXCLUDED_ACCOUNT_1 and HW_RMS_EXCLUDED_ACCOUNT_2 must be set for the acceptance tests.") + } +} + // lintignore:AT003 func TestAccPreCheckDms(t *testing.T) { if HW_DMS_ENVIRONMENT == "" { diff --git a/huaweicloud/services/acceptance/rms/resource_huaweicloud_rms_organizational_assignment_package_test.go b/huaweicloud/services/acceptance/rms/resource_huaweicloud_rms_organizational_assignment_package_test.go index 1bb81763d4..77302974ad 100644 --- a/huaweicloud/services/acceptance/rms/resource_huaweicloud_rms_organizational_assignment_package_test.go +++ b/huaweicloud/services/acceptance/rms/resource_huaweicloud_rms_organizational_assignment_package_test.go @@ -66,6 +66,7 @@ func TestAccOrgAssignmentPackage_basic(t *testing.T) { PreCheck: func() { acceptance.TestAccPreCheck(t) acceptance.TestAccPreCheckOrganizationsOpen(t) + acceptance.TestAccPreCheckRMSExcludedAccounts(t) }, ProviderFactories: acceptance.TestAccProviderFactories, CheckDestroy: rc.CheckResourceDestroy(), @@ -85,6 +86,23 @@ func TestAccOrgAssignmentPackage_basic(t *testing.T) { resource.TestCheckResourceAttrSet(rName, "updated_at"), ), }, + { + Config: testOrgAssignmentPackage_update(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttrPair(rName, "organization_id", + "data.huaweicloud_organizations_organization.test", "id"), + resource.TestCheckResourceAttr(rName, "name", name+"-update"), + resource.TestCheckTypeSetElemNestedAttrs(rName, "vars_structure.*", map[string]string{ + "var_key": "lastBackupAgeValue", + "var_value": "25", + }), + resource.TestCheckResourceAttrSet(rName, "owner_id"), + resource.TestCheckResourceAttrSet(rName, "org_conformance_pack_urn"), + resource.TestCheckResourceAttrSet(rName, "created_at"), + resource.TestCheckResourceAttrSet(rName, "updated_at"), + ), + }, { ResourceName: rName, ImportState: true, @@ -100,13 +118,20 @@ func testOrgAssignmentPackage_basic(name string) string { return fmt.Sprintf(` data "huaweicloud_organizations_organization" "test" {} -data "huaweicloud_rms_assignment_package_templates" "test" {} +data "huaweicloud_rms_assignment_package_templates" "test" { + template_key = "Operational-Best-Practices-for-ECS.tf.json" +} resource "huaweicloud_rms_organizational_assignment_package" "test" { organization_id = data.huaweicloud_organizations_organization.test.id - name = "%s" + name = "%[1]s" template_key = data.huaweicloud_rms_assignment_package_templates.test.templates.0.template_key + excluded_accounts = [ + "%[2]s", + "%[3]s", + ] + dynamic "vars_structure" { for_each = data.huaweicloud_rms_assignment_package_templates.test.templates.0.parameters content { @@ -115,7 +140,36 @@ resource "huaweicloud_rms_organizational_assignment_package" "test" { } } } -`, name) +`, name, acceptance.HW_RMS_EXCLUDED_ACCOUNT_1, acceptance.HW_RMS_EXCLUDED_ACCOUNT_2) +} + +func testOrgAssignmentPackage_update(name string) string { + return fmt.Sprintf(` +data "huaweicloud_organizations_organization" "test" {} + +data "huaweicloud_rms_assignment_package_templates" "test" { + template_key = "Operational-Best-Practices-for-ECS.tf.json" +} + +resource "huaweicloud_rms_organizational_assignment_package" "test" { + organization_id = data.huaweicloud_organizations_organization.test.id + name = "%[1]s-update" + template_key = data.huaweicloud_rms_assignment_package_templates.test.templates.0.template_key + + excluded_accounts = [ + "%[2]s", + "%[3]s", + ] + + dynamic "vars_structure" { + for_each = data.huaweicloud_rms_assignment_package_templates.test.templates.0.parameters + content { + var_key = vars_structure.value["name"] + var_value = vars_structure.value["name"] == "lastBackupAgeValue" ? 25 : vars_structure.value["default_value"] + } + } +} +`, name, acceptance.HW_RMS_EXCLUDED_ACCOUNT_1, acceptance.HW_RMS_EXCLUDED_ACCOUNT_2) } func testOrgAssignmentPackageImportState(name string) resource.ImportStateIdFunc { diff --git a/huaweicloud/services/rms/resource_huaweicloud_rms_organizational_assignment_package.go b/huaweicloud/services/rms/resource_huaweicloud_rms_organizational_assignment_package.go index 2b564d5a71..3ce78185df 100644 --- a/huaweicloud/services/rms/resource_huaweicloud_rms_organizational_assignment_package.go +++ b/huaweicloud/services/rms/resource_huaweicloud_rms_organizational_assignment_package.go @@ -25,13 +25,15 @@ import ( ) // @API Config POST /v1/resource-manager/organizations/{organization_id}/conformance-packs -// @API Config GET /v1/resource-manager/organizations/{organization_id}/conformance-packs/detailed-statuses +// @API Config GET /v1/resource-manager/organizations/{organization_id}/conformance-packs/statuses // @API Config GET /v1/resource-manager/organizations/{organization_id}/conformance-packs/{conformance_pack_id} // @API Config DELETE /v1/resource-manager/organizations/{organization_id}/conformance-packs/{conformance_pack_id} +// @API Config PUT /v1/resource-manager/organizations/{organization_id}/conformance-packs/{conformance_pack_id} func ResourceOrgAssignmentPackage() *schema.Resource { return &schema.Resource{ CreateContext: resourceOrgAssignmentPackageCreate, ReadContext: resourceOrgAssignmentPackageRead, + UpdateContext: resourceOrgAssignmentPackageUpdate, DeleteContext: resourceOrgAssignmentPackageDelete, Importer: &schema.ResourceImporter{ StateContext: resourceOrgAssignmentPackageImportState, @@ -39,6 +41,7 @@ func ResourceOrgAssignmentPackage() *schema.Resource { Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), Delete: schema.DefaultTimeout(30 * time.Minute), }, @@ -52,7 +55,6 @@ func ResourceOrgAssignmentPackage() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ForceNew: true, Description: `Specifies the assignment package name.`, }, "excluded_accounts": { @@ -60,7 +62,6 @@ func ResourceOrgAssignmentPackage() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, Computed: true, - ForceNew: true, Description: `Specifies the excluded accounts for conformance package deployment.`, }, "template_key": { @@ -90,7 +91,6 @@ func ResourceOrgAssignmentPackage() *schema.Resource { Elem: orgAssignmentPackageVarStructureSchema(), Optional: true, Computed: true, - ForceNew: true, Description: `Specifies the parameters of a conformance package.`, }, "owner_id": { @@ -192,7 +192,7 @@ func resourceOrgAssignmentPackageCreate(ctx context.Context, d *schema.ResourceD } if _, err = stateConf.WaitForStateContext(ctx); err != nil { - return diag.Errorf("error waiting for RMS organizational assignment Package (%s) to be created: %s", + return diag.Errorf("error waiting for RMS organizational assignment package (%s) to be created: %s", id, err) } @@ -200,7 +200,7 @@ func resourceOrgAssignmentPackageCreate(ctx context.Context, d *schema.ResourceD } func buildCreateOrgAssignmentPackageBodyParams(d *schema.ResourceData) (map[string]interface{}, error) { - varsStructure, err := buildCreateOrgAssignmentPackageRequestBodyVarStructure(d.Get("vars_structure")) + varsStructure, err := buildOrgAssignmentPackageRequestBodyVarStructure(d.Get("vars_structure")) if err != nil { return nil, err } @@ -216,7 +216,7 @@ func buildCreateOrgAssignmentPackageBodyParams(d *schema.ResourceData) (map[stri return bodyParams, nil } -func buildCreateOrgAssignmentPackageRequestBodyVarStructure(rawParams interface{}) ([]map[string]interface{}, error) { +func buildOrgAssignmentPackageRequestBodyVarStructure(rawParams interface{}) ([]map[string]interface{}, error) { rawArray := rawParams.(*schema.Set).List() if len(rawArray) == 0 { return nil, nil @@ -307,6 +307,73 @@ func flattenGetOrgAssignmentPackageResponseBodyVarStructure(resp interface{}) [] return rst } +func resourceOrgAssignmentPackageUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + cfg := meta.(*config.Config) + region := cfg.GetRegion(d) + + // updateOrgAssignmentPackage: Update an existing RMS organizational assignment package + var ( + updateOrgAssignmentPackageHttpUrl = "v1/resource-manager/organizations/{organization_id}/conformance-packs/{conformance_pack_id}" + updateOrgAssignmentPackageProduct = "rms" + conformancePackId = d.Id() + ) + updateOrgAssignmentPackageClient, err := cfg.NewServiceClient(updateOrgAssignmentPackageProduct, region) + if err != nil { + return diag.Errorf("error creating Config client: %s", err) + } + + updateOrgAssignmentPackagePath := updateOrgAssignmentPackageClient.Endpoint + updateOrgAssignmentPackageHttpUrl + updateOrgAssignmentPackagePath = strings.ReplaceAll(updateOrgAssignmentPackagePath, "{organization_id}", + d.Get("organization_id").(string)) + updateOrgAssignmentPackagePath = strings.ReplaceAll(updateOrgAssignmentPackagePath, "{conformance_pack_id}", conformancePackId) + + updateOrgAssignmentPackageOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + } + + updateOpts, err := buildUpdateOrgAssignmentPackageBodyParams(d) + if err != nil { + return diag.FromErr(err) + } + + updateOrgAssignmentPackageOpt.JSONBody = utils.RemoveNil(updateOpts) + _, err = updateOrgAssignmentPackageClient.Request("PUT", + updateOrgAssignmentPackagePath, &updateOrgAssignmentPackageOpt) + if err != nil { + return diag.Errorf("error updating RMS organizational assignment package: %s", err) + } + + stateConf := &resource.StateChangeConf{ + Target: []string{"UPDATE_SUCCESSFUL", "ROLLBACK_SUCCESSFUL"}, + Pending: []string{"UPDATE_IN_PROGRESS"}, + Refresh: refreshDeployStatus(d, updateOrgAssignmentPackageClient), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 10 * time.Second, + PollInterval: 10 * time.Second, + } + + if _, err = stateConf.WaitForStateContext(ctx); err != nil { + return diag.Errorf("error waiting for RMS organizational assignment package (%s) to be updated: %s", + conformancePackId, err) + } + + return resourceOrgAssignmentPackageRead(ctx, d, meta) +} + +func buildUpdateOrgAssignmentPackageBodyParams(d *schema.ResourceData) (map[string]interface{}, error) { + varsStructure, err := buildOrgAssignmentPackageRequestBodyVarStructure(d.Get("vars_structure")) + if err != nil { + return nil, err + } + + bodyParams := map[string]interface{}{ + "name": d.Get("name"), + "excluded_accounts": utils.ValueIgnoreEmpty(d.Get("excluded_accounts")), + "vars_structure": varsStructure, + } + return bodyParams, nil +} + func resourceOrgAssignmentPackageDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { cfg := meta.(*config.Config) region := cfg.GetRegion(d) @@ -346,7 +413,7 @@ func resourceOrgAssignmentPackageDelete(ctx context.Context, d *schema.ResourceD } if _, err = stateConf.WaitForStateContext(ctx); err != nil { - return diag.Errorf("error waiting for RMS organizational assignment Package (%s) to be deleted: %s", d.Id(), err) + return diag.Errorf("error waiting for RMS organizational assignment package (%s) to be deleted: %s", d.Id(), err) } return nil @@ -356,7 +423,7 @@ func refreshDeployStatus(d *schema.ResourceData, client *golangsdk.ServiceClient return func() (result interface{}, state string, err error) { // getDeployStatus: Query the RMS organizational assignment package var ( - getDeployStatusHttpUrl = "v1/resource-manager/organizations/{organization_id}/conformance-packs/detailed-statuses" + getDeployStatusHttpUrl = "v1/resource-manager/organizations/{organization_id}/conformance-packs/statuses" ) getDeployStatusPath := client.Endpoint + getDeployStatusHttpUrl @@ -378,8 +445,7 @@ func refreshDeployStatus(d *schema.ResourceData, client *golangsdk.ServiceClient if err != nil { return nil, "", err } - return getDeployStatusRespBody, utils.PathSearch(fmt.Sprintf("statuses[?conformance_pack_name=='%s']|[0].state", - conformancePackName), getDeployStatusRespBody, "").(string), nil + return getDeployStatusRespBody, utils.PathSearch("statuses|[0].state", getDeployStatusRespBody, "").(string), nil } }