diff --git a/pkg/apis/eksctl.io/v1alpha3/vpc.go b/pkg/apis/eksctl.io/v1alpha3/vpc.go index 35249917f3..1b8ff421e1 100644 --- a/pkg/apis/eksctl.io/v1alpha3/vpc.go +++ b/pkg/apis/eksctl.io/v1alpha3/vpc.go @@ -45,6 +45,14 @@ const ( SubnetTopologyPublic SubnetTopology = "Public" ) +// SubnetTopologies returns a list of topologies +func SubnetTopologies() []SubnetTopology { + return []SubnetTopology{ + SubnetTopologyPrivate, + SubnetTopologyPublic, + } +} + // DefaultCIDR returns default global CIDR for VPC func DefaultCIDR() ipnet.IPNet { return ipnet.IPNet{ diff --git a/pkg/cfn/builder/cluster.go b/pkg/cfn/builder/cluster.go index 16231c78ba..d95dcdcc4e 100644 --- a/pkg/cfn/builder/cluster.go +++ b/pkg/cfn/builder/cluster.go @@ -59,7 +59,7 @@ func (c *ClusterResourceSet) AddAllResources() error { c.addResourcesForIAM() c.addResourcesForControlPlane() - c.rs.newOutput(cfnOutputClusterStackName, gfn.RefStackName, false) + c.rs.newOutput(CfnOutputClusterStackName, gfn.RefStackName, false) return nil } @@ -93,9 +93,9 @@ func (c *ClusterResourceSet) addResourcesForControlPlane() { ResourcesVpcConfig: clusterVPC, }) - c.rs.newOutputFromAtt(cfnOutputClusterCertificateAuthorityData, "ControlPlane.CertificateAuthorityData", false) - c.rs.newOutputFromAtt(cfnOutputClusterEndpoint, "ControlPlane.Endpoint", true) - c.rs.newOutputFromAtt(cfnOutputClusterARN, "ControlPlane.Arn", true) + c.rs.newOutputFromAtt(CfnOutputClusterCertificateAuthorityData, "ControlPlane.CertificateAuthorityData", false) + c.rs.newOutputFromAtt(CfnOutputClusterEndpoint, "ControlPlane.Endpoint", true) + c.rs.newOutputFromAtt(CfnOutputClusterARN, "ControlPlane.Arn", true) } // GetAllOutputs collects all outputs of the cluster @@ -104,27 +104,27 @@ func (c *ClusterResourceSet) GetAllOutputs(stack cfn.Stack) error { return err } - c.spec.VPC.ID = c.outputs[cfnOutputClusterVPC] - c.spec.VPC.SecurityGroup = c.outputs[cfnOutputClusterSecurityGroup] + c.spec.VPC.ID = c.outputs[CfnOutputClusterVPC] + c.spec.VPC.SecurityGroup = c.outputs[CfnOutputClusterSecurityGroup] - if err := vpc.UseSubnets(c.provider, c.spec, api.SubnetTopologyPrivate, strings.Split(c.outputs[cfnOutputClusterSubnetsPrivate], ",")); err != nil { + if err := vpc.UseSubnets(c.provider, c.spec, api.SubnetTopologyPrivate, strings.Split(c.outputs[CfnOutputClusterSubnetsPrivate], ",")); err != nil { return err } - if err := vpc.UseSubnets(c.provider, c.spec, api.SubnetTopologyPublic, strings.Split(c.outputs[cfnOutputClusterSubnetsPublic], ",")); err != nil { + if err := vpc.UseSubnets(c.provider, c.spec, api.SubnetTopologyPublic, strings.Split(c.outputs[CfnOutputClusterSubnetsPublic], ",")); err != nil { return err } - caData, err := base64.StdEncoding.DecodeString(c.outputs[cfnOutputClusterCertificateAuthorityData]) + caData, err := base64.StdEncoding.DecodeString(c.outputs[CfnOutputClusterCertificateAuthorityData]) if err != nil { return errors.Wrap(err, "decoding certificate authority data") } c.spec.Status = &api.ClusterStatus{ CertificateAuthorityData: caData, - Endpoint: c.outputs[cfnOutputClusterEndpoint], - ARN: c.outputs[cfnOutputClusterARN], - StackName: c.outputs[cfnOutputClusterStackName], + Endpoint: c.outputs[CfnOutputClusterEndpoint], + ARN: c.outputs[CfnOutputClusterARN], + StackName: c.outputs[CfnOutputClusterStackName], } return nil diff --git a/pkg/cfn/builder/iam.go b/pkg/cfn/builder/iam.go index 3c55bc317f..32fa1d0818 100644 --- a/pkg/cfn/builder/iam.go +++ b/pkg/cfn/builder/iam.go @@ -128,5 +128,5 @@ func (n *NodeGroupResourceSet) addResourcesForIAM() { ) } - n.rs.newOutputFromAtt(cfnOutputNodeGroupInstanceRoleARN, "NodeInstanceRole.Arn", true) + n.rs.newOutputFromAtt(CfnOutputNodeGroupInstanceRoleARN, "NodeInstanceRole.Arn", true) } diff --git a/pkg/cfn/builder/nodegroup.go b/pkg/cfn/builder/nodegroup.go index e189742a2e..507751bd70 100644 --- a/pkg/cfn/builder/nodegroup.go +++ b/pkg/cfn/builder/nodegroup.go @@ -44,7 +44,7 @@ func (n *NodeGroupResourceSet) AddAllResources() error { n.spec.AMIFamily, n.spec.AllowSSH, n.spec.SubnetTopology(), templateDescriptionSuffix) - n.vpc = makeImportValue(n.clusterStackName, cfnOutputClusterVPC) + n.vpc = makeImportValue(n.clusterStackName, CfnOutputClusterVPC) userData, err := nodebootstrap.NewUserData(n.clusterSpec, n.spec) if err != nil { @@ -142,7 +142,7 @@ func (n *NodeGroupResourceSet) addResourcesForNodeGroup() error { vpcZoneIdentifier = map[string][]interface{}{ gfn.FnSplit: []interface{}{ ",", - makeImportValue(n.clusterStackName, cfnOutputClusterSubnets+string(n.spec.SubnetTopology())), + makeImportValue(n.clusterStackName, CfnOutputClusterSubnets+string(n.spec.SubnetTopology())), }, } } @@ -199,7 +199,7 @@ func (n *NodeGroupResourceSet) GetAllOutputs(stack cfn.Stack) error { return err } - n.spec.IAM.InstanceRoleARN = n.outputs[cfnOutputNodeGroupInstanceRoleARN] + n.spec.IAM.InstanceRoleARN = n.outputs[CfnOutputNodeGroupInstanceRoleARN] return nil } diff --git a/pkg/cfn/builder/outputs.go b/pkg/cfn/builder/outputs.go index 7559260b73..eb0d0b431f 100644 --- a/pkg/cfn/builder/outputs.go +++ b/pkg/cfn/builder/outputs.go @@ -10,21 +10,22 @@ import ( api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha3" ) +// Stack output names const ( // outputs from cluster stack - cfnOutputClusterVPC = "VPC" - cfnOutputClusterSecurityGroup = "SecurityGroup" - cfnOutputClusterSubnets = "Subnets" - cfnOutputClusterSubnetsPrivate = string(cfnOutputClusterSubnets + api.SubnetTopologyPrivate) - cfnOutputClusterSubnetsPublic = string(cfnOutputClusterSubnets + api.SubnetTopologyPublic) + CfnOutputClusterVPC = "VPC" + CfnOutputClusterSecurityGroup = "SecurityGroup" + CfnOutputClusterSubnets = "Subnets" + CfnOutputClusterSubnetsPrivate = string(CfnOutputClusterSubnets + api.SubnetTopologyPrivate) + CfnOutputClusterSubnetsPublic = string(CfnOutputClusterSubnets + api.SubnetTopologyPublic) - cfnOutputClusterCertificateAuthorityData = "CertificateAuthorityData" - cfnOutputClusterEndpoint = "Endpoint" - cfnOutputClusterARN = "ARN" - cfnOutputClusterStackName = "ClusterStackName" + CfnOutputClusterCertificateAuthorityData = "CertificateAuthorityData" + CfnOutputClusterEndpoint = "Endpoint" + CfnOutputClusterARN = "ARN" + CfnOutputClusterStackName = "ClusterStackName" // outputs from nodegroup stack - cfnOutputNodeGroupInstanceRoleARN = "InstanceRoleARN" + CfnOutputNodeGroupInstanceRoleARN = "InstanceRoleARN" ) // newOutput defines a new output and optionally exports it diff --git a/pkg/cfn/builder/vpc.go b/pkg/cfn/builder/vpc.go index 15de05e3ae..d13d8f2f58 100644 --- a/pkg/cfn/builder/vpc.go +++ b/pkg/cfn/builder/vpc.go @@ -94,9 +94,9 @@ func (c *ClusterResourceSet) importResourcesForVPC() { } func (c *ClusterResourceSet) addOutputsForVPC() { - c.rs.newOutput(cfnOutputClusterVPC, c.vpc, true) + c.rs.newOutput(CfnOutputClusterVPC, c.vpc, true) for topology := range c.spec.VPC.Subnets { - c.rs.newJoinedOutput(cfnOutputClusterSubnets+string(topology), c.subnets[topology], true) + c.rs.newJoinedOutput(CfnOutputClusterSubnets+string(topology), c.subnets[topology], true) } } @@ -106,7 +106,7 @@ func (c *ClusterResourceSet) addResourcesForSecurityGroups() { VpcId: c.vpc, }) c.securityGroups = []*gfn.Value{refSG} - c.rs.newJoinedOutput(cfnOutputClusterSecurityGroup, c.securityGroups, true) + c.rs.newJoinedOutput(CfnOutputClusterSecurityGroup, c.securityGroups, true) } func (n *NodeGroupResourceSet) addResourcesForSecurityGroups() { @@ -127,9 +127,9 @@ func (n *NodeGroupResourceSet) addResourcesForSecurityGroups() { nodeMaxPort = gfn.NewInteger(65535) ) - refCP := makeImportValue(n.clusterStackName, cfnOutputClusterSecurityGroup) + refCP := makeImportValue(n.clusterStackName, CfnOutputClusterSecurityGroup) refSG := n.newResource("SG", &gfn.AWSEC2SecurityGroup{ - VpcId: makeImportValue(n.clusterStackName, cfnOutputClusterVPC), + VpcId: makeImportValue(n.clusterStackName, CfnOutputClusterVPC), GroupDescription: gfn.NewString("Communication between the control plane and " + desc), Tags: []gfn.Tag{{ Key: gfn.NewString("kubernetes.io/cluster/" + n.clusterSpec.Metadata.Name), diff --git a/pkg/cfn/manager/api.go b/pkg/cfn/manager/api.go index 6d8be2a7a0..42e6ddb899 100644 --- a/pkg/cfn/manager/api.go +++ b/pkg/cfn/manager/api.go @@ -253,7 +253,7 @@ func (c *StackCollection) BlockingWaitDeleteStack(name string) error { } func fmtStacksRegexForCluster(name string) string { - const ourStackRegexFmt = "^(eksctl|EKS)-%s-((cluster|nodegroup-.+)|(VPC|ServiceRole|DefaultNodeGroup))$" + const ourStackRegexFmt = "^(eksctl|EKS)-%s-((cluster|nodegroup-.+)|(VPC|ServiceRole|ControlPlane|DefaultNodeGroup))$" return fmt.Sprintf(ourStackRegexFmt, name) } diff --git a/pkg/cfn/manager/cluster.go b/pkg/cfn/manager/cluster.go index 420024c85a..295d3c686e 100644 --- a/pkg/cfn/manager/cluster.go +++ b/pkg/cfn/manager/cluster.go @@ -1,7 +1,11 @@ package manager import ( + "strings" + + cfn "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/kris-nova/logger" + api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha3" "github.com/weaveworks/eksctl/pkg/cfn/builder" ) @@ -22,6 +26,24 @@ func (c *StackCollection) CreateCluster(errs chan error, _ interface{}) error { return c.CreateStack(name, stack, nil, nil, errs) } +// DescribeClusterStack calls DescribeStacks and filters out cluster stack +func (c *StackCollection) DescribeClusterStack() (*Stack, error) { + stacks, err := c.DescribeStacks() + if err != nil { + return nil, err + } + + for _, s := range stacks { + if *s.StackStatus == cfn.StackStatusDeleteComplete { + continue + } + if getClusterName(s) != "" { + return s, nil + } + } + return nil, nil +} + // DeleteCluster deletes the cluster func (c *StackCollection) DeleteCluster() error { _, err := c.DeleteStack(c.makeClusterStackName()) @@ -32,3 +54,18 @@ func (c *StackCollection) DeleteCluster() error { func (c *StackCollection) WaitDeleteCluster() error { return c.BlockingWaitDeleteStack(c.makeClusterStackName()) } + +func getClusterName(s *Stack) string { + for _, tag := range s.Tags { + if *tag.Key == api.ClusterNameTag { + if strings.HasSuffix(*s.StackName, "-cluster") { + return *tag.Value + } + } + } + + if strings.HasPrefix(*s.StackName, "EKS-") && strings.HasSuffix(*s.StackName, "-ControlPlane") { + return strings.TrimPrefix("EKS-", strings.TrimSuffix(*s.StackName, "-ControlPlane")) + } + return "" +} diff --git a/pkg/cfn/manager/nodegroup.go b/pkg/cfn/manager/nodegroup.go index 258719a157..73c5e7589e 100644 --- a/pkg/cfn/manager/nodegroup.go +++ b/pkg/cfn/manager/nodegroup.go @@ -249,12 +249,3 @@ func getNodeGroupName(s *Stack) string { } return "" } - -func getClusterName(s *Stack) string { - for _, tag := range s.Tags { - if *tag.Key == api.ClusterNameTag { - return *tag.Value - } - } - return "" -} diff --git a/pkg/eks/eks.go b/pkg/eks/eks.go index 727ff16746..fcd7706af1 100644 --- a/pkg/eks/eks.go +++ b/pkg/eks/eks.go @@ -7,12 +7,14 @@ import ( "strings" "time" + "github.com/weaveworks/eksctl/pkg/cfn/builder" + "github.com/weaveworks/eksctl/pkg/vpc" + awseks "github.com/aws/aws-sdk-go/service/eks" "github.com/kris-nova/logger" "github.com/pkg/errors" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha3" "github.com/weaveworks/eksctl/pkg/printers" - "github.com/weaveworks/eksctl/pkg/vpc" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/kubernetes" ) @@ -86,25 +88,42 @@ func (c *ClusterProvider) GetCredentials(spec *api.ClusterConfig) error { // GetClusterVPC retrieves the VPC configuration func (c *ClusterProvider) GetClusterVPC(spec *api.ClusterConfig) error { - // Check the cluster exists and is active - cluster, err := c.DescribeControlPlaneMustBeActive(spec.Metadata) + cluster, err := c.NewStackManager(spec).DescribeClusterStack() if err != nil { return err } - logger.Debug("cluster = %#v", cluster) + + outputs := map[string]string{} + for _, x := range cluster.Outputs { + outputs[*x.OutputKey] = *x.OutputValue + } if spec.VPC == nil { spec.VPC = &api.ClusterVPC{} } - if err := vpc.ImportVPC(c.Provider, spec, *cluster.ResourcesVpcConfig.VpcId); err != nil { - return err + requiredKeyErrFmt := "cluster stack has no output key %q" + + if vpc, ok := outputs[builder.CfnOutputClusterVPC]; ok { + spec.VPC.ID = vpc + } else { + return fmt.Errorf(requiredKeyErrFmt, builder.CfnOutputClusterVPC) } - if numSGs := len(cluster.ResourcesVpcConfig.SecurityGroupIds); numSGs == 1 { - spec.VPC.SecurityGroup = *cluster.ResourcesVpcConfig.SecurityGroupIds[0] + if securityGroup, ok := outputs[builder.CfnOutputClusterSecurityGroup]; ok { + spec.VPC.SecurityGroup = securityGroup } else { - return fmt.Errorf("cluster %q has %d security groups, expected 1", spec.Metadata.Name, numSGs) + return fmt.Errorf(requiredKeyErrFmt, builder.CfnOutputClusterSecurityGroup) + } + + for _, topology := range api.SubnetTopologies() { + // either of subnet topologies are optional + if subnets, ok := outputs[builder.CfnOutputClusterSubnets+string(topology)]; ok { + subnets := strings.Split(subnets, ",") + if err := vpc.UseSubnets(c.Provider, spec, topology, subnets); err != nil { + return err + } + } } return nil