diff --git a/.editorconfig b/.editorconfig index a6d81a313b..22dffe113f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,6 +15,9 @@ indent_style = space [*.go] indent_style = tab +[*_test.go] +trim_trailing_whitespace = false + [{*.html, *.css, *.scss}] indent_size = 2 diff --git a/go.mod b/go.mod index 5d4d272fce..7d407e0482 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( github.com/xgfone/netaddr v0.5.1 golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/tools v0.1.9 + golang.org/x/tools v0.1.10 gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.8.1 @@ -432,7 +432,7 @@ require ( go.uber.org/multierr v1.7.0 // indirect go.uber.org/zap v1.19.0 // indirect gocloud.dev v0.24.0 // indirect - golang.org/x/mod v0.5.1 // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a // indirect diff --git a/go.sum b/go.sum index 9dfdd721ac..5d3ca7d363 100644 --- a/go.sum +++ b/go.sum @@ -210,7 +210,6 @@ github.com/DisgoOrg/restclient v1.2.8/go.mod h1:2pc/htya/5kjxvWNYya98sb8B4mexobx github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= -github.com/GeertJohan/go.rice v1.0.2 h1:PtRw+Tg3oa3HYwiDBZyvOJ8LdIyf6lAovJJtr7YOAYk= github.com/GeertJohan/go.rice v1.0.2/go.mod h1:af5vUNlDNkCjOZeSGFgIJxDje9qdjsO6hshx0gTmZt4= github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= github.com/GoogleCloudPlatform/cloudsql-proxy v1.24.0/go.mod h1:3tx938GhY4FC+E1KT/jNjDw7Z5qxAEtIiERJ2sXjnII= @@ -739,7 +738,6 @@ github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= -github.com/daaku/go.zipexe v1.0.1 h1:wV4zMsDOI2SZ2m7Tdz1Ps96Zrx+TzaK15VbUaGozw0M= github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8= github.com/daixiang0/gci v0.3.1-0.20220208004058-76d765e3ab48 h1:9rJGqaC5do9zkvKrtRdx0HJoxj7Jd6vDa0O2eBU0AbU= github.com/daixiang0/gci v0.3.1-0.20220208004058-76d765e3ab48/go.mod h1:jaASoJmv/ykO9dAAPy31iJnreV19248qKDdVWf3QgC4= @@ -1029,7 +1027,6 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= @@ -1106,7 +1103,6 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -1495,7 +1491,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= @@ -1550,7 +1545,6 @@ github.com/kisielk/errcheck v1.6.0 h1:YTDO4pNy7AUN/021p+JGHycQyYNIyMoenM1YDVK6Rl github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/kisom/goutils v1.4.3/go.mod h1:Lp5qrquG7yhYnWzZCI/68Pa/GpFynw//od6EkGnWpac= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= @@ -2361,7 +2355,6 @@ github.com/weaveworks/mesh v0.0.0-20191105120815-58dbcc3e8e63/go.mod h1:RZebXKv5 github.com/weaveworks/schemer v0.0.0-20210802122110-338b258ad2ca h1:2P7ELY25OkuvkzAkLrIAXwYCZZEaEEHUshssVKslz8k= github.com/weaveworks/schemer v0.0.0-20210802122110-338b258ad2ca/go.mod h1:y8Luzq6JDsYVoIV0QAlnvIiq8bSaap0myMjWKyzVFTY= github.com/weppos/publicsuffix-go v0.13.1-0.20210123135404-5fd73613514e/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= -github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss= github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= @@ -2421,9 +2414,7 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= github.com/zmap/zcrypto v0.0.0-20210123152837-9cf5beac6d91/go.mod h1:R/deQh6+tSWlgI9tb4jNmXxn8nSCabl5ZQsBX9//I/E= -github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc h1:zkGwegkOW709y0oiAraH/3D8njopUR/pARHv4tZZ6pw= github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc/go.mod h1:FM4U1E3NzlNMRnSUTU3P1UdukWhYGifqEsjk9fn7BCk= -github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0= github.com/zmap/zlint/v3 v3.1.0/go.mod h1:L7t8s3sEKkb0A2BxGy1IWrxt1ZATa1R4QfJZaQOD3zU= gitlab.com/bosi/decorder v0.2.1 h1:ehqZe8hI4w7O4b1vgsDZw1YU1PE7iJXrQWFMsocbQ1w= gitlab.com/bosi/decorder v0.2.1/go.mod h1:6C/nhLSbF6qZbYD8bRmISBwc6vcWdNsiIBkRvjJFrH0= @@ -2616,8 +2607,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3048,8 +3040,9 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/cfn/manager/iam.go b/pkg/cfn/manager/iam.go index 4487c87407..3d4f7a2633 100644 --- a/pkg/cfn/manager/iam.go +++ b/pkg/cfn/manager/iam.go @@ -3,8 +3,10 @@ package manager import ( "fmt" + "github.com/aws/aws-sdk-go/aws/awserr" cfn "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/kris-nova/logger" + "github.com/pkg/errors" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" "github.com/weaveworks/eksctl/pkg/cfn/builder" @@ -17,6 +19,53 @@ func (c *StackCollection) makeIAMServiceAccountStackName(namespace, name string) return fmt.Sprintf("eksctl-%s-addon-iamserviceaccount-%s-%s", c.spec.Metadata.Name, namespace, name) } +// stackHasRolledBack alerts of existing stack in rollback status +func (c *StackCollection) stackHasRolledBack(stackName string) (*Stack, error) { + input := &cfn.DescribeStacksInput{ + StackName: &stackName, + } + resp, err := c.cloudformationAPI.DescribeStacks(input) + if err != nil { + aerr, ok := err.(awserr.Error) + if !ok { + return nil, errors.Wrapf(err, "convertion to an AWS error failed") + } + if len(aerr.Code()) == 0 { + return nil, err + } + if aerr.Code() != "ValidationError" { + return nil, errors.Wrapf(err, "describing CloudFormation stack %q, code %q", stackName, aerr.Code()) + } + } + if len(resp.Stacks) == 0 { + return nil, nil + } + for _, s := range resp.Stacks { + if *(s.StackStatus) == cfn.StackStatusRollbackComplete { + return s, nil + } + if !c.StackStatusIsNotTransitional(s) { + return nil, errors.Wrapf(err, "stack %q is in a transitional status (%q)", stackName, *(s.StackStatus)) + } + } + return nil, nil +} + +func (c *StackCollection) deleteRolledbackStack(name string) error { + rollbackedStack, err := c.stackHasRolledBack(name) + if err != nil { + return err + } + if rollbackedStack != nil { + logger.Warning("deleting existing rolled back stack %q", name) + err = c.DeleteStackSync(rollbackedStack) + if err != nil { + return err + } + } + return nil +} + // createIAMServiceAccountTask creates the iamserviceaccount in CloudFormation func (c *StackCollection) createIAMServiceAccountTask(errs chan error, spec *api.ClusterIAMServiceAccount, oidc *iamoidc.OpenIDConnectManager) error { name := c.makeIAMServiceAccountStackName(spec.Namespace, spec.Name) @@ -31,6 +80,9 @@ func (c *StackCollection) createIAMServiceAccountTask(errs chan error, spec *api } spec.Tags[api.IAMServiceAccountNameTag] = spec.NameString() + if err := c.deleteRolledbackStack(name); err != nil { + return err + } if err := c.CreateStack(name, stack, spec.Tags, nil, errs); err != nil { logger.Info("an error occurred creating the stack, to cleanup resources, run 'eksctl delete iamserviceaccount --region=%s --name=%s --namespace=%s'", c.spec.Metadata.Region, spec.Name, spec.Namespace) return err @@ -50,6 +102,10 @@ func (c *StackCollection) DescribeIAMServiceAccountStacks() ([]*Stack, error) { if *s.StackStatus == cfn.StackStatusDeleteComplete { continue } + if *s.StackStatus == cfn.StackStatusRollbackComplete { + logger.Warning("found stack %v in ROLLBACK_COMPLETE", *s.StackName) + continue + } if GetIAMServiceAccountName(s) != "" { iamServiceAccountStacks = append(iamServiceAccountStacks, s) } diff --git a/pkg/cfn/manager/tasks_test.go b/pkg/cfn/manager/tasks_test.go index c7028279c5..48ed486a8d 100644 --- a/pkg/cfn/manager/tasks_test.go +++ b/pkg/cfn/manager/tasks_test.go @@ -3,12 +3,19 @@ package manager import ( "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/awstesting" + "github.com/aws/aws-sdk-go/service/cloudformation" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/pkg/errors" "github.com/stretchr/testify/mock" + "k8s.io/client-go/kubernetes/fake" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + iamoidc "github.com/weaveworks/eksctl/pkg/iam/oidc" + "github.com/weaveworks/eksctl/pkg/kubernetes" "github.com/weaveworks/eksctl/pkg/testutils/mockprovider" vpcfakes "github.com/weaveworks/eksctl/pkg/vpc/fakes" ) @@ -201,6 +208,282 @@ var _ = Describe("StackCollection Tasks", func() { }) }) }) + + Describe("IAMServiceAccountTask", func() { + var ( + clusterName string + iamServiceAccounts []*api.ClusterIAMServiceAccount + name string + namespace string + oidc *iamoidc.OpenIDConnectManager + stackArn string + stackArnPrefix string + stackName string + ) + // service account creation should succeed + When("service account doesn't exist yet", func() { + // setup + BeforeEach(func() { + clusterName = "cluster1" + namespace = "default" + name = "new" + cfg = newClusterConfig(clusterName) + iamServiceAccounts = []*api.ClusterIAMServiceAccount{ + { + ClusterIAMMeta: api.ClusterIAMMeta{ + Name: name, + Namespace: namespace, + }, + AttachPolicyARNs: []string{"arn-123"}, + }, + } + cfg.IAM.ServiceAccounts = iamServiceAccounts + var err error + oidc, err = iamoidc.NewOpenIDConnectManager(nil, "456123987123", "https://oidc.eks.us-west-2.amazonaws.com/id/A39A2842863C47208955D753DE205E6E", "aws", nil) + Expect(err).NotTo(HaveOccurred()) + oidc.ProviderARN = "arn:aws:iam::456123987123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/A39A2842863C47208955D753DE205E6E" + p = mockprovider.NewMockProvider() + stackManager = NewStackCollection(p, cfg) + stackName = fmt.Sprintf("eksctl-%s-addon-iamserviceaccount-%s-%s", clusterName, namespace, name) + stackArn = fmt.Sprintf("arn:aws:cloudformation:eu-west-1:456123987123:stack/%s/01", stackName) + p.MockCloudFormation().On("DescribeStacks", &cloudformation.DescribeStacksInput{StackName: aws.String(stackName)}).Return(&cloudformation.DescribeStacksOutput{}, nil) + p.MockCloudFormation().On("CreateStack", mock.Anything).Return(&cloudformation.CreateStackOutput{StackId: aws.String(stackArn)}, nil) + describeStacksOutput := &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackName: aws.String(stackName), + StackId: aws.String(stackArn), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("alpha.eksctl.io/cluster-name"), + Value: aws.String(clusterName), + }, + }, + Outputs: []*cloudformation.Output{ + { + Description: aws.String("IAM Role"), + OutputKey: aws.String("Role1"), + OutputValue: aws.String("role-arn-123"), + ExportName: aws.String("role-arn-123"), + }, + }, + }, + }, + } + p.MockCloudFormation().On("DescribeStacks", &cloudformation.DescribeStacksInput{StackName: aws.String(stackArn)}).Return(describeStacksOutput, nil) + req := awstesting.NewClient(aws.NewConfig().WithRegion("us-west-2")).NewRequest(&request.Operation{Name: "Operation"}, nil, describeStacksOutput) + p.MockCloudFormation().On("DescribeStacksRequest", &cloudformation.DescribeStacksInput{StackName: aws.String(stackArn)}).Return(req, describeStacksOutput) + }) + It("creates a new stack without errors", func() { + tasks := stackManager.NewTasksToCreateIAMServiceAccounts(iamServiceAccounts, oidc, kubernetes.NewCachedClientSet(fake.NewSimpleClientset())) + Expect(tasks.Len()).To(Equal(1)) + Expect(tasks.Describe()).To(Equal(`1 task: { + 2 sequential sub-tasks: { + create IAM role for serviceaccount "default/new", + create serviceaccount "default/new", + } }`)) + errs := tasks.DoAllSync() + Expect(errs).To(HaveLen(0)) + // all calls should happen + Expect(p.MockCloudFormation().AssertExpectations(GinkgoT())) + }) + }) + // existing stack in CREATE_COMPLETE status, shouldn't fail + When("service account already exists", func() { + BeforeEach(func() { + clusterName = "cluster1" + namespace = "default" + name = "existing" + cfg = newClusterConfig(clusterName) + iamServiceAccounts = []*api.ClusterIAMServiceAccount{ + { + ClusterIAMMeta: api.ClusterIAMMeta{ + Name: name, + Namespace: namespace, + }, + AttachPolicyARNs: []string{"arn-123"}, + }, + } + cfg.IAM.ServiceAccounts = iamServiceAccounts + var err error + oidc, err = iamoidc.NewOpenIDConnectManager(nil, "456123987123", "https://oidc.eks.us-west-2.amazonaws.com/id/A39A2842863C47208955D753DE205E6E", "aws", nil) + Expect(err).NotTo(HaveOccurred()) + oidc.ProviderARN = "arn:aws:iam::456123987123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/A39A2842863C47208955D753DE205E6E" + p = mockprovider.NewMockProvider() + stackManager = NewStackCollection(p, cfg) + stackName = fmt.Sprintf("eksctl-%s-addon-iamserviceaccount-%s-%s", clusterName, namespace, name) + stackArnPrefix = fmt.Sprintf("arn:aws:cloudformation:eu-west-1:456123987123:stack/%s", stackName) + describeStacksOutput01 := &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackName: aws.String(stackName), + StackId: aws.String(fmt.Sprintf("%s/01", stackArnPrefix)), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("alpha.eksctl.io/cluster-name"), + Value: aws.String(clusterName), + }, + }, + Outputs: []*cloudformation.Output{ + { + Description: aws.String("IAM Role"), + OutputKey: aws.String("Role1"), + OutputValue: aws.String("role-arn-123"), + ExportName: aws.String("role-arn-123"), + }, + }, + }, + }, + } + describeStacksOutput02 := &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackName: aws.String(stackName), + StackId: aws.String(fmt.Sprintf("%s/02", stackArnPrefix)), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("alpha.eksctl.io/cluster-name"), + Value: aws.String(clusterName), + }, + }, + Outputs: []*cloudformation.Output{ + { + Description: aws.String("IAM Role"), + OutputKey: aws.String("Role1"), + OutputValue: aws.String("role-arn-123"), + ExportName: aws.String("role-arn-123"), + }, + }, + }, + }, + } + // filter on stack name when looking for a unknown stack + p.MockCloudFormation().On("DescribeStacks", &cloudformation.DescribeStacksInput{StackName: aws.String(stackName)}).Return(describeStacksOutput01, nil) + // filter on stack id when looking for a known stack + p.MockCloudFormation().On("DescribeStacks", &cloudformation.DescribeStacksInput{StackName: aws.String(fmt.Sprintf("%s/02", stackArnPrefix))}).Return(describeStacksOutput02, nil) + p.MockCloudFormation().On("CreateStack", mock.Anything).Return(&cloudformation.CreateStackOutput{StackId: aws.String(fmt.Sprintf("%s/02", stackArnPrefix))}, nil) + req := awstesting.NewClient(aws.NewConfig().WithRegion("us-west-2")).NewRequest(&request.Operation{Name: "Operation"}, nil, describeStacksOutput02) + p.MockCloudFormation().On("DescribeStacksRequest", &cloudformation.DescribeStacksInput{StackName: aws.String(fmt.Sprintf("%s/02", stackArnPrefix))}).Return(req, describeStacksOutput02) + }) + It("doesn't fail", func() { + tasks := stackManager.NewTasksToCreateIAMServiceAccounts(iamServiceAccounts, oidc, kubernetes.NewCachedClientSet(fake.NewSimpleClientset())) + Expect(tasks.Len()).To(Equal(1)) + errs := tasks.DoAllSync() + Expect(errs).To(HaveLen(0)) + // all calls should happen + Expect(p.MockCloudFormation().AssertExpectations(GinkgoT())) + }) + }) + // should delete and attempt to recreate the stack + When("service account exists and is in ROLLBACK_COMPLETE status", func() { + BeforeEach(func() { + clusterName = "cluster1" + namespace = "default" + name = "rollback" + cfg = newClusterConfig(clusterName) + iamServiceAccounts = []*api.ClusterIAMServiceAccount{ + { + ClusterIAMMeta: api.ClusterIAMMeta{ + Name: name, + Namespace: namespace, + }, + AttachPolicyARNs: []string{"arn-123"}, + }, + } + cfg.IAM.ServiceAccounts = iamServiceAccounts + var err error + oidc, err = iamoidc.NewOpenIDConnectManager(nil, "456123987123", "https://oidc.eks.us-west-2.amazonaws.com/id/A39A2842863C47208955D753DE205E6E", "aws", nil) + Expect(err).NotTo(HaveOccurred()) + oidc.ProviderARN = "arn:aws:iam::456123987123:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/A39A2842863C47208955D753DE205E6E" + p = mockprovider.NewMockProvider() + p.SetRegion("us-west-2") + stackManager = NewStackCollection(p, cfg) + stackName = fmt.Sprintf("eksctl-%s-addon-iamserviceaccount-%s-%s", clusterName, namespace, name) + stackArnPrefix = fmt.Sprintf("arn:aws:cloudformation:eu-west-1:456123987123:stack/%s", stackName) + describeStacksOutput01 := &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackName: aws.String(stackName), + StackId: aws.String(fmt.Sprintf("%s/01", stackArnPrefix)), + StackStatus: aws.String(cloudformation.StackStatusRollbackComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("alpha.eksctl.io/cluster-name"), + Value: aws.String(clusterName), + }, + }, + }, + }, + } + describeStacksOutput01Deleted := &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackName: aws.String(stackName), + StackId: aws.String(fmt.Sprintf("%s/01", stackArnPrefix)), + StackStatus: aws.String(cloudformation.StackStatusDeleteComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("alpha.eksctl.io/cluster-name"), + Value: aws.String(clusterName), + }, + }, + }, + }, + } + describeStacksOutput02 := &cloudformation.DescribeStacksOutput{ + Stacks: []*cloudformation.Stack{ + { + StackName: aws.String(stackName), + StackId: aws.String(fmt.Sprintf("%s/02", stackArnPrefix)), + StackStatus: aws.String(cloudformation.StackStatusCreateComplete), + Tags: []*cloudformation.Tag{ + { + Key: aws.String("alpha.eksctl.io/cluster-name"), + Value: aws.String(clusterName), + }, + }, + Outputs: []*cloudformation.Output{ + { + Description: aws.String("IAM Role"), + OutputKey: aws.String("Role1"), + OutputValue: aws.String("role-arn-123"), + ExportName: aws.String("role-arn-123"), + }, + }, + }, + }, + } + p.MockCloudFormation().On("DescribeStacks", &cloudformation.DescribeStacksInput{StackName: aws.String(stackName)}).Return(describeStacksOutput01, nil) + p.MockCloudFormation().On("DescribeStacks", &cloudformation.DescribeStacksInput{StackName: aws.String(fmt.Sprintf("%s/02", stackArnPrefix))}).Return(describeStacksOutput02, nil) + p.MockCloudFormation().On("CreateStack", mock.Anything).Return(&cloudformation.CreateStackOutput{StackId: aws.String(fmt.Sprintf("%s/02", stackArnPrefix))}, nil) + p.MockCloudFormation().On("DeleteStack", mock.Anything).Return(&cloudformation.DeleteStackOutput{}, nil) + // for deletion request + req01 := awstesting.NewClient(aws.NewConfig().WithRegion("us-west-2")).NewRequest(&request.Operation{Name: "Operation"}, nil, describeStacksOutput01Deleted) + p.MockCloudFormation().On("DescribeStacksRequest", &cloudformation.DescribeStacksInput{StackName: aws.String(fmt.Sprintf("%s/01", stackArnPrefix))}).Return(req01, describeStacksOutput01Deleted) + // for creation request + req02 := awstesting.NewClient(aws.NewConfig().WithRegion("us-west-2")).NewRequest(&request.Operation{Name: "Operation"}, nil, describeStacksOutput02) + p.MockCloudFormation().On("DescribeStacksRequest", &cloudformation.DescribeStacksInput{StackName: aws.String(fmt.Sprintf("%s/02", stackArnPrefix))}).Return(req02, describeStacksOutput02) + }) + It("doesn't fail", func() { + tasks := stackManager.NewTasksToCreateIAMServiceAccounts(iamServiceAccounts, oidc, kubernetes.NewCachedClientSet(fake.NewSimpleClientset())) + Expect(tasks.Describe()).To(Equal(`1 task: { + 2 sequential sub-tasks: { + create IAM role for serviceaccount "default/rollback", + create serviceaccount "default/rollback", + } }`)) + Expect(tasks.Len()).To(Equal(1)) + errs := tasks.DoAllSync() + Expect(errs).To(HaveLen(0)) + // all calls should happen + Expect(p.MockCloudFormation().AssertExpectations(GinkgoT())) + // all calls should happen + Expect(p.MockCloudFormation().AssertExpectations(GinkgoT())) + }) + }) + }) }) func makeNodeGroups(names ...string) []*api.NodeGroup { diff --git a/pkg/cfn/manager/waiters.go b/pkg/cfn/manager/waiters.go index 0e20849558..8c1eb1d970 100644 --- a/pkg/cfn/manager/waiters.go +++ b/pkg/cfn/manager/waiters.go @@ -108,6 +108,8 @@ func (c *StackCollection) troubleshootStackFailureCause(i *Stack, desiredStatus logger.Critical(msg) case cfn.ResourceStatusDeleteInProgress: logger.Warning(msg) + case cfn.ResourceStatusRollbackInProgress: + logger.Warning(msg) default: logger.Debug(msg) // only output this when verbose logging is enabled } diff --git a/pkg/ctl/cmdutils/filter/iamserviceaccount_filter.go b/pkg/ctl/cmdutils/filter/iamserviceaccount_filter.go index 2f7e8ff8b4..c1a388fd37 100644 --- a/pkg/ctl/cmdutils/filter/iamserviceaccount_filter.go +++ b/pkg/ctl/cmdutils/filter/iamserviceaccount_filter.go @@ -78,7 +78,7 @@ func (f *IAMServiceAccountFilter) SetExcludeExistingFilter(stackManager serviceA } // SetDeleteFilter uses stackManager to list existing iamserviceaccount stacks and configures -// the filter to either explictily exluce or include iamserviceaccounts that are missing from given serviceAccounts +// the filter to either explicitly exclude or include iamserviceaccounts that are missing from given serviceAccounts func (f *IAMServiceAccountFilter) SetDeleteFilter(lister serviceAccountLister, includeOnlyMissing bool, cfg *api.ClusterConfig) error { existing, err := lister.ListIAMServiceAccountStacks() if err != nil {