diff --git a/pkg/actions/iam/delete.go b/pkg/actions/iam/delete.go new file mode 100644 index 00000000000..bf877d14725 --- /dev/null +++ b/pkg/actions/iam/delete.go @@ -0,0 +1,29 @@ +package iam + +import ( + "fmt" + + "github.com/kris-nova/logger" + "github.com/weaveworks/eksctl/pkg/ctl/cmdutils" + "github.com/weaveworks/eksctl/pkg/kubernetes" +) + +func (m *Manager) Delete(serviceAccounts []string, plan, wait bool) error { + tasks, err := m.stackManager.NewTasksToDeleteIAMServiceAccounts(serviceAccounts, kubernetes.NewCachedClientSet(m.clientSet), wait) + if err != nil { + return err + } + tasks.PlanMode = plan + + logger.Info(tasks.Describe()) + if errs := tasks.DoAllSync(); len(errs) > 0 { + logger.Info("%d error(s) occurred and IAM Role stacks haven't been deleted properly, you may wish to check CloudFormation console", len(errs)) + for _, err := range errs { + logger.Critical("%s\n", err.Error()) + } + return fmt.Errorf("failed to delete iamserviceaccount(s)") + } + + cmdutils.LogPlanModeWarning(plan && len(serviceAccounts) > 0) + return nil +} diff --git a/pkg/actions/iam/delete_test.go b/pkg/actions/iam/delete_test.go new file mode 100644 index 00000000000..e7f1ad59964 --- /dev/null +++ b/pkg/actions/iam/delete_test.go @@ -0,0 +1,90 @@ +package iam_test + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudformation" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/weaveworks/eksctl/pkg/cfn/manager" + "github.com/weaveworks/eksctl/pkg/eks" + + "github.com/weaveworks/eksctl/pkg/actions/iam" + "github.com/weaveworks/eksctl/pkg/actions/iam/fakes" + api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + iamoidc "github.com/weaveworks/eksctl/pkg/iam/oidc" + "github.com/weaveworks/eksctl/pkg/testutils/mockprovider" +) + +var _ = Describe("Delete", func() { + + var ( + iamManager *iam.Manager + oidc *iamoidc.OpenIDConnectManager + fakeStackManager *fakes.FakeStackManager + mockProvider *mockprovider.MockProvider + serviceAccount []*api.ClusterIAMServiceAccount + ) + + BeforeEach(func() { + serviceAccount = []*api.ClusterIAMServiceAccount{ + { + ClusterIAMMeta: api.ClusterIAMMeta{ + Name: "test-sa", + Namespace: "default", + }, + AttachPolicyARNs: []string{"arn-123"}, + }, + } + var err error + + fakeStackManager = new(fakes.FakeStackManager) + mockProvider = mockprovider.NewMockProvider() + + oidc, err = iamoidc.NewOpenIDConnectManager(nil, "456123987123", "https://oidc.eks.us-west-2.amazonaws.com/id/A39A2842863C47208955D753DE205E6E", "aws") + Expect(err).ToNot(HaveOccurred()) + oidc.ProviderARN = "arn:aws:iam::456123987123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/A39A2842863C47208955D753DE205E6E" + iamManager = iam.New("my-cluster", &eks.ClusterProvider{Provider: mockProvider}, fakeStackManager, oidc, nil) + }) + + When("the IAMServiceAccount exists", func() { + It("deletes it", func() { + fakeStackManager.ListStacksMatchingReturns([]*cloudformation.Stack{ + { + StackName: aws.String("eksctl-my-cluster-addon-iamserviceaccount-default-test-sa"), + }, + }, nil) + + err := iamManager.Delete(serviceAccount, false) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeStackManager.ListStacksMatchingCallCount()).To(Equal(1)) + Expect(fakeStackManager.ListStacksMatchingArgsForCall(0)).To(Equal("eksctl-.*-addon-iamserviceaccount")) + Expect(fakeStackManager.UpdateStackCallCount()).To(Equal(1)) + fakeStackManager.UpdateStackArgsForCall(0) + stackName, changeSetName, description, templateData, _ := fakeStackManager.UpdateStackArgsForCall(0) + Expect(stackName).To(Equal("eksctl-my-cluster-addon-iamserviceaccount-default-test-sa")) + Expect(changeSetName).To(Equal("updating-policy")) + Expect(description).To(Equal("updating policies for IAMServiceAccount default/test-sa")) + Expect(err).NotTo(HaveOccurred()) + Expect(string(templateData.(manager.TemplateBody))).To(ContainSubstring("arn-123")) + Expect(string(templateData.(manager.TemplateBody))).To(ContainSubstring(":sub\":\"system:serviceaccount:default:test-sa")) + }) + + //When("in plan mode", func() { + // It("does not trigger a delete", func() { + // fakeStackManager.ListStacksMatchingReturns([]*cloudformation.Stack{ + // { + // StackName: aws.String("eksctl-my-cluster-addon-iamserviceaccount-default-test-sa"), + // }, + // }, nil) + // + // err := iamManager.UpdateIAMServiceAccounts(serviceAccount, true) + // Expect(err).NotTo(HaveOccurred()) + // + // Expect(fakeStackManager.ListStacksMatchingCallCount()).To(Equal(1)) + // Expect(fakeStackManager.ListStacksMatchingArgsForCall(0)).To(Equal("eksctl-.*-addon-iamserviceaccount")) + // Expect(fakeStackManager.UpdateStackCallCount()).To(Equal(0)) + // }) + //}) + }) +}) diff --git a/pkg/actions/iam/fakes/fake_stack_manager.go b/pkg/actions/iam/fakes/fake_stack_manager.go index e5d3dd86bd4..d33290b3b88 100644 --- a/pkg/actions/iam/fakes/fake_stack_manager.go +++ b/pkg/actions/iam/fakes/fake_stack_manager.go @@ -54,6 +54,21 @@ type FakeStackManager struct { newTasksToCreateIAMServiceAccountsReturnsOnCall map[int]struct { result1 *tasks.TaskTree } + NewTasksToDeleteIAMServiceAccountsStub func(func(string) bool, kubernetes.ClientSetGetter, bool) (*tasks.TaskTree, error) + newTasksToDeleteIAMServiceAccountsMutex sync.RWMutex + newTasksToDeleteIAMServiceAccountsArgsForCall []struct { + arg1 func(string) bool + arg2 kubernetes.ClientSetGetter + arg3 bool + } + newTasksToDeleteIAMServiceAccountsReturns struct { + result1 *tasks.TaskTree + result2 error + } + newTasksToDeleteIAMServiceAccountsReturnsOnCall map[int]struct { + result1 *tasks.TaskTree + result2 error + } UpdateStackStub func(string, string, string, manager.TemplateData, map[string]string) error updateStackMutex sync.RWMutex updateStackArgsForCall []struct { @@ -263,6 +278,72 @@ func (fake *FakeStackManager) NewTasksToCreateIAMServiceAccountsReturnsOnCall(i }{result1} } +func (fake *FakeStackManager) NewTasksToDeleteIAMServiceAccounts(arg1 func(string) bool, arg2 kubernetes.ClientSetGetter, arg3 bool) (*tasks.TaskTree, error) { + fake.newTasksToDeleteIAMServiceAccountsMutex.Lock() + ret, specificReturn := fake.newTasksToDeleteIAMServiceAccountsReturnsOnCall[len(fake.newTasksToDeleteIAMServiceAccountsArgsForCall)] + fake.newTasksToDeleteIAMServiceAccountsArgsForCall = append(fake.newTasksToDeleteIAMServiceAccountsArgsForCall, struct { + arg1 func(string) bool + arg2 kubernetes.ClientSetGetter + arg3 bool + }{arg1, arg2, arg3}) + stub := fake.NewTasksToDeleteIAMServiceAccountsStub + fakeReturns := fake.newTasksToDeleteIAMServiceAccountsReturns + fake.recordInvocation("NewTasksToDeleteIAMServiceAccounts", []interface{}{arg1, arg2, arg3}) + fake.newTasksToDeleteIAMServiceAccountsMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStackManager) NewTasksToDeleteIAMServiceAccountsCallCount() int { + fake.newTasksToDeleteIAMServiceAccountsMutex.RLock() + defer fake.newTasksToDeleteIAMServiceAccountsMutex.RUnlock() + return len(fake.newTasksToDeleteIAMServiceAccountsArgsForCall) +} + +func (fake *FakeStackManager) NewTasksToDeleteIAMServiceAccountsCalls(stub func(func(string) bool, kubernetes.ClientSetGetter, bool) (*tasks.TaskTree, error)) { + fake.newTasksToDeleteIAMServiceAccountsMutex.Lock() + defer fake.newTasksToDeleteIAMServiceAccountsMutex.Unlock() + fake.NewTasksToDeleteIAMServiceAccountsStub = stub +} + +func (fake *FakeStackManager) NewTasksToDeleteIAMServiceAccountsArgsForCall(i int) (func(string) bool, kubernetes.ClientSetGetter, bool) { + fake.newTasksToDeleteIAMServiceAccountsMutex.RLock() + defer fake.newTasksToDeleteIAMServiceAccountsMutex.RUnlock() + argsForCall := fake.newTasksToDeleteIAMServiceAccountsArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeStackManager) NewTasksToDeleteIAMServiceAccountsReturns(result1 *tasks.TaskTree, result2 error) { + fake.newTasksToDeleteIAMServiceAccountsMutex.Lock() + defer fake.newTasksToDeleteIAMServiceAccountsMutex.Unlock() + fake.NewTasksToDeleteIAMServiceAccountsStub = nil + fake.newTasksToDeleteIAMServiceAccountsReturns = struct { + result1 *tasks.TaskTree + result2 error + }{result1, result2} +} + +func (fake *FakeStackManager) NewTasksToDeleteIAMServiceAccountsReturnsOnCall(i int, result1 *tasks.TaskTree, result2 error) { + fake.newTasksToDeleteIAMServiceAccountsMutex.Lock() + defer fake.newTasksToDeleteIAMServiceAccountsMutex.Unlock() + fake.NewTasksToDeleteIAMServiceAccountsStub = nil + if fake.newTasksToDeleteIAMServiceAccountsReturnsOnCall == nil { + fake.newTasksToDeleteIAMServiceAccountsReturnsOnCall = make(map[int]struct { + result1 *tasks.TaskTree + result2 error + }) + } + fake.newTasksToDeleteIAMServiceAccountsReturnsOnCall[i] = struct { + result1 *tasks.TaskTree + result2 error + }{result1, result2} +} + func (fake *FakeStackManager) UpdateStack(arg1 string, arg2 string, arg3 string, arg4 manager.TemplateData, arg5 map[string]string) error { fake.updateStackMutex.Lock() ret, specificReturn := fake.updateStackReturnsOnCall[len(fake.updateStackArgsForCall)] @@ -337,6 +418,8 @@ func (fake *FakeStackManager) Invocations() map[string][][]interface{} { defer fake.listStacksMatchingMutex.RUnlock() fake.newTasksToCreateIAMServiceAccountsMutex.RLock() defer fake.newTasksToCreateIAMServiceAccountsMutex.RUnlock() + fake.newTasksToDeleteIAMServiceAccountsMutex.RLock() + defer fake.newTasksToDeleteIAMServiceAccountsMutex.RUnlock() fake.updateStackMutex.RLock() defer fake.updateStackMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} diff --git a/pkg/actions/iam/iam.go b/pkg/actions/iam/iam.go index 981cab2d269..5bd66086c77 100644 --- a/pkg/actions/iam/iam.go +++ b/pkg/actions/iam/iam.go @@ -26,6 +26,8 @@ type StackManager interface { ListStacksMatching(nameRegex string, statusFilters ...string) ([]*manager.Stack, error) UpdateStack(stackName, changeSetName, description string, templateData manager.TemplateData, parameters map[string]string) error NewTasksToCreateIAMServiceAccounts(serviceAccounts []*api.ClusterIAMServiceAccount, oidc *iamoidc.OpenIDConnectManager, clientSetGetter kubernetes.ClientSetGetter, replaceExistingRole bool) *tasks.TaskTree + NewTasksToDeleteIAMServiceAccounts(serviceAccountsToDelete []string, clientSetGetter kubernetes.ClientSetGetter, wait bool) (*tasks.TaskTree, error) + GetIAMServiceAccounts() ([]*api.ClusterIAMServiceAccount, error) } diff --git a/pkg/ctl/delete/iamserviceaccount.go b/pkg/ctl/delete/iamserviceaccount.go index 36e96e84e3d..0d3ea5c834c 100644 --- a/pkg/ctl/delete/iamserviceaccount.go +++ b/pkg/ctl/delete/iamserviceaccount.go @@ -7,10 +7,10 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "github.com/weaveworks/eksctl/pkg/actions/iam" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" "github.com/weaveworks/eksctl/pkg/ctl/cmdutils" "github.com/weaveworks/eksctl/pkg/ctl/cmdutils/filter" - "github.com/weaveworks/eksctl/pkg/kubernetes" "github.com/weaveworks/eksctl/pkg/printers" ) @@ -45,7 +45,7 @@ func deleteIAMServiceAccountCmdWithRunFunc(cmd *cmdutils.Cmd, runFunc func(cmd * fs.StringVar(&serviceAccount.Namespace, "namespace", "default", "namespace where to delete the iamserviceaccount") cmdutils.AddIAMServiceAccountFilterFlags(fs, &cmd.Include, &cmd.Exclude) - fs.BoolVar(&onlyMissing, "only-missing", false, "Only delete nodegroups that are not defined in the given config file") + fs.BoolVar(&onlyMissing, "only-missing", false, "Only delete iamserviceaccounts that are not defined in the given config file") cmdutils.AddApproveFlag(fs, cmd) cmdutils.AddRegionFlag(fs, &cmd.ProviderConfig) cmdutils.AddConfigFileFlag(fs, &cmd.ClusterConfigFile) @@ -116,26 +116,9 @@ func doDeleteIAMServiceAccount(cmd *cmdutils.Cmd, serviceAccount *api.ClusterIAM saSubset, _ := saFilter.MatchAll(cfg.IAM.ServiceAccounts) - tasks, err := stackManager.NewTasksToDeleteIAMServiceAccounts(saSubset.Has, kubernetes.NewCachedClientSet(clientSet), cmd.Wait) - if err != nil { - return err - } - tasks.PlanMode = cmd.Plan + iamServiceAccountManager := iam.New(cfg.Metadata.Name, ctl, stackManager, oidc, clientSet) if err := printer.LogObj(logger.Debug, "cfg.json = \\\n%s\n", cfg); err != nil { - return err } - - logger.Info(tasks.Describe()) - if errs := tasks.DoAllSync(); len(errs) > 0 { - logger.Info("%d error(s) occurred and IAM Role stacks haven't been deleted properly, you may wish to check CloudFormation console", len(errs)) - for _, err := range errs { - logger.Critical("%s\n", err.Error()) - } - return fmt.Errorf("failed to delete iamserviceaccount(s)") - } - - cmdutils.LogPlanModeWarning(cmd.Plan && saSubset.Len() > 0) - - return nil + return iamServiceAccountManager.Delete(saSubset.List(), cmd.Plan, cmd.Wait) }