Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable use of existing subnets #305

Merged
merged 4 commits into from Nov 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 5 additions & 12 deletions pkg/cfn/builder/cluster.go
@@ -1,8 +1,6 @@
package builder

import (
"fmt"

cfn "github.com/aws/aws-sdk-go/service/cloudformation"
gfn "github.com/awslabs/goformation/cloudformation"

Expand Down Expand Up @@ -33,19 +31,14 @@ func (c *ClusterResourceSet) AddAllResources() error {

templateDescriptionFeatures := clusterTemplateDescriptionDefaultFeatures

if c.spec.VPC.ID != "" && c.spec.HasSufficientPublicSubnets() {
if err := c.spec.HasSufficientSubnets(); err != nil {
return err
}

if c.spec.VPC.ID != "" {
c.importResourcesForVPC()
templateDescriptionFeatures = " (with shared VPC and dedicated IAM role) "
} else {
topologies := len(c.spec.VPC.Subnets)
switch {
case topologies < 1:
return fmt.Errorf("too few subnet topologies: %v", c.spec.VPC.Subnets)
case topologies == 1 && !c.spec.HasSufficientPublicSubnets():
return fmt.Errorf("too few public subnets: %v", c.spec.VPC.Subnets["Public"])
case topologies == 2 && !c.spec.HasSufficientPrivateSubnets():
return fmt.Errorf("too few private subnets: %v", c.spec.VPC.Subnets["Private"])
}
c.addResourcesForVPC()
}
c.addOutputsForVPC()
Expand Down
1 change: 1 addition & 0 deletions pkg/cfn/builder/vpc.go
Expand Up @@ -85,6 +85,7 @@ func (c *ClusterResourceSet) addResourcesForVPC() {

func (c *ClusterResourceSet) importResourcesForVPC() {
c.vpc = gfn.NewString(c.spec.VPC.ID)
c.subnets = make(map[api.SubnetTopology][]*gfn.Value)
for topology := range c.spec.VPC.Subnets {
for _, subnet := range c.spec.SubnetIDs(topology) {
c.subnets[topology] = append(c.subnets[topology], gfn.NewString(subnet))
Expand Down
95 changes: 82 additions & 13 deletions pkg/ctl/create/cluster.go
Expand Up @@ -30,6 +30,7 @@ var (
availabilityZones []string

kopsClusterNameForVPC string
subnets map[api.SubnetTopology]*[]string
)

func createClusterCmd() *cobra.Command {
Expand Down Expand Up @@ -94,6 +95,11 @@ func createClusterCmd() *cobra.Command {

fs.IPNetVar(cfg.VPC.CIDR, "vpc-cidr", api.DefaultCIDR(), "global CIDR to use for VPC")

subnets = map[api.SubnetTopology]*[]string{
api.SubnetTopologyPrivate: fs.StringSlice("vpc-private-subnets", nil, "re-use private subnets of an existing VPC"),
api.SubnetTopologyPublic: fs.StringSlice("vpc-public-subnets", nil, "re-use public subnets of an existing VPC"),
}

fs.BoolVarP(&ng.PrivateNetworking, "node-private-networking", "P", false, "whether to make initial nodegroup networking private")

return cmd
Expand Down Expand Up @@ -127,27 +133,90 @@ func doCreateCluster(cfg *api.ClusterConfig, ng *api.NodeGroup, name string) err
return fmt.Errorf("--ssh-public-key must be non-empty string")
}

if kopsClusterNameForVPC != "" {
if len(availabilityZones) != 0 {
return fmt.Errorf("--vpc-from-kops-cluster and --zones cannot be used at the same time")
createOrImportVPC := func() error {
subnetsGiven := len(*subnets[api.SubnetTopologyPrivate])+len(*subnets[api.SubnetTopologyPublic]) != 0

subnetInfo := func() string {
return fmt.Sprintf("VPC (%s) and subnets (private:%v public:%v)",
cfg.VPC.ID, cfg.SubnetIDs(api.SubnetTopologyPrivate), cfg.SubnetIDs(api.SubnetTopologyPublic))
}
kw, err := kops.NewWrapper(cfg.Region, kopsClusterNameForVPC)
if err != nil {
return err

customNetworkingNotice := "custom VPC/subnets will be used; if resulting cluster doesn't function as expected, make sure to review the configuration of VPC/subnets"

canUseForPrivateNodeGroup := func() error {
if ng.PrivateNetworking && !cfg.HasSufficientPrivateSubnets() {
return fmt.Errorf("none or too few private subnets to use with --node-private-networking")
}
return nil
}

if err := kw.UseVPC(cfg); err != nil {
return err
if !subnetsGiven && kopsClusterNameForVPC == "" {
// default: create dedicated VPC
if err := ctl.SetAvailabilityZones(availabilityZones); err != nil {
return err
}
if err := ctl.SetSubnets(); err != nil {
return err
}
return nil
}

if kopsClusterNameForVPC != "" {
// import VPC from a given kops cluster
if len(availabilityZones) != 0 {
return fmt.Errorf("--vpc-from-kops-cluster and --zones cannot be used at the same time")
}

if subnetsGiven {
return fmt.Errorf("--vpc-from-kops-cluster and --vpc-private-subnets/--vpc-public-subnets cannot be used at the same time")
}

kw, err := kops.NewWrapper(cfg.Region, kopsClusterNameForVPC)
if err != nil {
return err
}

if err := kw.UseVPC(cfg); err != nil {
return err
}

if err := canUseForPrivateNodeGroup(); err != nil {
return err
}

logger.Success("using %s from kops cluster %q", subnetInfo(), kopsClusterNameForVPC)
logger.Warning(customNetworkingNotice)
return nil
}

// use subnets as specified by --vpc-{private,public}-subnets flags

if len(availabilityZones) != 0 {
return fmt.Errorf("--vpc-private-subnets/--vpc-public-subnets and --zones cannot be used at the same time")
}

for topology := range subnets {
if err := ctl.UseSubnets(topology, *subnets[topology]); err != nil {
return err
}
}
logger.Success("using VPC (%s) and subnets (%v) from kops cluster %q", cfg.VPC.ID, cfg.SubnetIDs(api.SubnetTopologyPublic), kopsClusterNameForVPC)
} else {
// kw.UseVPC() sets AZs based on subenets used
if err := ctl.SetAvailabilityZones(availabilityZones); err != nil {

if err := cfg.HasSufficientSubnets(); err != nil {
logger.Critical("unable to use given %s", subnetInfo())
return err
}
if err := ctl.SetSubnets(); err != nil {

if err := canUseForPrivateNodeGroup(); err != nil {
return err
}

logger.Success("using existing %s", subnetInfo())
logger.Warning(customNetworkingNotice)
return nil
}

if err := createOrImportVPC(); err != nil {
return err
}

if err := ctl.EnsureAMI(ng); err != nil {
Expand Down
31 changes: 31 additions & 0 deletions pkg/eks/vpc.go
Expand Up @@ -2,6 +2,10 @@ package eks

import (
"fmt"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"

"github.com/kubicorn/kubicorn/pkg/logger"
"github.com/weaveworks/eksctl/pkg/eks/api"
Expand Down Expand Up @@ -48,3 +52,30 @@ func (c *ClusterProvider) SetSubnets() error {

return nil
}

// UseSubnets imports
func (c *ClusterProvider) UseSubnets(topology api.SubnetTopology, subnetIDs []string) error {
if len(subnetIDs) == 0 {
return nil
}
input := &ec2.DescribeSubnetsInput{
SubnetIds: aws.StringSlice(subnetIDs),
}
output, err := c.Provider.EC2().DescribeSubnets(input)
if err != nil {
return err
}

for _, subnet := range output.Subnets {
if c.Spec.VPC.ID == "" {
c.Spec.VPC.ID = *subnet.VpcId
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to get the CIDR for the VPC (as DNS IP depends on this), and we already have them for each of the subnets too (so might as well set them here).

} else if c.Spec.VPC.ID != *subnet.VpcId {
return fmt.Errorf("given subnets (%s) are not in the same VPC", strings.Join(subnetIDs, ", "))
}

c.Spec.ImportSubnet(topology, *subnet.AvailabilityZone, *subnet.SubnetId)
c.Spec.AppendAvailabilityZone(*subnet.AvailabilityZone)
}

return nil
}