From 8d05318892adf62d9065a00f0008f9df53df5e26 Mon Sep 17 00:00:00 2001 From: Ilya Dmitrichenko Date: Thu, 17 Jan 2019 11:16:34 +0000 Subject: [PATCH] Simplify cluster stack updates - import VPC and subnets, as at the point of stack update imported VPC descriptor will be the same as the one already in use, whether it was originally imported or created as part of the cluster stack - generalise updates to only append any new resources --- pkg/cfn/manager/api.go | 2 + pkg/cfn/manager/cluster.go | 121 ++++++-------------------- pkg/cfn/manager/nodegroup.go | 10 +-- pkg/ctl/utils/update_cluster_stack.go | 25 ++---- 4 files changed, 40 insertions(+), 118 deletions(-) diff --git a/pkg/cfn/manager/api.go b/pkg/cfn/manager/api.go index 8c84d557fe..cee67f3a17 100644 --- a/pkg/cfn/manager/api.go +++ b/pkg/cfn/manager/api.go @@ -13,6 +13,8 @@ import ( "github.com/weaveworks/eksctl/pkg/cfn/builder" ) +const resourcesRootPath = "Resources" + var ( stackCapabilitiesIAM = aws.StringSlice([]string{cloudformation.CapabilityCapabilityIam}) ) diff --git a/pkg/cfn/manager/cluster.go b/pkg/cfn/manager/cluster.go index 4453101ed5..b090e37741 100644 --- a/pkg/cfn/manager/cluster.go +++ b/pkg/cfn/manager/cluster.go @@ -13,7 +13,6 @@ import ( api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha3" "github.com/weaveworks/eksctl/pkg/cfn/builder" - "github.com/weaveworks/eksctl/pkg/utils/ipnet" ) func (c *StackCollection) makeClusterStackName() string { @@ -62,99 +61,29 @@ func (c *StackCollection) WaitDeleteCluster() error { return c.BlockingWaitDeleteStack(c.makeClusterStackName()) } -// UpdateClusterForCompability will update cluster -// with new resources based on features that have -// a critical effect on forward-compatibility with -// respect to overal functionality and integrity -func (c *StackCollection) UpdateClusterForCompability() error { - const resourceRootPath = "Resources" - +// AppendNewClusterStackResource will update cluster +// stack with new resources in append-only way +func (c *StackCollection) AppendNewClusterStackResource() error { name := c.makeClusterStackName() - currentStack, err := c.DescribeClusterStack() - if err != nil { - return err - } - - // NOTE: currently we can only append new - // resources to the stack, as there are a - // few limitations; - // we don't have a way of recompiling the - // definition of the stack from it's current - // template and we don't have all feature - // indicators we would need (e.g. when - // existing VPC is used); - // to do that in a sensible manner we would - // have to thoroughly insepect the current - // template and see if e.g. VPC or SGs are - // imported or managed by us; - // addtionally, the EKS control plane itself - // cannot yet be updated via CloudFormation - - missingSharedNodeSecurityGroup := true - - for _, x := range currentStack.Outputs { - switch *x.OutputKey { - case builder.CfnOutputClusterSharedNodeSecurityGroup: - missingSharedNodeSecurityGroup = false - } - } + // NOTE: currently we can only append new resources to the stack, + // as there are a few limitations: + // - it must work with VPC that are imported as well as VPC that + // is mamaged as part of the stack; + // - CloudFormation cannot yet upgrade EKS control plane itself; - // Get current stack currentTemplate, err := c.GetStackTemplate(name) if err != nil { return errors.Wrapf(err, "error getting stack template %s", name) } - updateFeatureList := []string{} addResources := []string{} - if missingSharedNodeSecurityGroup { - updateFeatureList = append(updateFeatureList, "shared node security group") - addResources = append(addResources, - "ClusterSharedNodeSecurityGroup", - "IngressInterNodeGroupSG", - ) - } - - if len(addResources) == 0 { - logger.Success("all resources in cluster stack %q are up-to-date", name) - return nil - } - - currentResources := gjson.Get(currentTemplate, resourceRootPath) + currentResources := gjson.Get(currentTemplate, resourcesRootPath) if !currentResources.IsObject() { return fmt.Errorf("unexpected template format of the current stack ") } - { - // We need to use same subnet CIDRs in order to recompile the template - // with all of default resources - vpc := c.spec.VPC - vpc.Subnets = map[api.SubnetTopology]map[string]api.Network{ - api.SubnetTopologyPublic: map[string]api.Network{}, - api.SubnetTopologyPrivate: map[string]api.Network{}, - } - currentResources.ForEach(func(resourceKey, resource gjson.Result) bool { - if resource.Get("Type").Value() == "AWS::EC2::Subnet" { - az := resource.Get("Properties.AvailabilityZone").String() - cidr, _ := ipnet.ParseCIDR(resource.Get("Properties.CidrBlock").String()) - k := resourceKey.String() - if strings.HasPrefix(k, "SubnetPrivate") { - vpc.Subnets[api.SubnetTopologyPrivate][az] = api.Network{ - CIDR: cidr, - } - } - if strings.HasPrefix(k, "SubnetPublic") { - vpc.Subnets[api.SubnetTopologyPublic][az] = api.Network{ - CIDR: cidr, - } - } - } - return true - }) - } - logger.Info("creating cluster stack %q", name) newStack := builder.NewClusterResourceSet(c.provider, c.spec) if err := newStack.AddAllResources(); err != nil { @@ -167,30 +96,36 @@ func (c *StackCollection) UpdateClusterForCompability() error { } logger.Debug("newTemplate = %s", newTemplate) - newResources := gjson.Get(string(newTemplate), resourceRootPath) - + newResources := gjson.Get(string(newTemplate), resourcesRootPath) if !newResources.IsObject() { return fmt.Errorf("unexpected template format of the new version of the stack ") } logger.Debug("currentTemplate = %s", currentTemplate) - for _, resourceKey := range addResources { - var err error - resource := newResources.Get(resourceKey) - if !resource.Exists() { - return fmt.Errorf("resource with key %q doesn't exist in the new version of the stack", resourceKey) - } - currentTemplate, err = sjson.Set(currentTemplate, resourceRootPath+"."+resourceKey, resource.Value()) - if err != nil { - return errors.Wrapf(err, "unable to add resource with key %q to cluster stack", resourceKey) + var iterErr error + newResources.ForEach(func(resourceKey, resource gjson.Result) bool { + key := resourceKey.String() + if currentResources.Get(key).Exists() { + return true } + addResources = append(addResources, key) + path := resourcesRootPath + "." + key + currentTemplate, iterErr = sjson.Set(currentTemplate, path, resource.Value()) + return iterErr == nil + }) + if iterErr != nil { + return errors.Wrap(iterErr, "updating stack template") + } + + if len(addResources) == 0 { + logger.Success("all resources in cluster stack %q are up-to-date", name) + return nil } logger.Debug("currentTemplate = %s", currentTemplate) - describeUpdate := fmt.Sprintf("updating stack to add new features: %s;", - strings.Join(updateFeatureList, ", ")) + describeUpdate := fmt.Sprintf("updating stack to add new resources: %v", addResources) return c.UpdateStack(name, "update-cluster", describeUpdate, []byte(currentTemplate), nil) } diff --git a/pkg/cfn/manager/nodegroup.go b/pkg/cfn/manager/nodegroup.go index de03c1ab8a..298417a7d6 100644 --- a/pkg/cfn/manager/nodegroup.go +++ b/pkg/cfn/manager/nodegroup.go @@ -18,11 +18,11 @@ import ( ) const ( - desiredCapacityPath = "Resources.NodeGroup.Properties.DesiredCapacity" - maxSizePath = "Resources.NodeGroup.Properties.MaxSize" - minSizePath = "Resources.NodeGroup.Properties.MinSize" - instanceTypePath = "Resources.NodeLaunchConfig.Properties.InstanceType" - imageIDPath = "Resources.NodeLaunchConfig.Properties.ImageId" + desiredCapacityPath = resourcesRootPath + ".NodeGroup.Properties.DesiredCapacity" + maxSizePath = resourcesRootPath + ".NodeGroup.Properties.MaxSize" + minSizePath = resourcesRootPath + ".NodeGroup.Properties.MinSize" + instanceTypePath = resourcesRootPath + ".NodeLaunchConfig.Properties.InstanceType" + imageIDPath = resourcesRootPath + ".NodeLaunchConfig.Properties.ImageId" ) // NodeGroupSummary represents a summary of a nodegroup stack diff --git a/pkg/ctl/utils/update_cluster_stack.go b/pkg/ctl/utils/update_cluster_stack.go index d3dcada8cb..ebdadf71c0 100644 --- a/pkg/ctl/utils/update_cluster_stack.go +++ b/pkg/ctl/utils/update_cluster_stack.go @@ -5,6 +5,7 @@ import ( "os" "github.com/kris-nova/logger" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -61,32 +62,16 @@ func doUpdateClusterStacksCmd(p *api.ProviderConfig, cfg *api.ClusterConfig, nam return fmt.Errorf("--name must be set") } - stackManager := ctl.NewStackManager(cfg) - - { - stack, err := stackManager.DescribeClusterStack() - if err != nil { - return err - } - logger.Info("cluster = %#v", stack) + if err := ctl.GetClusterVPC(cfg); err != nil { + return errors.Wrapf(err, "getting VPC configuration for cluster %q", cfg.Metadata.Name) } - // if err := ctl.GetClusterVPC(cfg); err != nil { - // return errors.Wrapf(err, "getting VPC configuration for cluster %q", cfg.Metadata.Name) - // } + stackManager := ctl.NewStackManager(cfg) - if err := stackManager.UpdateClusterForCompability(); err != nil { + if err := stackManager.AppendNewClusterStackResource(); err != nil { return err } - { - stack, err := stackManager.DescribeClusterStack() - if err != nil { - return err - } - logger.Info("cluster = %#v", stack) - } - if err := ctl.ValidateExistingNodeGroupsForCompatibility(cfg, stackManager); err != nil { logger.Critical("failed checking nodegroups", err.Error()) }