Skip to content
This repository was archived by the owner on Sep 30, 2020. It is now read-only.

Commit 2960930

Browse files
authored
Merge pull request #760 from mumoshu/vpc-igw-x-stack-ref
Support cross-stack references of VPC, IGW
2 parents f94860c + d15a9a8 commit 2960930

File tree

9 files changed

+264
-113
lines changed

9 files changed

+264
-113
lines changed

core/controlplane/cluster/cluster.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,24 +54,32 @@ type ec2Service interface {
5454
}
5555

5656
func (c *ClusterRef) validateExistingVPCState(ec2Svc ec2Service) error {
57-
if c.VPCID == "" {
57+
if !c.VPC.HasIdentifier() {
5858
//The VPC will be created. No existing state to validate
5959
return nil
6060
}
6161

62+
// TODO kube-aws should de-reference the vpc id from the stack output and continue validating with it
63+
if c.VPC.IDFromStackOutput != "" {
64+
fmt.Printf("kube-aws doesn't support validating the vpc referenced by the stack output `%s`. Skipped validation of exsiting vpc state. The cluster creation may fail afterwards if the VPC isn't configured properly.", c.VPC.IDFromStackOutput)
65+
return nil
66+
}
67+
68+
vpcId := c.VPC.ID
69+
6270
describeVpcsInput := ec2.DescribeVpcsInput{
63-
VpcIds: []*string{aws.String(c.VPCID)},
71+
VpcIds: []*string{aws.String(vpcId)},
6472
}
6573
vpcOutput, err := ec2Svc.DescribeVpcs(&describeVpcsInput)
6674
if err != nil {
6775
return fmt.Errorf("error describing existing vpc: %v", err)
6876
}
6977
if len(vpcOutput.Vpcs) == 0 {
70-
return fmt.Errorf("could not find vpc %s in region %s", c.VPCID, c.Region)
78+
return fmt.Errorf("could not find vpc %s in region %s", vpcId, c.Region)
7179
}
7280
if len(vpcOutput.Vpcs) > 1 {
7381
//Theoretically this should never happen. If it does, we probably want to know.
74-
return fmt.Errorf("found more than one vpc with id %s. this is NOT NORMAL", c.VPCID)
82+
return fmt.Errorf("found more than one vpc with id %s. this is NOT NORMAL", vpcId)
7583
}
7684

7785
existingVPC := vpcOutput.Vpcs[0]

core/controlplane/config/config.go

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ func (c *Cluster) Load() error {
228228

229229
c.HostedZoneID = withHostedZoneIDPrefix(c.HostedZoneID)
230230

231+
c.ConsumeDeprecatedKeys()
232+
231233
if err := c.valid(); err != nil {
232234
return fmt.Errorf("invalid cluster: %v", err)
233235
}
@@ -259,6 +261,19 @@ func (c *Cluster) Load() error {
259261
return nil
260262
}
261263

264+
func (c *Cluster) ConsumeDeprecatedKeys() {
265+
// TODO Remove in v0.9.9-rc.1
266+
if c.DeprecatedVPCID != "" {
267+
fmt.Println("WARN: vpcId is deprecated and will be removed in v0.9.9. Please use vpc.id instead")
268+
c.VPC.ID = c.DeprecatedVPCID
269+
}
270+
271+
if c.DeprecatedInternetGatewayID != "" {
272+
fmt.Println("WARN: internetGatewayId is deprecated and will be removed in v0.9.9. Please use internetGateway.id instead")
273+
c.InternetGateway.ID = c.DeprecatedInternetGatewayID
274+
}
275+
}
276+
262277
func (c *Cluster) SetDefaults() {
263278
// For backward-compatibility
264279
if len(c.Subnets) == 0 {
@@ -380,15 +395,17 @@ type ComputedDeploymentSettings struct {
380395
// Though it is highly configurable, it's basically users' responsibility to provide `correct` values if they're going beyond the defaults.
381396
type DeploymentSettings struct {
382397
ComputedDeploymentSettings
383-
ClusterName string `yaml:"clusterName,omitempty"`
384-
KeyName string `yaml:"keyName,omitempty"`
385-
Region model.Region `yaml:",inline"`
386-
AvailabilityZone string `yaml:"availabilityZone,omitempty"`
387-
ReleaseChannel string `yaml:"releaseChannel,omitempty"`
388-
AmiId string `yaml:"amiId,omitempty"`
389-
VPCID string `yaml:"vpcId,omitempty"`
390-
InternetGatewayID string `yaml:"internetGatewayId,omitempty"`
391-
RouteTableID string `yaml:"routeTableId,omitempty"`
398+
ClusterName string `yaml:"clusterName,omitempty"`
399+
KeyName string `yaml:"keyName,omitempty"`
400+
Region model.Region `yaml:",inline"`
401+
AvailabilityZone string `yaml:"availabilityZone,omitempty"`
402+
ReleaseChannel string `yaml:"releaseChannel,omitempty"`
403+
AmiId string `yaml:"amiId,omitempty"`
404+
DeprecatedVPCID string `yaml:"vpcId,omitempty"`
405+
VPC model.VPC `yaml:"vpc,omitempty"`
406+
DeprecatedInternetGatewayID string `yaml:"internetGatewayId,omitempty"`
407+
InternetGateway model.InternetGateway `yaml:"internetGateway,omitempty"`
408+
RouteTableID string `yaml:"routeTableId,omitempty"`
392409
// Required for validations like e.g. if instance cidr is contained in vpc cidr
393410
VPCCIDR string `yaml:"vpcCIDR,omitempty"`
394411
InstanceCIDR string `yaml:"instanceCIDR,omitempty"`
@@ -811,28 +828,35 @@ func (c Cluster) EtcdIndexEnvVarName() string {
811828
return "KUBE_AWS_ETCD_INDEX"
812829
}
813830

814-
func (c Config) VPCLogicalName() string {
815-
return vpcLogicalName
831+
func (c Config) VPCLogicalName() (string, error) {
832+
if c.VPC.HasIdentifier() {
833+
return "", fmt.Errorf("[BUG] .VPCLogicalName should not be called in stack template when vpc id is specified")
834+
}
835+
return vpcLogicalName, nil
816836
}
817837

818-
func (c Config) VPCRef() string {
819-
if c.VPCID != "" {
820-
return fmt.Sprintf("%q", c.VPCID)
821-
} else {
822-
return fmt.Sprintf(`{ "Ref" : %q }`, c.VPCLogicalName())
838+
func (c Config) VPCID() (string, error) {
839+
fmt.Println("WARN: .VPCID in stack template is deprecated and will be removed in v0.9.9. Please use .VPC.ID instead")
840+
if !c.VPC.HasIdentifier() {
841+
return "", fmt.Errorf("[BUG] .VPCID should not be called in stack template when vpc.id(FromStackOutput) is specified. Use .VPCManaged instead.")
823842
}
843+
return c.VPC.ID, nil
844+
}
845+
846+
func (c Config) VPCManaged() bool {
847+
return !c.VPC.HasIdentifier()
848+
}
849+
850+
func (c Config) VPCRef() (string, error) {
851+
return c.VPC.RefOrError(c.VPCLogicalName)
824852
}
825853

826854
func (c Config) InternetGatewayLogicalName() string {
827855
return internetGatewayLogicalName
828856
}
829857

830858
func (c Config) InternetGatewayRef() string {
831-
if c.InternetGatewayID != "" {
832-
return fmt.Sprintf("%q", c.InternetGatewayID)
833-
} else {
834-
return fmt.Sprintf(`{ "Ref" : %q }`, c.InternetGatewayLogicalName())
835-
}
859+
return c.InternetGateway.Ref(c.InternetGatewayLogicalName)
836860
}
837861

838862
// ExternalDNSNames returns all the DNS names of Kubernetes API endpoints should be covered in the TLS cert for k8s API
@@ -1025,8 +1049,8 @@ func (c DeploymentSettings) Valid() (*DeploymentValidationResult, error) {
10251049
return nil, errors.New("kmsKeyArn must be set")
10261050
}
10271051

1028-
if c.VPCID == "" && (c.RouteTableID != "" || c.InternetGatewayID != "") {
1029-
return nil, errors.New("vpcId must be specified if routeTableId or internetGatewayId are specified")
1052+
if !c.VPC.HasIdentifier() && (c.RouteTableID != "" || c.InternetGateway.HasIdentifier()) {
1053+
return nil, errors.New("vpc id must be specified if route table id or internet gateway id are specified")
10301054
}
10311055

10321056
if c.Region.IsEmpty() {
@@ -1098,21 +1122,21 @@ func (c DeploymentSettings) Valid() (*DeploymentValidationResult, error) {
10981122
return nil, fmt.Errorf("either subnets[].routeTable.id(%s) or routeTableId(%s) but not both can be specified", subnet.RouteTableID(), c.RouteTableID)
10991123
}
11001124

1101-
if subnet.ManageSubnet() && (subnet.Public() && c.MapPublicIPs) && c.VPCID != "" && (subnet.ManageRouteTable() && c.RouteTableID == "") && c.InternetGatewayID == "" {
1102-
return nil, errors.New("internetGatewayId can't be omitted when there're one or more managed public subnets in an existing VPC")
1125+
if subnet.ManageSubnet() && (subnet.Public() && c.MapPublicIPs) && c.VPC.HasIdentifier() && (subnet.ManageRouteTable() && c.RouteTableID == "") && !c.InternetGateway.HasIdentifier() {
1126+
return nil, errors.New("internet gateway id can't be omitted when there're one or more managed public subnets in an existing VPC")
11031127
}
11041128
}
11051129

11061130
// All the subnets are explicitly/implicitly(they're public by default) configured to be "public".
11071131
// They're also configured to reuse existing route table(s).
11081132
// However, the IGW, which won't be applied to anywhere, is specified
1109-
if (allPublic && c.MapPublicIPs) && (c.RouteTableID != "" || allExistingRouteTable) && c.InternetGatewayID != "" {
1110-
return nil, errors.New("internetGatewayId can't be specified when all the public subnets have existing route tables associated. kube-aws doesn't try to modify an exisinting route table to include a route to the internet gateway")
1133+
if (allPublic && c.MapPublicIPs) && (c.RouteTableID != "" || allExistingRouteTable) && c.InternetGateway.HasIdentifier() {
1134+
return nil, errors.New("internet gateway id can't be specified when all the public subnets have existing route tables associated. kube-aws doesn't try to modify an exisinting route table to include a route to the internet gateway")
11111135
}
11121136

11131137
// All the subnets are explicitly configured to be "private" but the IGW, which won't be applied anywhere, is specified
1114-
if (allPrivate || !c.MapPublicIPs) && c.InternetGatewayID != "" {
1115-
return nil, errors.New("internetGatewayId can't be spcified when all the subnets are existing private subnets")
1138+
if (allPrivate || !c.MapPublicIPs) && c.InternetGateway.HasIdentifier() {
1139+
return nil, errors.New("internet gateway id can't be specified when all the subnets are existing private subnets")
11161140
}
11171141

11181142
if c.RouteTableID != "" && !allPublic && !allPrivate {

core/controlplane/config/templates/cluster.yaml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,12 +676,26 @@ worker:
676676

677677
## Networking config
678678

679-
# ID of existing VPC to create subnet in. Leave blank to create a new VPC
679+
# CAUTION: Deprecated and will be removed in v0.9.9. Please use vpc.id instead
680680
# vpcId:
681681

682-
# ID of existing Internet Gateway to associate subnet with. Leave blank to create a new Internet Gateway
682+
#vpc:
683+
# # ID of existing VPC to create subnet in. Leave blank to create a new VPC
684+
# id:
685+
# # Exported output's name from another stack
686+
# # Only specify either id or idFromStackOutput but not both
687+
# #idFromStackOutput: myinfra-Vpc
688+
689+
# CAUTION: Deprecated and will be removed in v0.9.9. Please use internetGateway.id instead
683690
# internetGatewayId:
684691

692+
#internetGateway:
693+
# # ID of existing Internet Gateway to associate subnet with. Leave blank to create a new Internet Gateway
694+
# id:
695+
# # Exported output's name from another stack
696+
# # Only specify either id or idFromStackOutput but not both
697+
# #idFromStackOutput: myinfra-igw
698+
685699
# Advanced: ID of existing route table in existing VPC to attach subnet to.
686700
# Leave blank to use the VPC's main route table.
687701
# This should be specified if and only if vpcId is specified.

core/controlplane/config/templates/stack-template.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,7 +1484,7 @@
14841484
{{end}}
14851485
{{end}}
14861486

1487-
{{if not .VPCID}}
1487+
{{if .VPCManaged}}
14881488
,
14891489
"{{.InternetGatewayLogicalName}}": {
14901490
"Properties": {
@@ -1528,7 +1528,7 @@
15281528
},
15291529

15301530
"Outputs": {
1531-
{{if not .VPCID}}
1531+
{{if .VPCManaged}}
15321532
"VPC" : {
15331533
"Description" : "The VPC managed by this stack",
15341534
"Value" : { "Ref" : "{{.VPCLogicalName}}" },

core/nodepool/config/config.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,16 @@ func (c ProvidedConfig) StackNameEnvVarName() string {
349349
return "KUBE_AWS_STACK_NAME"
350350
}
351351

352-
func (c ProvidedConfig) VPCRef() string {
353-
//This means this VPC already exists, and we can reference it directly by ID
354-
if c.VPCID != "" {
355-
return fmt.Sprintf("%q", c.VPCID)
356-
}
357-
return `{"Fn::ImportValue" : {"Fn::Sub" : "${ControlPlaneStackName}-VPC"}}`
352+
func (c ProvidedConfig) VPCRef() (string, error) {
353+
igw := c.InternetGateway
354+
// When HasIdentifier returns true, it means the VPC already exists, and we can reference it directly by ID
355+
if !c.VPC.HasIdentifier() {
356+
// Otherwise import the VPC ID from the control-plane stack
357+
igw.IDFromStackOutput = `{"Fn::Sub" : "${ControlPlaneStackName}-VPC"}`
358+
}
359+
return igw.RefOrError(func() (string, error) {
360+
return "", fmt.Errorf("[BUG] Tried to reference VPC by its logical name")
361+
})
358362
}
359363

360364
func (c ProvidedConfig) SecurityGroupRefs() []string {

core/nodepool/config/deployment.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ func (c DeploymentSettings) ValidateInputs() error {
1010
// By design, kube-aws doesn't allow customizing the following settings among node pools.
1111
//
1212
// Every node pool imports subnets from the main stack and therefore there's no need for setting:
13-
// * VPCID
14-
// * InternetGatewayID
13+
// * VPC.ID(FromStackOutput)
14+
// * InternetGateway.ID(FromStackOutput)
1515
// * RouteTableID
1616
// * VPCCIDR
1717
// * InstanceCIDR
1818
// * MapPublicIPs
1919
// * ElasticFileSystemID
20-
if c.VPCID != "" {
21-
return fmt.Errorf("although you can't customize `vpcId` per node pool but you did specify \"%s\" in your cluster.yaml", c.VPCID)
20+
if c.VPC.HasIdentifier() {
21+
return fmt.Errorf("although you can't customize VPC per node pool but you did specify \"%v\" in your cluster.yaml", c.VPC)
2222
}
23-
if c.InternetGatewayID != "" {
24-
return fmt.Errorf("although you can't customize `internetGatewayId` per node pool but you did specify \"%s\" in your cluster.yaml", c.InternetGatewayID)
23+
if c.InternetGateway.HasIdentifier() {
24+
return fmt.Errorf("although you can't customize internet gateway per node pool but you did specify \"%v\" in your cluster.yaml", c.InternetGateway)
2525
}
2626
if c.RouteTableID != "" {
2727
return fmt.Errorf("although you can't customize `routeTableId` per node pool but you did specify \"%s\" in your cluster.yaml", c.RouteTableID)

e2e/run

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ customize_cluster_yaml() {
149149
echo Writing to $(pwd)/cluster.yaml
150150

151151
if [ "${KUBE_AWS_DEPLOY_TO_EXISTING_VPC}" != "" ]; then
152-
echo "vpcId: $(testinfra_vpc)" >> cluster.yaml
153-
echo "routeTableId: $(testinfra_public_routetable)" >> cluster.yaml
152+
echo -e "vpc:\n id: $(testinfra_vpc)" >> cluster.yaml
153+
echo -e "routeTableId: $(testinfra_public_routetable)" >> cluster.yaml
154154
echo -e "workerSecurityGroupIds:\n- $(testinfra_glue_sg)" >> cluster.yaml
155155
fi
156156

model/vpc.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package model
2+
3+
// kube-aws manages at most one VPC per cluster
4+
// If ID or IDFromStackOutput is non-zero, kube-aws doesn't manage the VPC but its users' responsibility to
5+
// provide properly configured one to be reused by kube-aws.
6+
// More concretely:
7+
// * If an user is going to reuse an existing VPC, it must have an internet gateway attached and
8+
// * A valid internet gateway ID must be provided via `internetGateway.id` or `internetGateway.idFromStackOutput`.
9+
// In other words, kube-aws doesn't create an internet gateway in an existing VPC.
10+
type VPC struct {
11+
Identifier `yaml:",inline"`
12+
}

0 commit comments

Comments
 (0)