diff --git a/ibm/acctest/acctest.go b/ibm/acctest/acctest.go index 410539545e..f04f347419 100644 --- a/ibm/acctest/acctest.go +++ b/ibm/acctest/acctest.go @@ -394,6 +394,7 @@ var ( // For IAM Access Management var ( TargetAccountId string + TargetEnterpriseId string ) // For Partner Center Sell @@ -1870,6 +1871,12 @@ func init() { fmt.Println("[INFO] Set the environment variable IBM_POLICY_ASSIGNMENT_TARGET_ACCOUNT_ID for testing ibm_iam_policy_assignment resource else tests will fail if this is not set correctly") } + + TargetEnterpriseId = os.Getenv("IBM_POLICY_ASSIGNMENT_TARGET_ENTERPRISE_ID") + if TargetEnterpriseId == "" { + fmt.Println("[INFO] Set the environment variable IBM_POLICY_ASSIGNMENT_TARGET_ENTERPRISE_ID for testing ibm_iam_policy_assignment resource else tests will fail if this is not set correctly") + } + PcsRegistrationAccountId = os.Getenv("PCS_REGISTRATION_ACCOUNT_ID") if PcsRegistrationAccountId == "" { fmt.Println("[WARN] Set the environment variable PCS_REGISTRATION_ACCOUNT_ID for testing iam_onboarding resource else tests will fail if this is not set correctly") diff --git a/ibm/service/iampolicy/data_source_ibm_iam_policy_assignment_test.go b/ibm/service/iampolicy/data_source_ibm_iam_policy_assignment_test.go index 4bcc33195a..96e7647359 100644 --- a/ibm/service/iampolicy/data_source_ibm_iam_policy_assignment_test.go +++ b/ibm/service/iampolicy/data_source_ibm_iam_policy_assignment_test.go @@ -64,12 +64,6 @@ resource "ibm_iam_policy_assignment" "policy_assignment" { id = "%s" } - options { - root { - requester_id = "orchestrator" - assignment_id = "test" - } - } templates{ id = ibm_iam_policy_template.policy_s2s_template.template_id version = ibm_iam_policy_template.policy_s2s_template.version diff --git a/ibm/service/iampolicy/resource_ibm_iam_policy_assignment.go b/ibm/service/iampolicy/resource_ibm_iam_policy_assignment.go index 9072c9f76d..766c154505 100644 --- a/ibm/service/iampolicy/resource_ibm_iam_policy_assignment.go +++ b/ibm/service/iampolicy/resource_ibm_iam_policy_assignment.go @@ -7,8 +7,11 @@ import ( "context" "fmt" "log" + "strings" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns" @@ -17,6 +20,12 @@ import ( "github.com/IBM/platform-services-go-sdk/iampolicymanagementv1" ) +const ( + InProgress = "in_progress" + complete = "complete" + failed = "failed" +) + func ResourceIBMIAMPolicyAssignment() *schema.Resource { return &schema.Resource{ CreateContext: resourceIBMPolicyAssignmentCreate, @@ -24,7 +33,11 @@ func ResourceIBMIAMPolicyAssignment() *schema.Resource { UpdateContext: resourceIBMPolicyAssignmentUpdate, DeleteContext: resourceIBMPolicyAssignmentDelete, Importer: &schema.ResourceImporter{}, - + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, Schema: map[string]*schema.Schema{ "version": { Type: schema.TypeString, @@ -251,9 +264,9 @@ func resourceIBMPolicyAssignmentCreate(context context.Context, d *schema.Resour createPolicyTemplateAssignmentOptions := &iampolicymanagementv1.CreatePolicyTemplateAssignmentOptions{} createPolicyTemplateAssignmentOptions.SetVersion(d.Get("version").(string)) - targetModel, err := ResourceIBMPolicyAssignmentMapToAssignmentTargetDetails(d.Get("target").(map[string]interface{})) - if err != nil { - return diag.FromErr(err) + targetModel, diags := GetTargetModel(d) + if diags.HasError() { + return diags } createPolicyTemplateAssignmentOptions.SetTarget(targetModel) var templates []iampolicymanagementv1.AssignmentTemplateDetails @@ -278,6 +291,14 @@ func resourceIBMPolicyAssignmentCreate(context context.Context, d *schema.Resour d.SetId(*policyAssignmentV1Collection.Assignments[0].ID) + if targetModel.Type != nil && (*targetModel.Type == "Account") { + log.Printf("[DEBUG] Skipping waitForAssignment for target type: %s", *targetModel.Type) + } else { + _, err = waitForAssignment(d.Timeout(schema.TimeoutCreate), meta, d, isAccessPolicyAssigned) + if err != nil { + return diag.FromErr(fmt.Errorf("error assigning: %s", err)) + } + } return resourceIBMPolicyAssignmentRead(context, d, meta) } @@ -385,6 +406,12 @@ func resourceIBMPolicyAssignmentUpdate(context context.Context, d *schema.Resour updatePolicyAssignmentOptions.SetAssignmentID(d.Id()) + targetModel, diags := GetTargetModel(d) + + if diags.HasError() { + return diags + } + hasChange := false if d.HasChange("template_version") { @@ -416,6 +443,16 @@ func resourceIBMPolicyAssignmentUpdate(context context.Context, d *schema.Resour log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage()) return tfErr.GetDiag() } + + if targetModel.Type != nil && (*targetModel.Type == "Account") { + log.Printf("[DEBUG] Skipping waitForAssignment for target type: %s", *targetModel.Type) + } else { + _, err = waitForAssignment(d.Timeout(schema.TimeoutUpdate), meta, d, isAccessPolicyAssigned) + if err != nil { + return diag.FromErr(fmt.Errorf("error assigning: %s", err)) + } + } + } return resourceIBMPolicyAssignmentRead(context, d, meta) @@ -433,11 +470,34 @@ func resourceIBMPolicyAssignmentDelete(context context.Context, d *schema.Resour deletePolicyAssignmentOptions.SetAssignmentID(d.Id()) - _, err = iamPolicyManagementClient.DeletePolicyAssignmentWithContext(context, deletePolicyAssignmentOptions) + + response, err := iamPolicyManagementClient.DeletePolicyAssignmentWithContext(context, deletePolicyAssignmentOptions) if err != nil { - tfErr := flex.TerraformErrorf(err, fmt.Sprintf("DeletePolicyAssignmentWithContext failed: %s", err.Error()), "ibm_policy_assignment", "delete") - log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage()) - return tfErr.GetDiag() + if response != nil && response.StatusCode == 404 { + return nil + } else { + tfErr := flex.TerraformErrorf(err, fmt.Sprintf("DeletePolicyAssignmentWithContext failed: %s", err.Error()), "ibm_policy_assignment", "delete") + log.Printf("[DEBUG]\n%s", tfErr.GetDebugMessage()) + return tfErr.GetDiag() + } + } + + targetModel, diags := GetTargetModel(d) + if diags.HasError() { + return diags + } + + if targetModel.Type != nil && (*targetModel.Type == "Account") { + log.Printf("[DEBUG] Skipping waitForAssignment for target type: %s", *targetModel.Type) + } else { + _, err = waitForAssignment(d.Timeout(schema.TimeoutDelete), meta, d, isAccessPolicyAssignedDeleted) + if err != nil { + if strings.Contains(err.Error(), "not found") { + return nil + } else { + return diag.FromErr(fmt.Errorf("error assigning: %s", err)) + } + } } d.SetId("") @@ -477,3 +537,121 @@ func ResourceIBMPolicyAssignmentAssignmentTemplateDetailsToMap(model *iampolicym } return modelMap, nil } + +func GetTargetModel(d *schema.ResourceData) (*iampolicymanagementv1.AssignmentTargetDetails, diag.Diagnostics) { + targetModel, err := ResourceIBMPolicyAssignmentMapToAssignmentTargetDetails(d.Get("target").(map[string]interface{})) + + if err != nil { + return targetModel, diag.FromErr(err) + } + + return targetModel, nil +} + + +func waitForAssignment(timeout time.Duration, meta interface{}, d *schema.ResourceData, refreshFn func(string, interface{}) resource.StateRefreshFunc) (interface{}, error) { + + stateConf := &resource.StateChangeConf{ + Pending: []string{InProgress}, + Target: []string{complete}, + Refresh: refreshFn(d.Id(), meta), + Delay: 30 * time.Second, + PollInterval: time.Minute, + Timeout: timeout, + } + + return stateConf.WaitForState() +} + +func isAccessPolicyAssigned(id string, meta interface{}) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + iamPolicyManagementClient, err := meta.(conns.ClientSession).IAMPolicyManagementV1API() + if err != nil { + return nil, failed, err + } + + getAssignmentPolicyOptions := &iampolicymanagementv1.GetPolicyAssignmentOptions{ + AssignmentID: core.StringPtr(id), + Version: core.StringPtr("1.0"), + } + + getAssignmentPolicyOptions.SetAssignmentID(id) + + assignmentDetails, response, err := iamPolicyManagementClient.GetPolicyAssignment(getAssignmentPolicyOptions) + + if err != nil { + if response != nil && response.StatusCode == 404 { + return nil, failed, err + } + return nil, failed, err + } + + assignment, ok := assignmentDetails.(*iampolicymanagementv1.GetPolicyAssignmentResponse) + + if !ok { + return nil, failed, fmt.Errorf("[ERROR] Type assertion failed for assignment details : %s", id) + } + + if assignment != nil { + if *assignment.Status == "accepted" || *assignment.Status == "in_progress" { + log.Printf("Assignment still in progress\n") + return assignment, InProgress, nil + } + + if *assignment.Status == "succeeded" { + return assignment, complete, nil + } + + if *assignment.Status == "failed" { + return assignment, failed, fmt.Errorf("[ERROR] The assignment %s did not complete successfully and has a 'failed' status. Please check assignment resource for detailed errors: %s\n", id, response) + } + } + + return assignment, failed, fmt.Errorf("[ERROR] Unexpected status reached for assignment %s.: %s\n", id, response) + } +} + +func isAccessPolicyAssignedDeleted(id string, meta interface{}) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + iamPolicyManagementClient, err := meta.(conns.ClientSession).IAMPolicyManagementV1API() + if err != nil { + return nil, failed, err + } + + getAssignmentPolicyOptions := &iampolicymanagementv1.GetPolicyAssignmentOptions{ + AssignmentID: core.StringPtr(id), + Version: core.StringPtr("1.0"), + } + + getAssignmentPolicyOptions.SetAssignmentID(id) + + assignmentDetails, response, err := iamPolicyManagementClient.GetPolicyAssignment(getAssignmentPolicyOptions) + + if err != nil { + if response != nil && response.StatusCode == 404 { + return nil, failed, err + } + return nil, failed, err + } + + assignment, ok := assignmentDetails.(*iampolicymanagementv1.GetPolicyAssignmentResponse) + + if !ok { + return nil, failed, fmt.Errorf("[ERROR] Type assertion failed for assignment details : %s", id) + } + + + if assignment != nil { + if *assignment.Status == "accepted" || *assignment.Status == "in_progress" { + log.Printf("Assignment still in progress\n") + return assignment, InProgress, nil + } + + if *assignment.Status == "failed" { + return assignment, failed, fmt.Errorf("[ERROR] The assignment %s did not complete successfully and has a 'failed' status. Please check assignment resource for detailed errors: %s\n", id, response) + } + } + + return assignment, failed, fmt.Errorf("[ERROR] Unexpected status reached for assignment %s.: %s\n", id, response) + } +} diff --git a/ibm/service/iampolicy/resource_ibm_iam_policy_assignment_test.go b/ibm/service/iampolicy/resource_ibm_iam_policy_assignment_test.go index 925fe756b1..d6da6ad589 100644 --- a/ibm/service/iampolicy/resource_ibm_iam_policy_assignment_test.go +++ b/ibm/service/iampolicy/resource_ibm_iam_policy_assignment_test.go @@ -46,6 +46,7 @@ func TestAccIBMPolicyAssignmentBasic(t *testing.T) { }) } + func TestAccIBMPolicyAssignmentS2SBasic(t *testing.T) { var conf iampolicymanagementv1.GetPolicyAssignmentResponse var name string = fmt.Sprintf("TerraformTemplateTest%d", acctest.RandIntRange(10, 100)) @@ -70,6 +71,27 @@ func TestAccIBMPolicyAssignmentS2SBasic(t *testing.T) { }) } +func TestAccIBMPolicyAssignmentEnterprise(t *testing.T) { + var conf iampolicymanagementv1.GetPolicyAssignmentResponse + var name string = fmt.Sprintf("TerraformTemplateTest%d", acctest.RandIntRange(10, 100)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMPolicyAssignmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMPolicyAssignmentConfigEnterprise(name, acc.TargetEnterpriseId), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMPolicyAssignmentExists("ibm_iam_policy_assignment.policy_assignment", conf), + resource.TestCheckResourceAttr("ibm_iam_policy_template.policy_s2s_template", "name", name), + resource.TestCheckResourceAttr("ibm_iam_policy_template.policy_s2s_template", "policy.0.resource.0.attributes.0.value", resourceServiceName), + resource.TestCheckResourceAttr("ibm_iam_policy_template.policy_s2s_template", "policy.0.subject.0.attributes.0.value", "compliance"), + ), + }, + }, + }) +} + func testAccCheckIBMPolicyAssignmentConfigBasic(name string, targetId string) string { return fmt.Sprintf(` resource "ibm_iam_policy_template" "policy_s2s_template" { @@ -215,8 +237,46 @@ func testAccCheckIBMPolicyAssignmentConfigUpdate(name string, targetId string) s } templates{ - id = ibm_iam_policy_template.policy_s2stemplate.template_id - version = ibm_iam_policy_template.policy_s2stemplate.version + id = ibm_iam_policy_template_version.template_version.template_id + version = ibm_iam_policy_template_version.template_version.version + } + }`, name, targetId) +} + +func testAccCheckIBMPolicyAssignmentConfigEnterprise(name string, targetId string) string { + return fmt.Sprintf(` + resource "ibm_iam_policy_template" "policy_s2s_template" { + name = "%s" + policy { + type = "authorization" + description = "description" + resource { + attributes { + key = "serviceName" + operator = "stringEquals" + value = "kms" + } + } + subject { + attributes { + key = "serviceName" + operator = "stringEquals" + value = "compliance" + } + } + roles = ["Reader"] + } + committed=true + } + resource "ibm_iam_policy_assignment" "policy_assignment" { + version = "1.0" + target ={ + type = "Enterprise" + id = "%s" + } + templates{ + id = ibm_iam_policy_template.policy_s2s_template.template_id + version = ibm_iam_policy_template.policy_s2s_template.version } }`, name, targetId) } diff --git a/website/docs/d/iam_policy_assignment.html.markdown b/website/docs/d/iam_policy_assignment.html.markdown index a309ddb4cf..19bfcbce60 100644 --- a/website/docs/d/iam_policy_assignment.html.markdown +++ b/website/docs/d/iam_policy_assignment.html.markdown @@ -19,6 +19,14 @@ data "ibm_iam_policy_assignments" "policy_assignment" { ## Argument Reference +## Timeouts section + +The resource includes default timeout settings for the following operations: + +* `create` - (Timeout) Defaults to 30 minutes. +* `update` - (Timeout) Defaults to 30 minutes. +* `delete` - (Timeout) Defaults to 30 minutes. + ## Attribute Reference diff --git a/website/docs/r/policy_assignment.html.markdown b/website/docs/r/policy_assignment.html.markdown index cea35cb8bc..f65e3c172b 100644 --- a/website/docs/r/policy_assignment.html.markdown +++ b/website/docs/r/policy_assignment.html.markdown @@ -73,6 +73,34 @@ resource "ibm_iam_policy_assignment" "policy_assignment" { } template_version=ibm_iam_policy_template_version.template_version.version } + +resource "ibm_iam_policy_assignment" "policy_assignment" { + version ="1.0" + target ={ + type = "Account Group" + id = "" + } + + templates{ + id = ibm_iam_policy_template.policy_s2s_template.template_id + version = ibm_iam_policy_template.policy_s2s_template.version + } + template_version=ibm_iam_policy_template_version.template_version.version +} + +resource "ibm_iam_policy_assignment" "policy_assignment" { + version ="1.0" + target ={ + type = "Enterprise" + id = "" + } + + templates{ + id = ibm_iam_policy_template.policy_s2s_template.template_id + version = ibm_iam_policy_template.policy_s2s_template.version + } + template_version=ibm_iam_policy_template_version.template_version.version +} ``` **Note**: Above configuration is to create policy template versions and assign to a target enterprise account. Update this parameter(***template_version***) and terraform apply again to update the assignment @@ -94,10 +122,18 @@ Nested schema for **target**: * `id` - (Required, String) ID of the target account. * Constraints: The maximum length is `32` characters. The minimum length is `1` character. The value must match regular expression `/^[A-Za-z0-9-]*$/`. * `type` - (Required, String) Assignment target type. - * Constraints: Allowable values are: `Account`. The maximum length is `30` characters. The minimum length is `1` character. + * Constraints: Allowable values are: `Account`, `Account Group` and `Enterprise`. The maximum length is `30` characters. The minimum length is `1` character. * `version` - (Required, String) specify version of response body format. * Constraints: Allowable values are: `1.0`. The minimum length is `1` character. +## Timeouts section + +The resource includes default timeout settings for the following operations: + +* `create` - (Timeout) Defaults to 30 minutes. +* `update` - (Timeout) Defaults to 30 minutes. +* `delete` - (Timeout) Defaults to 30 minutes. + ## Attribute Reference After your resource is created, you can read values from the listed arguments and the following attributes.