diff --git a/go.mod b/go.mod index 457f8b66..b769c375 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Cox-Automotive/terraform-provider-alks go 1.18 require ( - github.com/Cox-Automotive/alks-go v0.0.0-20221026220646-c20da5c3cb3a + github.com/Cox-Automotive/alks-go v0.0.0-20230724175933-0e9cb0a59b55 github.com/aws/aws-sdk-go v1.42.18 github.com/hashicorp/awspolicyequivalence v1.6.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.21.0 diff --git a/go.sum b/go.sum index 4fa646f8..dddeed22 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Cox-Automotive/alks-go v0.0.0-20221026220646-c20da5c3cb3a h1:EJJqr3Dg89DRi7eqtD9OX0XOqabndVqK0r9cy8Y7aq0= -github.com/Cox-Automotive/alks-go v0.0.0-20221026220646-c20da5c3cb3a/go.mod h1:jJNgXthl59Vt2tJHSC3WZ0vlopV9xqdclfQuLgwHjOw= +github.com/Cox-Automotive/alks-go v0.0.0-20230724175933-0e9cb0a59b55 h1:aIZaqUtNC6gZISs1VMWo6/jhlOjJcdbkoEqQHagxfWU= +github.com/Cox-Automotive/alks-go v0.0.0-20230724175933-0e9cb0a59b55/go.mod h1:jJNgXthl59Vt2tJHSC3WZ0vlopV9xqdclfQuLgwHjOw= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= diff --git a/resource_alks_iamrole.go b/resource_alks_iamrole.go index ddfb85bf..192facdf 100644 --- a/resource_alks_iamrole.go +++ b/resource_alks_iamrole.go @@ -50,7 +50,7 @@ func resourceAlksIamRole() *schema.Resource { "assume_role_policy": { Type: schema.TypeString, Optional: true, - ForceNew: true, + ForceNew: false, ExactlyOneOf: []string{"assume_role_policy", "type"}, DiffSuppressFunc: SuppressEquivalentTrustPolicyDiffs, }, @@ -249,6 +249,13 @@ func resourceAlksIamRoleRead(ctx context.Context, d *schema.ResourceData, meta i func resourceAlksIamRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { log.Printf("[INFO] ALKS IAM Role Update") + providerStruct := meta.(*AlksClient) + client := providerStruct.client + + if err := validateIAMEnabled(client); err != nil { + return diag.FromErr(err) + } + // enable partial state mode d.Partial(true) @@ -259,11 +266,44 @@ func resourceAlksIamRoleUpdate(ctx context.Context, d *schema.ResourceData, meta } } + //Do a read to get existing tags. If any of those are in ignore_tags, then they are externally managed + //and they should be included in the update so they don't get removed. + foundRole, err := client.GetIamRole(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + options := alks.UpdateIamRoleRequest{ + RoleName: &foundRole.RoleName, + } + if d.HasChange("tags_all") { - // try updating enable_alks_access - if err := updateIamRoleTags(d, meta); err != nil { + + existingTags := tagSliceToMap(foundRole.Tags) + externalTags := getExternalyManagedTags(existingTags, *providerStruct.ignoreTags) + internalTags := d.Get("tags_all").(map[string]interface{}) + + //Tags includes default tags, role specific tags, and tags that exist externally on the role itself and are specified in ignored_tags + tags := tagMapToSlice(combineTagMaps(internalTags, externalTags)) + + options.Tags = &tags + + } + + if d.HasChange("assume_role_policy") { + // try updating assume_role_policy + trustPolicyString := d.Get("assume_role_policy").(string) + trustPolicy := new(map[string]interface{}) + err := json.Unmarshal([]byte(trustPolicyString), trustPolicy) + if err != nil { return diag.FromErr(err) } + options.TrustPolicy = trustPolicy + } + + if _, err := client.UpdateIamRole(&options); err != nil { + return diag.FromErr(err) } d.Partial(false) diff --git a/resource_alks_iamrole_test.go b/resource_alks_iamrole_test.go index aa4bfe1a..421f9fc8 100644 --- a/resource_alks_iamrole_test.go +++ b/resource_alks_iamrole_test.go @@ -9,6 +9,7 @@ import ( "github.com/Cox-Automotive/alks-go" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + awspolicy "github.com/hashicorp/awspolicyequivalence" ) func TestAccAlksIamRole_Basic(t *testing.T) { @@ -141,6 +142,59 @@ func TestAccAlksIamRole_DefaultTags_TrustPolicy(t *testing.T) { }) } +func TestAccAlksIamRole_DefaultTags_TrustPolicyUpdate(t *testing.T) { + var resp alks.IamRoleResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAlksIamRoleDestroy(&resp), + Steps: []resource.TestStep{ + { + // create resource with tags + Config: testAccCheckAlksIamRoleUpdateWithTagsWithDefault_TrustPolicy, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "alks_iamrole.foo", "name", "bar430"), + resource.TestCheckResourceAttr( + "alks_iamrole.foo", "tags_all.defaultTagKey2", "defaultTagValue2"), + resource.TestCheckResourceAttr( + "alks_iamrole.foo", "tags.testKey1", "testValue1"), + ), + }, + { + // update resource with tags + Config: testAccCheckAlksIamRoleUpdateWithTagsWithDefault_TrustPolicyUpdate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "alks_iamrole.foo", "name", "bar430"), + resource.TestCheckResourceAttr( + "alks_iamrole.foo", "tags.testKey4", "testValue4"), + resource.TestCheckResourceAttr( + "alks_iamrole.foo", "tags_all.defaultTagKey1", "defaultTagValue1"), + // Check the Assume Role Policy after the update + testCheckAssumeRolePolicy("assume_role_policy", expectedAssumeRolePolicyAfterUpdate), + ), + }, + }, + }) +} + +func testCheckAssumeRolePolicy(attr, expected string) resource.TestCheckFunc { + return func(s *terraform.State) error { + actual := s.RootModule().Resources["alks_iamrole.foo"].Primary.Attributes[attr] + equivalent, err := awspolicy.PoliciesAreEquivalent(actual, expected) + if err != nil { + return fmt.Errorf("Unexpected error %s occured while comparing policies %s, and %s", err, expected, actual) + } + if !equivalent { + return fmt.Errorf("Expected %s to be %s, got %s", attr, expected, actual) + } + return nil + + } +} + func TestAccAlksIamRole_DefaultTags_RoleType(t *testing.T) { var resp alks.IamRoleResponse @@ -768,6 +822,49 @@ const testAccCheckAlksIamRoleUpdateWithTagsWithDefault_TrustPolicy = ` } ` +const testAccCheckAlksIamRoleUpdateWithTagsWithDefault_TrustPolicyUpdate = ` + provider "alks" { + default_tags { + tags = { + defaultTagKey1 = "defaultTagValue1" + } + } + } + resource "alks_iamrole" "foo" { + name = "bar430" + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) + include_default_policies = false + tags = { + testKey1 = "testValue1" + testKey4 = "testValue4" + } + } +` + +const expectedAssumeRolePolicyAfterUpdate = `{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] +}` + const testAccCheckAlksIamRoleUpdateWithTags = ` resource "alks_iamrole" "foo" { name = "bar430" diff --git a/vendor/github.com/Cox-Automotive/alks-go/iam_role.go b/vendor/github.com/Cox-Automotive/alks-go/iam_role.go index 000c0f33..e8accd88 100644 --- a/vendor/github.com/Cox-Automotive/alks-go/iam_role.go +++ b/vendor/github.com/Cox-Automotive/alks-go/iam_role.go @@ -360,8 +360,9 @@ func (c *Client) CreateIamTrustRole(options *CreateIamRoleOptions) (*IamRoleResp } type UpdateIamRoleRequest struct { - RoleName *string `json:"roleName"` - Tags *[]Tag `json:"tags"` + RoleName *string `json:"roleName"` + Tags *[]Tag `json:"tags"` + TrustPolicy *map[string]interface{} `json:"trustPolicy"` } type UpdateIamRoleResponse struct { @@ -375,8 +376,7 @@ type UpdateIamRoleResponse struct { Tags *[]Tag `json:"tags"` } -/* UpdateIamRole adds resource tags to an existing IAM role. - */ +// Updates an IAM role with the given options. func (c *Client) UpdateIamRole(options *UpdateIamRoleRequest) (*UpdateIamRoleResponse, *AlksError) { if err := options.updateIamRoleValidate(); err != nil { return nil, &AlksError{ @@ -385,7 +385,14 @@ func (c *Client) UpdateIamRole(options *UpdateIamRoleRequest) (*UpdateIamRoleRes Err: err, } } - log.Printf("[INFO] update IAM role %s with Tags: %v", *options.RoleName, *options.Tags) + // considering a non empty tag object + if options.Tags != nil { + log.Printf("[INFO] update IAM role %s with tags: %v", *options.RoleName, *options.Tags) + } + // considering a non empty TrustPolicy map + if options.TrustPolicy != nil { + log.Printf("[INFO] update IAM role %s with trust policy: %v", *options.RoleName, *options.TrustPolicy) + } b, err := json.Marshal(struct { UpdateIamRoleRequest @@ -466,9 +473,6 @@ func (req *UpdateIamRoleRequest) updateIamRoleValidate() error { if req.RoleName == nil { return fmt.Errorf("roleName option must not be nil") } - if req.Tags == nil { - return fmt.Errorf("tags option must not be nil") - } return nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 5e7e48e3..87dd5d13 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/Cox-Automotive/alks-go v0.0.0-20221026220646-c20da5c3cb3a +# github.com/Cox-Automotive/alks-go v0.0.0-20230724175933-0e9cb0a59b55 ## explicit; go 1.16 github.com/Cox-Automotive/alks-go # github.com/agext/levenshtein v1.2.2