Skip to content

Commit f71fdb5

Browse files
authored
Merge pull request #4079 from Skarlso/fix-cf-resource-delete-order
bug: order of deleting cloud formation resources matters and fix missing GroupName setting from Bootstrap user
2 parents 7ff580a + 2d45d45 commit f71fdb5

File tree

5 files changed

+108
-65
lines changed

5 files changed

+108
-65
lines changed

cmd/clusterawsadm/api/bootstrap/v1beta1/defaults.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20-
runtime "k8s.io/apimachinery/pkg/runtime"
20+
"k8s.io/apimachinery/pkg/runtime"
2121
"k8s.io/utils/pointer"
2222

2323
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
@@ -27,6 +27,8 @@ import (
2727
const (
2828
// DefaultBootstrapUserName is the default bootstrap user name.
2929
DefaultBootstrapUserName = "bootstrapper.cluster-api-provider-aws.sigs.k8s.io"
30+
// DefaultBootstrapGroupName is the default bootstrap user name.
31+
DefaultBootstrapGroupName = "bootstrapper.cluster-api-provider-aws.sigs.k8s.io"
3032
// DefaultStackName is the default CloudFormation stack name.
3133
DefaultStackName = "cluster-api-provider-aws-sigs-k8s-io"
3234
// DefaultPartitionName is the default security partition for AWS ARNs.
@@ -43,8 +45,13 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error {
4345

4446
// SetDefaults_BootstrapUser is used by defaulter-gen.
4547
func SetDefaults_BootstrapUser(obj *BootstrapUser) { //nolint:golint,stylecheck
46-
if obj != nil && obj.UserName == "" {
47-
obj.UserName = DefaultBootstrapUserName
48+
if obj != nil {
49+
if obj.UserName == "" {
50+
obj.UserName = DefaultBootstrapUserName
51+
}
52+
if obj.GroupName == "" {
53+
obj.GroupName = DefaultBootstrapGroupName
54+
}
4855
}
4956
}
5057

cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_bootstrap_user.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
AWSTemplateFormatVersion: 2010-09-09
22
Resources:
33
AWSIAMGroupBootstrapper:
4-
Properties: {}
4+
Properties:
5+
GroupName: bootstrapper.cluster-api-provider-aws.sigs.k8s.io
56
Type: AWS::IAM::Group
67
AWSIAMInstanceProfileControlPlane:
78
Properties:

cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_custom_bootstrap_user.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
AWSTemplateFormatVersion: 2010-09-09
22
Resources:
33
AWSIAMGroupBootstrapper:
4-
Properties: {}
4+
Properties:
5+
GroupName: bootstrapper.cluster-api-provider-aws.sigs.k8s.io
56
Type: AWS::IAM::Group
67
AWSIAMInstanceProfileControlPlane:
78
Properties:

cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_extra_statements.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
AWSTemplateFormatVersion: 2010-09-09
22
Resources:
33
AWSIAMGroupBootstrapper:
4-
Properties: {}
4+
Properties:
5+
GroupName: bootstrapper.cluster-api-provider-aws.sigs.k8s.io
56
Type: AWS::IAM::Group
67
AWSIAMInstanceProfileControlPlane:
78
Properties:

test/e2e/shared/aws.go

Lines changed: 92 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -469,73 +469,104 @@ func SetMultitenancyEnvVars(prov client.ConfigProvider) error {
469469
func deleteResourcesInCloudFormation(prov client.ConfigProvider, t *cfn_bootstrap.Template) {
470470
iamSvc := iam.New(prov)
471471
temp := *renderCustomCloudFormation(t)
472+
var (
473+
iamRoles []*cfn_iam.Role
474+
instanceProfiles []*cfn_iam.InstanceProfile
475+
policies []*cfn_iam.ManagedPolicy
476+
groups []*cfn_iam.Group
477+
)
478+
// the deletion order of these resources is important. Policies need to be last,
479+
// so they don't have any attached resources which prevents their deletion.
480+
// temp.Resources is a map. Traversing that directly results in undetermined order.
472481
for _, val := range temp.Resources {
473-
By(fmt.Sprintf("deleting the following resource: %s", val.AWSCloudFormationType()))
474-
tayp := val.AWSCloudFormationType()
475-
if tayp == configservice.ResourceTypeAwsIamRole {
482+
switch val.AWSCloudFormationType() {
483+
case configservice.ResourceTypeAwsIamRole:
476484
role := val.(*cfn_iam.Role)
477-
By(fmt.Sprintf("cleanup for role with name '%s'", role.RoleName))
478-
// added repeat to not keep flooding the logs.
479-
repeat := false
480-
Eventually(func(gomega Gomega) bool {
481-
_, err := iamSvc.DeleteRole(&iam.DeleteRoleInput{RoleName: aws.String(role.RoleName)})
482-
if err != nil && !repeat {
483-
By(fmt.Sprintf("failed to delete role '%s'; reason: %+v", role.RoleName, err))
484-
repeat = true
485-
}
486-
code, ok := awserrors.Code(err)
487-
return err == nil || (ok && code == iam.ErrCodeNoSuchEntityException)
488-
}, 5*time.Minute, 5*time.Second).Should(BeTrue())
489-
}
490-
if val.AWSCloudFormationType() == "AWS::IAM::InstanceProfile" {
485+
iamRoles = append(iamRoles, role)
486+
case "AWS::IAM::InstanceProfile":
491487
profile := val.(*cfn_iam.InstanceProfile)
492-
By(fmt.Sprintf("cleanup for profile with name '%s'", profile.InstanceProfileName))
493-
repeat := false
494-
Eventually(func(gomega Gomega) bool {
495-
_, err := iamSvc.DeleteInstanceProfile(&iam.DeleteInstanceProfileInput{InstanceProfileName: aws.String(profile.InstanceProfileName)})
496-
if err != nil && !repeat {
497-
By(fmt.Sprintf("failed to delete role '%s'; reason: %+v", profile.InstanceProfileName, err))
498-
repeat = true
499-
}
500-
code, ok := awserrors.Code(err)
501-
return err == nil || (ok && code == iam.ErrCodeNoSuchEntityException)
502-
}, 5*time.Minute, 5*time.Second).Should(BeTrue())
503-
}
504-
if val.AWSCloudFormationType() == "AWS::IAM::ManagedPolicy" {
488+
instanceProfiles = append(instanceProfiles, profile)
489+
case "AWS::IAM::ManagedPolicy":
505490
policy := val.(*cfn_iam.ManagedPolicy)
506-
policies, err := iamSvc.ListPolicies(&iam.ListPoliciesInput{})
507-
Expect(err).NotTo(HaveOccurred())
508-
if len(policies.Policies) > 0 {
509-
for _, p := range policies.Policies {
510-
if aws.StringValue(p.PolicyName) == policy.ManagedPolicyName {
511-
By(fmt.Sprintf("cleanup for policy '%s'", p.String()))
512-
repeat := false
513-
Eventually(func(gomega Gomega) bool {
514-
_, err := iamSvc.DeletePolicy(&iam.DeletePolicyInput{PolicyArn: p.Arn})
515-
if err != nil && !repeat {
516-
By(fmt.Sprintf("failed to delete policy '%s'; reason: %+v", policy.Description, err))
517-
repeat = true
518-
}
519-
code, ok := awserrors.Code(err)
520-
return err == nil || (ok && code == iam.ErrCodeNoSuchEntityException)
521-
}, 5*time.Minute, 5*time.Second).Should(BeTrue())
522-
// TODO: why is there a break here? Don't we want to clean up everything?
523-
break
524-
}
491+
policies = append(policies, policy)
492+
case configservice.ResourceTypeAwsIamGroup:
493+
group := val.(*cfn_iam.Group)
494+
groups = append(groups, group)
495+
}
496+
}
497+
for _, role := range iamRoles {
498+
By(fmt.Sprintf("deleting the following role: %s", role.RoleName))
499+
repeat := false
500+
Eventually(func(gomega Gomega) bool {
501+
err := DeleteRole(prov, role.RoleName)
502+
if err != nil && !repeat {
503+
By(fmt.Sprintf("failed to delete role '%s'; reason: %+v", role.RoleName, err))
504+
repeat = true
505+
}
506+
code, ok := awserrors.Code(err)
507+
return err == nil || (ok && code == iam.ErrCodeNoSuchEntityException)
508+
}, 5*time.Minute, 5*time.Second).Should(BeTrue())
509+
}
510+
for _, profile := range instanceProfiles {
511+
By(fmt.Sprintf("cleanup for profile with name '%s'", profile.InstanceProfileName))
512+
repeat := false
513+
Eventually(func(gomega Gomega) bool {
514+
_, err := iamSvc.DeleteInstanceProfile(&iam.DeleteInstanceProfileInput{InstanceProfileName: aws.String(profile.InstanceProfileName)})
515+
if err != nil && !repeat {
516+
By(fmt.Sprintf("failed to delete role '%s'; reason: %+v", profile.InstanceProfileName, err))
517+
repeat = true
518+
}
519+
code, ok := awserrors.Code(err)
520+
return err == nil || (ok && code == iam.ErrCodeNoSuchEntityException)
521+
}, 5*time.Minute, 5*time.Second).Should(BeTrue())
522+
}
523+
for _, group := range groups {
524+
repeat := false
525+
Eventually(func(gomega Gomega) bool {
526+
_, err := iamSvc.DeleteGroup(&iam.DeleteGroupInput{GroupName: aws.String(group.GroupName)})
527+
if err != nil && !repeat {
528+
By(fmt.Sprintf("failed to delete group '%s'; reason: %+v", group.GroupName, err))
529+
repeat = true
530+
}
531+
code, ok := awserrors.Code(err)
532+
return err == nil || (ok && code == iam.ErrCodeNoSuchEntityException)
533+
}, 5*time.Minute, 5*time.Second).Should(BeTrue())
534+
}
535+
for _, policy := range policies {
536+
policies, err := iamSvc.ListPolicies(&iam.ListPoliciesInput{})
537+
Expect(err).NotTo(HaveOccurred())
538+
if len(policies.Policies) > 0 {
539+
for _, p := range policies.Policies {
540+
if aws.StringValue(p.PolicyName) == policy.ManagedPolicyName {
541+
By(fmt.Sprintf("cleanup for policy '%s'", p.String()))
542+
repeat := false
543+
Eventually(func(gomega Gomega) bool {
544+
response, err := iamSvc.DeletePolicy(&iam.DeletePolicyInput{
545+
PolicyArn: p.Arn,
546+
})
547+
if err != nil && !repeat {
548+
By(fmt.Sprintf("failed to delete policy '%s'; reason: %+v, response: %s", policy.Description, err, response.String()))
549+
repeat = true
550+
}
551+
code, ok := awserrors.Code(err)
552+
return err == nil || (ok && code == iam.ErrCodeNoSuchEntityException)
553+
}, 5*time.Minute, 5*time.Second).Should(BeTrue())
554+
// TODO: why is there a break here? Don't we want to clean up everything?
555+
break
525556
}
526557
}
527558
}
528-
if val.AWSCloudFormationType() == configservice.ResourceTypeAwsIamGroup {
529-
group := val.(*cfn_iam.Group)
530-
_, _ = iamSvc.DeleteGroup(&iam.DeleteGroupInput{GroupName: aws.String(group.GroupName)})
531-
}
532559
}
533560
}
534561

535562
// TODO: remove once test infra accounts are fixed.
536563
func deleteMultitenancyRoles(prov client.ConfigProvider) {
537-
DeleteRole(prov, "multi-tenancy-role")
538-
DeleteRole(prov, "multi-tenancy-nested-role")
564+
if err := DeleteRole(prov, "multi-tenancy-role"); err != nil {
565+
By(fmt.Sprintf("failed to delete role multi-tenancy-role %s", err))
566+
}
567+
if err := DeleteRole(prov, "multi-tenancy-nested-role"); err != nil {
568+
By(fmt.Sprintf("failed to delete role multi-tenancy-nested-role %s", err))
569+
}
539570
}
540571

541572
// detachAllPoliciesForRole detaches all policies for role.
@@ -564,23 +595,25 @@ func detachAllPoliciesForRole(prov client.ConfigProvider, name string) error {
564595
}
565596

566597
// DeleteRole deletes roles in a best effort manner.
567-
func DeleteRole(prov client.ConfigProvider, name string) {
598+
func DeleteRole(prov client.ConfigProvider, name string) error {
568599
iamSvc := iam.New(prov)
569600

570601
// if role does not exist, return.
571602
_, err := iamSvc.GetRole(&iam.GetRoleInput{RoleName: aws.String(name)})
572603
if err != nil {
573-
return
604+
return err
574605
}
575606

576607
if err := detachAllPoliciesForRole(prov, name); err != nil {
577-
return
608+
return err
578609
}
579610

580611
_, err = iamSvc.DeleteRole(&iam.DeleteRoleInput{RoleName: aws.String(name)})
581612
if err != nil {
582-
return
613+
return err
583614
}
615+
616+
return nil
584617
}
585618

586619
func GetPolicyArn(prov client.ConfigProvider, name string) string {

0 commit comments

Comments
 (0)