package awsup import ( "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elb" "github.com/aws/aws-sdk-go/service/iam" "github.com/golang/glog" "k8s.io/kube-deploy/upup/pkg/fi" "strings" ) type AWSCloud struct { EC2 *ec2.EC2 IAM *iam.IAM ELB *elb.ELB Autoscaling *autoscaling.AutoScaling Region string tags map[string]string } var _ fi.Cloud = &AWSCloud{} func (c *AWSCloud) ProviderID() fi.CloudProviderID { return fi.CloudProviderAWS } func NewAWSCloud(region string, tags map[string]string) (*AWSCloud, error) { c := &AWSCloud{Region: region} config := aws.NewConfig().WithRegion(region) c.EC2 = ec2.New(session.New(), config) c.IAM = iam.New(session.New(), config) c.ELB = elb.New(session.New(), config) c.Autoscaling = autoscaling.New(session.New(), config) c.tags = tags return c, nil } func NewEC2Filter(name string, values ...string) *ec2.Filter { awsValues := []*string{} for _, value := range values { awsValues = append(awsValues, aws.String(value)) } filter := &ec2.Filter{ Name: aws.String(name), Values: awsValues, } return filter } func (c *AWSCloud) Tags() map[string]string { // Defensive copy tags := make(map[string]string) for k, v := range c.tags { tags[k] = v } return tags } func (c *AWSCloud) GetTags(resourceId string) (map[string]string, error) { tags := map[string]string{} request := &ec2.DescribeTagsInput{ Filters: []*ec2.Filter{ NewEC2Filter("resource-id", resourceId), }, } response, err := c.EC2.DescribeTags(request) if err != nil { return nil, fmt.Errorf("error listing tags on %v: %v", resourceId, err) } for _, tag := range response.Tags { if tag == nil { glog.Warning("unexpected nil tag") continue } tags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value) } return tags, nil } func (c *AWSCloud) CreateTags(resourceId string, tags map[string]string) error { if len(tags) == 0 { return nil } ec2Tags := []*ec2.Tag{} for k, v := range tags { ec2Tags = append(ec2Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)}) } request := &ec2.CreateTagsInput{ Tags: ec2Tags, Resources: []*string{&resourceId}, } _, err := c.EC2.CreateTags(request) if err != nil { return fmt.Errorf("error creating tags on %v: %v", resourceId, err) } return nil } func (c *AWSCloud) BuildTags(name *string) map[string]string { tags := make(map[string]string) if name != nil { tags["Name"] = *name } else { glog.Warningf("Name not set when filtering by name") } for k, v := range c.tags { tags[k] = v } return tags } func (c *AWSCloud) BuildFilters(name *string) []*ec2.Filter { filters := []*ec2.Filter{} merged := make(map[string]string) if name != nil { merged["Name"] = *name } else { glog.Warningf("Name not set when filtering by name") } for k, v := range c.tags { merged[k] = v } for k, v := range merged { filter := NewEC2Filter("tag:"+k, v) filters = append(filters, filter) } return filters } // DescribeInstance is a helper that queries for the specified instance by id func (t *AWSCloud) DescribeInstance(instanceID string) (*ec2.Instance, error) { glog.V(2).Infof("Calling DescribeInstances for instance %q", instanceID) request := &ec2.DescribeInstancesInput{ InstanceIds: []*string{&instanceID}, } response, err := t.EC2.DescribeInstances(request) if err != nil { return nil, fmt.Errorf("error listing Instances: %v", err) } if response == nil || len(response.Reservations) == 0 { return nil, nil } if len(response.Reservations) != 1 { glog.Fatalf("found multiple Reservations for instance id") } reservation := response.Reservations[0] if len(reservation.Instances) == 0 { return nil, nil } if len(reservation.Instances) != 1 { return nil, fmt.Errorf("found multiple Instances for instance id") } instance := reservation.Instances[0] return instance, nil } // DescribeVPC is a helper that queries for the specified vpc by id func (t *AWSCloud) DescribeVPC(vpcID string) (*ec2.Vpc, error) { glog.V(2).Infof("Calilng DescribeVPC for VPC %q", vpcID) request := &ec2.DescribeVpcsInput{ VpcIds: []*string{&vpcID}, } response, err := t.EC2.DescribeVpcs(request) if err != nil { return nil, fmt.Errorf("error listing VPCs: %v", err) } if response == nil || len(response.Vpcs) == 0 { return nil, nil } if len(response.Vpcs) != 1 { return nil, fmt.Errorf("found multiple VPCs for instance id") } vpc := response.Vpcs[0] return vpc, nil } // ResolveImage finds an AMI image based on the given name. // The name can be one of: // `ami-...` in which case it is presumed to be an id // owner/name in which case we find the image with the specified name, owned by owner // name in which case we find the image with the specified name, with the current owner func (c *AWSCloud) ResolveImage(name string) (*ec2.Image, error) { // TODO: Cache this result during a single execution (we get called multiple times) glog.V(2).Infof("Calilng DescribeImages to resolve name %q", name) request := &ec2.DescribeImagesInput{} if strings.HasPrefix(name, "ami-") { // ami-xxxxxxxx request.ImageIds = []*string{&name} } else { // Either or / tokens := strings.Split(name, "/") if len(tokens) == 1 { // self is a well-known value in the DescribeImages call request.Owners = aws.StringSlice([]string{"self"}) request.Filters = append(request.Filters, NewEC2Filter("name", name)) } else if len(tokens) == 2 { request.Owners = []*string{&tokens[0]} request.Filters = append(request.Filters, NewEC2Filter("name", tokens[1])) } else { return nil, fmt.Errorf("image name specification not recognized: %q", name) } } response, err := c.EC2.DescribeImages(request) if err != nil { return nil, fmt.Errorf("error listing images: %v", err) } if response == nil || len(response.Images) == 0 { return nil, fmt.Errorf("could not find Image for %q", name) } if len(response.Images) != 1 { return nil, fmt.Errorf("found multiple Images for %q", name) } image := response.Images[0] return image, nil }