mirror of https://github.com/kubernetes/kops.git
287 lines
7.5 KiB
Go
287 lines
7.5 KiB
Go
/*
|
|
Copyright 2016 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package awstasks
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
|
"github.com/golang/glog"
|
|
"k8s.io/kops/upup/pkg/fi"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
|
|
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
|
|
)
|
|
|
|
//go:generate fitask -type=NatGateway
|
|
type NatGateway struct {
|
|
Name *string
|
|
ElasticIP *ElasticIP
|
|
Subnet *Subnet
|
|
ID *string
|
|
|
|
// We can't tag NatGateways, so we have to find through a surrogate
|
|
AssociatedRouteTable *RouteTable
|
|
}
|
|
|
|
var _ fi.CompareWithID = &NatGateway{} // Validate the IDs
|
|
|
|
func (e *NatGateway) CompareWithID() *string {
|
|
return e.ID
|
|
}
|
|
|
|
func (e *NatGateway) Find(c *fi.Context) (*NatGateway, error) {
|
|
ngw, err := e.findNatGateway(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ngw == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
actual := &NatGateway{
|
|
ID: ngw.NatGatewayId,
|
|
}
|
|
actual.Subnet = e.Subnet
|
|
if len(ngw.NatGatewayAddresses) == 0 {
|
|
// Not sure if this ever happens
|
|
actual.ElasticIP = nil
|
|
} else if len(ngw.NatGatewayAddresses) == 1 {
|
|
actual.ElasticIP = &ElasticIP{ID: ngw.NatGatewayAddresses[0].AllocationId}
|
|
} else {
|
|
return nil, fmt.Errorf("found multiple elastic IPs attached to NatGateway %q", aws.StringValue(ngw.NatGatewayId))
|
|
}
|
|
|
|
// NATGateways don't have a Name (no tags), so we set the name to avoid spurious changes
|
|
actual.Name = e.Name
|
|
|
|
actual.AssociatedRouteTable = e.AssociatedRouteTable
|
|
|
|
e.ID = actual.ID
|
|
return actual, nil
|
|
}
|
|
|
|
func (e *NatGateway) findNatGateway(c *fi.Context) (*ec2.NatGateway, error) {
|
|
cloud := c.Cloud.(awsup.AWSCloud)
|
|
|
|
id := e.ID
|
|
|
|
// Find via route on private route table
|
|
if id == nil && e.AssociatedRouteTable != nil {
|
|
ngw, err := findNatGatewayFromRouteTable(cloud, e.AssociatedRouteTable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ngw != nil {
|
|
return ngw, nil
|
|
}
|
|
}
|
|
|
|
// Find via tag on subnet
|
|
// TODO: Obsolete - we can get from the route table instead
|
|
if id == nil && e.Subnet != nil {
|
|
var filters []*ec2.Filter
|
|
filters = append(filters, awsup.NewEC2Filter("key", "AssociatedNatgateway"))
|
|
if e.Subnet.ID == nil {
|
|
glog.V(2).Infof("Unable to find subnet, bypassing Find() for NatGateway")
|
|
return nil, nil
|
|
}
|
|
filters = append(filters, awsup.NewEC2Filter("resource-id", *e.Subnet.ID))
|
|
|
|
request := &ec2.DescribeTagsInput{
|
|
Filters: filters,
|
|
}
|
|
|
|
response, err := cloud.EC2().DescribeTags(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error listing tags: %v", err)
|
|
}
|
|
|
|
if response == nil || len(response.Tags) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
if len(response.Tags) != 1 {
|
|
return nil, fmt.Errorf("found multiple tags for: %v", e)
|
|
}
|
|
t := response.Tags[0]
|
|
id = t.Value
|
|
glog.V(2).Infof("Found NatGateway via subnet tag: %v", *id)
|
|
}
|
|
|
|
if id != nil {
|
|
return findNatGatewayById(cloud, id)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func findNatGatewayById(cloud awsup.AWSCloud, id *string) (*ec2.NatGateway, error) {
|
|
request := &ec2.DescribeNatGatewaysInput{}
|
|
request.NatGatewayIds = []*string{id}
|
|
response, err := cloud.EC2().DescribeNatGateways(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error listing NatGateway %q: %v", id, err)
|
|
}
|
|
|
|
if response == nil || len(response.NatGateways) == 0 {
|
|
glog.V(2).Infof("Unable to find NatGateway %q", id)
|
|
return nil, nil
|
|
}
|
|
if len(response.NatGateways) != 1 {
|
|
return nil, fmt.Errorf("found multiple NatGateways with id %q", id)
|
|
}
|
|
return response.NatGateways[0], nil
|
|
}
|
|
|
|
func findNatGatewayFromRouteTable(cloud awsup.AWSCloud, routeTable *RouteTable) (*ec2.NatGateway, error) {
|
|
// Find via route on private route table
|
|
if routeTable.ID != nil {
|
|
glog.V(2).Infof("trying to match NatGateway via RouteTable %s", routeTable.ID)
|
|
rt, err := routeTable.findEc2RouteTable(cloud)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error finding associated RouteTable to NatGateway: %v", err)
|
|
}
|
|
|
|
if rt != nil {
|
|
var natGatewayIDs []*string
|
|
for _, route := range rt.Routes {
|
|
if route.NatGatewayId != nil {
|
|
natGatewayIDs = append(natGatewayIDs, route.NatGatewayId)
|
|
}
|
|
}
|
|
|
|
if len(natGatewayIDs) == 0 {
|
|
glog.V(2).Infof("no NatGateway found in route table %s", *rt.RouteTableId)
|
|
} else if len(natGatewayIDs) > 1 {
|
|
return nil, fmt.Errorf("found multiple NatGateways in route table %s", *rt.RouteTableId)
|
|
} else {
|
|
return findNatGatewayById(cloud, natGatewayIDs[0])
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *NatGateway) CheckChanges(a, e, changes *NatGateway) error {
|
|
// New
|
|
if a == nil {
|
|
if e.ElasticIP == nil {
|
|
return fi.RequiredField("ElasticIp")
|
|
}
|
|
if e.Subnet == nil {
|
|
return fi.RequiredField("Subnet")
|
|
}
|
|
}
|
|
|
|
// Delta
|
|
if a != nil {
|
|
if changes.ElasticIP != nil {
|
|
return fi.CannotChangeField("ElasticIp")
|
|
}
|
|
if changes.Subnet != nil {
|
|
return fi.CannotChangeField("Subnet")
|
|
}
|
|
if changes.ID != nil {
|
|
return fi.CannotChangeField("ID")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *NatGateway) Run(c *fi.Context) error {
|
|
return fi.DefaultDeltaRunMethod(e, c)
|
|
}
|
|
|
|
func (e *NatGateway) waitAvailable(cloud awsup.AWSCloud) error {
|
|
// It takes 'forever' (up to 5 min...) for a NatGateway to become available after it has been created
|
|
// We have to wait until it is actually up
|
|
|
|
// TODO: Cache availability status
|
|
|
|
id := aws.StringValue(e.ID)
|
|
if id == "" {
|
|
return fmt.Errorf("NAT Gateway %q did not have ID", e.Name)
|
|
}
|
|
|
|
glog.Infof("Waiting for NAT Gateway %q to be available", id)
|
|
params := &ec2.DescribeNatGatewaysInput{
|
|
NatGatewayIds: []*string{e.ID},
|
|
}
|
|
err := cloud.EC2().WaitUntilNatGatewayAvailable(params)
|
|
if err != nil {
|
|
return fmt.Errorf("error waiting for NAT Gateway %q to be available: %v", id, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (_ *NatGateway) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *NatGateway) error {
|
|
// New NGW
|
|
var id *string
|
|
if a == nil {
|
|
glog.V(2).Infof("Creating Nat Gateway")
|
|
|
|
request := &ec2.CreateNatGatewayInput{}
|
|
request.AllocationId = e.ElasticIP.ID
|
|
request.SubnetId = e.Subnet.ID
|
|
response, err := t.Cloud.EC2().CreateNatGateway(request)
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating Nat Gateway: %v", err)
|
|
}
|
|
e.ID = response.NatGateway.NatGatewayId
|
|
id = e.ID
|
|
} else {
|
|
id = a.ID
|
|
}
|
|
|
|
// Tag the associated subnet
|
|
if e.Subnet == nil {
|
|
return fmt.Errorf("Subnet not set")
|
|
} else if e.Subnet.ID == nil {
|
|
return fmt.Errorf("Subnet ID not set")
|
|
}
|
|
|
|
// TODO: Obsolete - we can get from the route table instead
|
|
tags := make(map[string]string)
|
|
tags["AssociatedNatgateway"] = *id
|
|
err := t.AddAWSTags(*e.Subnet.ID, tags)
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to tag subnet %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type terraformNATGateway struct {
|
|
AllocationID *terraform.Literal `json:"allocation_id,omitempty"`
|
|
SubnetID *terraform.Literal `json:"subnet_id,omitempty"`
|
|
}
|
|
|
|
func (_ *NatGateway) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *NatGateway) error {
|
|
tf := &terraformNATGateway{
|
|
AllocationID: e.ElasticIP.TerraformLink(),
|
|
SubnetID: e.Subnet.TerraformLink(),
|
|
}
|
|
|
|
return t.RenderResource("aws_nat_gateway", *e.Name, tf)
|
|
}
|
|
|
|
func (e *NatGateway) TerraformLink() *terraform.Literal {
|
|
return terraform.LiteralProperty("aws_nat_gateway", *e.Name, "id")
|
|
}
|