diff --git a/provisioner/aws.go b/provisioner/aws.go index 841605a4..4696f4fe 100644 --- a/provisioner/aws.go +++ b/provisioner/aws.go @@ -773,6 +773,7 @@ type EKSClusterDetails struct { Endpoint string CertificateAuthority string OIDCIssuerURL string + ServiceIPv6CIDR string } func (a *awsAdapter) GetEKSClusterDetails(cluster *api.Cluster) (*EKSClusterDetails, error) { @@ -787,5 +788,6 @@ func (a *awsAdapter) GetEKSClusterDetails(cluster *api.Cluster) (*EKSClusterDeta Endpoint: aws.StringValue(resp.Cluster.Endpoint), CertificateAuthority: aws.StringValue(resp.Cluster.CertificateAuthority.Data), OIDCIssuerURL: aws.StringValue(resp.Cluster.Identity.Oidc.Issuer), + ServiceIPv6CIDR: aws.StringValue(resp.Cluster.KubernetesNetworkConfig.ServiceIpv6Cidr), }, nil } diff --git a/provisioner/aws_az.go b/provisioner/aws_az.go index 6cd31a37..886363b0 100644 --- a/provisioner/aws_az.go +++ b/provisioner/aws_az.go @@ -4,14 +4,20 @@ import ( "sort" ) +// SubnetInfo has information about a subnet. +type SubnetInfo struct { + SubnetID string + SubnetIPV6CIDRs []string +} + // AZInfo tracks information about available AZs based on explicit restrictions or available subnets type AZInfo struct { - subnets map[string]string + subnets map[string]SubnetInfo } // RestrictAZs returns a new AZInfo that is restricted to provided AZs func (info *AZInfo) RestrictAZs(availableAZs []string) *AZInfo { - result := make(map[string]string) + result := make(map[string]SubnetInfo) for _, az := range availableAZs { if subnet, ok := info.subnets[az]; ok { @@ -28,17 +34,26 @@ func (info *AZInfo) SubnetsByAZ() map[string]string { result := make(map[string]string) for _, az := range info.AvailabilityZones() { subnet := info.subnets[az] - result[az] = subnet + result[az] = subnet.SubnetID if existing, ok := result[subnetAllAZName]; ok { - result[subnetAllAZName] = existing + "," + subnet + result[subnetAllAZName] = existing + "," + subnet.SubnetID } else { - result[subnetAllAZName] = subnet + result[subnetAllAZName] = subnet.SubnetID } } return result } +// SubnetIPV6CIDRs returns a list of available subnet IPV6 CIDRs. +func (info *AZInfo) SubnetIPV6CIDRs() []string { + var result []string + for _, subnetInfo := range info.subnets { + result = append(result, subnetInfo.SubnetIPV6CIDRs...) + } + return result +} + // AvailabilityZones returns a list of available AZs func (info *AZInfo) AvailabilityZones() []string { var result []string diff --git a/provisioner/aws_az_test.go b/provisioner/aws_az_test.go index 99dd4cfd..b224f7f6 100644 --- a/provisioner/aws_az_test.go +++ b/provisioner/aws_az_test.go @@ -8,10 +8,16 @@ import ( var ( info = &AZInfo{ - subnets: map[string]string{ - "eu-central-1a": "subnet-1a", - "eu-central-1b": "subnet-1b", - "eu-central-1c": "subnet-1c", + subnets: map[string]SubnetInfo{ + "eu-central-1a": SubnetInfo{ + SubnetID: "subnet-1a", + }, + "eu-central-1b": SubnetInfo{ + SubnetID: "subnet-1b", + }, + "eu-central-1c": SubnetInfo{ + SubnetID: "subnet-1c", + }, }, } ) diff --git a/provisioner/clusterpy.go b/provisioner/clusterpy.go index 6237a8b3..37e20c76 100644 --- a/provisioner/clusterpy.go +++ b/provisioner/clusterpy.go @@ -45,6 +45,7 @@ const ( stackTagValueTrue = "true" subnetsConfigItemKey = "subnets" subnetsValueKey = "subnets" + subnetIPV6CIDRsKey = "subnet_ipv6_cidrs" availabilityZonesConfigItemKey = "availability_zones" availabilityZonesValueKey = "availability_zones" vpcIDConfigItemKey = "vpc_id" @@ -251,6 +252,7 @@ func (p *clusterpyProvisioner) provision( values := map[string]interface{}{ subnetsValueKey: azInfo.SubnetsByAZ(), availabilityZonesValueKey: azInfo.AvailabilityZones(), + subnetIPV6CIDRsKey: strings.Join(azInfo.SubnetIPV6CIDRs(), ","), "hosted_zone": hostedZone, "load_balancer_certificate": loadBalancerCert.ID(), "vpc_ipv4_cidr": aws.StringValue(vpc.CidrBlock), @@ -303,7 +305,6 @@ func (p *clusterpyProvisioner) provision( postOptions, err = p.hook.Execute( awsAdapter, cluster, - outputs, ) if err != nil { return err @@ -312,11 +313,8 @@ func (p *clusterpyProvisioner) provision( if postOptions.APIServerURL != "" { cluster.APIServerURL = postOptions.APIServerURL } - if postOptions.AZInfo != nil { - azInfo = postOptions.AZInfo - } - for k, v := range postOptions.TemplateValues { - values[k] = v + if postOptions.ServiceIPv6CIDR != "" { + cluster.ConfigItems["service_ipv6_cidr"] = postOptions.ServiceIPv6CIDR } } @@ -648,9 +646,15 @@ func selectSubnetIDs(subnets []*ec2.Subnet) *AZInfo { } } - result := make(map[string]string, len(subnetsByAZ)) + result := make(map[string]SubnetInfo, len(subnetsByAZ)) for az, subnet := range subnetsByAZ { - result[az] = aws.StringValue(subnet.SubnetId) + subnetInfo := SubnetInfo{ + SubnetID: aws.StringValue(subnet.SubnetId), + } + for _, ipv6Cidr := range subnet.Ipv6CidrBlockAssociationSet { + subnetInfo.SubnetIPV6CIDRs = append(subnetInfo.SubnetIPV6CIDRs, aws.StringValue(ipv6Cidr.Ipv6CidrBlock)) + } + result[az] = subnetInfo } return &AZInfo{subnets: result} diff --git a/provisioner/provisioner.go b/provisioner/provisioner.go index 6d0c2b2a..692bd01b 100644 --- a/provisioner/provisioner.go +++ b/provisioner/provisioner.go @@ -46,7 +46,6 @@ type ( Execute( adapter awsInterface, cluster *api.Cluster, - cloudFormationOutput map[string]string, ) ( *HookResponse, error, @@ -56,10 +55,9 @@ type ( // HookResponse contain configuration parameters that a provisioner can use // at a later stage. HookResponse struct { - APIServerURL string - AZInfo *AZInfo - CAData []byte - TemplateValues map[string]interface{} + APIServerURL string + CAData []byte + ServiceIPv6CIDR string } // Options is the options that can be passed to a provisioner when initialized. diff --git a/provisioner/template.go b/provisioner/template.go index 727bce12..aa3ab98d 100644 --- a/provisioner/template.go +++ b/provisioner/template.go @@ -116,6 +116,7 @@ func renderTemplate(context *templateContext, file string) (string, error) { }, "nodeCIDRMaxNodesPodCIDR": nodeCIDRMaxNodes, "nodeCIDRMaxPods": nodeCIDRMaxPods, + "addressNFromIPV6CIDR": addressNFromIPV6CIDR, "parseInt64": parseInt64, "generateJWKSDocument": generateJWKSDocument, "generateOIDCDiscoveryDocument": generateOIDCDiscoveryDocument, @@ -581,6 +582,26 @@ func nodeCIDRMaxPods(maskSize int64, extraCapacity int64) (int64, error) { return maxPods, nil } +// addressNFromIPV6CIDR takes an IPv6 CIDR and returns the Nth address in the +// subnet. +func addressNFromIPV6CIDR(cidr string, n int) (string, error) { + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return "", err + } + + ip := ipNet.IP + ip = ip.To16() + if ip == nil { + return "", fmt.Errorf("invalid IP address: %s", ipNet.IP) + } + ip = ip.Mask(ipNet.Mask) + for i := 0; i < n; i++ { + ip[15]++ + } + return ip.String(), nil +} + func kubernetesSizeToKiloBytes(quantity string, scale float64) (string, error) { resource, err := k8sresource.ParseQuantity(quantity) if err != nil { diff --git a/provisioner/zalando_eks.go b/provisioner/zalando_eks.go index a1cf9047..a66d41cd 100644 --- a/provisioner/zalando_eks.go +++ b/provisioner/zalando_eks.go @@ -164,7 +164,6 @@ func NewZalandoEKSCreationHook( func (z *ZalandoEKSCreationHook) Execute( adapter awsInterface, cluster *api.Cluster, - cloudFormationOutput map[string]string, ) (*HookResponse, error) { res := &HookResponse{} @@ -194,6 +193,8 @@ func (z *ZalandoEKSCreationHook) Execute( toUpdate[KeyEKSOIDCIssuerURL] = clusterDetails.OIDCIssuerURL } + // TODO: Get ipv6 service CIDR + err = z.clusterRegistry.UpdateConfigItems(cluster, toUpdate) if err != nil { return nil, err @@ -201,25 +202,7 @@ func (z *ZalandoEKSCreationHook) Execute( res.APIServerURL = clusterDetails.Endpoint res.CAData = decodedCA - - subnets := map[string]string{} - for key, az := range map[string]string{ - "EKSSubneta": "eu-central-1a", - "EKSSubnetb": "eu-central-1b", - "EKSSubnetc": "eu-central-1c", - } { - if v, ok := cloudFormationOutput[key]; ok { - subnets[az] = v - } - } - if len(subnets) > 0 { - res.AZInfo = &AZInfo{ - subnets: subnets, - } - res.TemplateValues = map[string]interface{}{ - subnetsValueKey: subnets, - } - } + res.ServiceIPv6CIDR = clusterDetails.ServiceIPv6CIDR return res, nil } diff --git a/provisioner/zalando_eks_test.go b/provisioner/zalando_eks_test.go index 3245aabd..3f7b1a4e 100644 --- a/provisioner/zalando_eks_test.go +++ b/provisioner/zalando_eks_test.go @@ -44,31 +44,6 @@ func TestCreationHookExecute(t *testing.T) { cfOutput map[string]string expected *HookResponse }{ - { - cfOutput: map[string]string{ - "EKSSubneta": "subnet-123", - "EKSSubnetb": "subnet-456", - "EKSSubnetc": "subnet-789", - }, - expected: &HookResponse{ - APIServerURL: "https://api.cluster.local", - CAData: []byte("blah"), - AZInfo: &AZInfo{ - subnets: map[string]string{ - "eu-central-1a": "subnet-123", - "eu-central-1b": "subnet-456", - "eu-central-1c": "subnet-789", - }, - }, - TemplateValues: map[string]interface{}{ - subnetsValueKey: map[string]string{ - "eu-central-1a": "subnet-123", - "eu-central-1b": "subnet-456", - "eu-central-1c": "subnet-789", - }, - }, - }, - }, { cfOutput: map[string]string{}, expected: &HookResponse{