diff --git a/aws/aws.go b/aws/aws.go index cfc42c03..d49278b7 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -37,193 +37,205 @@ type Region struct { AutoScalingEndpoint string RdsEndpoint string Route53Endpoint string + ECSEndpoint string } var USGovWest = Region{ - "us-gov-west-1", - "https://ec2.us-gov-west-1.amazonaws.com", - "https://s3-fips-us-gov-west-1.amazonaws.com", - "", - true, - true, - "", - "https://sns.us-gov-west-1.amazonaws.com", - "https://sqs.us-gov-west-1.amazonaws.com", - "https://iam.us-gov.amazonaws.com", - "https://elasticloadbalancing.us-gov-west-1.amazonaws.com", - "https://autoscaling.us-gov-west-1.amazonaws.com", - "https://rds.us-gov-west-1.amazonaws.com", - "https://route53.amazonaws.com", + Name: "us-gov-west-1", + EC2Endpoint: "https://ec2.us-gov-west-1.amazonaws.com", + S3Endpoint: "https://s3-fips-us-gov-west-1.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "", + SNSEndpoint: "https://sns.us-gov-west-1.amazonaws.com", + SQSEndpoint: "https://sqs.us-gov-west-1.amazonaws.com", + IAMEndpoint: "https://iam.us-gov.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.us-gov-west-1.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.us-gov-west-1.amazonaws.com", + RdsEndpoint: "https://rds.us-gov-west-1.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var USEast = Region{ - "us-east-1", - "https://ec2.us-east-1.amazonaws.com", - "https://s3.amazonaws.com", - "", - false, - false, - "https://sdb.amazonaws.com", - "https://sns.us-east-1.amazonaws.com", - "https://sqs.us-east-1.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.us-east-1.amazonaws.com", - "https://autoscaling.us-east-1.amazonaws.com", - "https://rds.us-east-1.amazonaws.com", - "https://route53.amazonaws.com", + Name: "us-east-1", + EC2Endpoint: "https://ec2.us-east-1.amazonaws.com", + S3Endpoint: "https://s3.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: false, + S3LowercaseBucket: false, + SDBEndpoint: "https://sdb.amazonaws.com", + SNSEndpoint: "https://sns.us-east-1.amazonaws.com", + SQSEndpoint: "https://sqs.us-east-1.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.us-east-1.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.us-east-1.amazonaws.com", + RdsEndpoint: "https://rds.us-east-1.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "https://ecs.us-east-1.amazonaws.com", } var USWest = Region{ - "us-west-1", - "https://ec2.us-west-1.amazonaws.com", - "https://s3-us-west-1.amazonaws.com", - "", - true, - true, - "https://sdb.us-west-1.amazonaws.com", - "https://sns.us-west-1.amazonaws.com", - "https://sqs.us-west-1.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.us-west-1.amazonaws.com", - "https://autoscaling.us-west-1.amazonaws.com", - "https://rds.us-west-1.amazonaws.com", - "https://route53.amazonaws.com", + Name: "us-west-1", + EC2Endpoint: "https://ec2.us-west-1.amazonaws.com", + S3Endpoint: "https://s3-us-west-1.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "https://sdb.us-west-1.amazonaws.com", + SNSEndpoint: "https://sns.us-west-1.amazonaws.com", + SQSEndpoint: "https://sqs.us-west-1.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.us-west-1.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.us-west-1.amazonaws.com", + RdsEndpoint: "https://rds.us-west-1.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var USWest2 = Region{ - "us-west-2", - "https://ec2.us-west-2.amazonaws.com", - "https://s3-us-west-2.amazonaws.com", - "", - true, - true, - "https://sdb.us-west-2.amazonaws.com", - "https://sns.us-west-2.amazonaws.com", - "https://sqs.us-west-2.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.us-west-2.amazonaws.com", - "https://autoscaling.us-west-2.amazonaws.com", - "https://rds.us-west-2.amazonaws.com", - "https://route53.amazonaws.com", + Name: "us-west-2", + EC2Endpoint: "https://ec2.us-west-2.amazonaws.com", + S3Endpoint: "https://s3-us-west-2.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "https://sdb.us-west-2.amazonaws.com", + SNSEndpoint: "https://sns.us-west-2.amazonaws.com", + SQSEndpoint: "https://sqs.us-west-2.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.us-west-2.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.us-west-2.amazonaws.com", + RdsEndpoint: "https://rds.us-west-2.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var EUWest = Region{ - "eu-west-1", - "https://ec2.eu-west-1.amazonaws.com", - "https://s3-eu-west-1.amazonaws.com", - "", - true, - true, - "https://sdb.eu-west-1.amazonaws.com", - "https://sns.eu-west-1.amazonaws.com", - "https://sqs.eu-west-1.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.eu-west-1.amazonaws.com", - "https://autoscaling.eu-west-1.amazonaws.com", - "https://rds.eu-west-1.amazonaws.com", - "https://route53.amazonaws.com", + Name: "eu-west-1", + EC2Endpoint: "https://ec2.eu-west-1.amazonaws.com", + S3Endpoint: "https://s3-eu-west-1.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "https://sdb.eu-west-1.amazonaws.com", + SNSEndpoint: "https://sns.eu-west-1.amazonaws.com", + SQSEndpoint: "https://sqs.eu-west-1.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.eu-west-1.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.eu-west-1.amazonaws.com", + RdsEndpoint: "https://rds.eu-west-1.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var EUCentral = Region{ - "eu-central-1", - "https://ec2.eu-central-1.amazonaws.com", - "https://s3-eu-central-1.amazonaws.com", - "", - true, - true, - "", - "https://sns.eu-central-1.amazonaws.com", - "https://sqs.eu-central-1.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.eu-central-1.amazonaws.com", - "https://autoscaling.eu-central-1.amazonaws.com", - "https://rds.eu-central-1.amazonaws.com", - "https://route53.amazonaws.com", + Name: "eu-central-1", + EC2Endpoint: "https://ec2.eu-central-1.amazonaws.com", + S3Endpoint: "https://s3-eu-central-1.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "", + SNSEndpoint: "https://sns.eu-central-1.amazonaws.com", + SQSEndpoint: "https://sqs.eu-central-1.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.eu-central-1.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.eu-central-1.amazonaws.com", + RdsEndpoint: "https://rds.eu-central-1.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var APSoutheast = Region{ - "ap-southeast-1", - "https://ec2.ap-southeast-1.amazonaws.com", - "https://s3-ap-southeast-1.amazonaws.com", - "", - true, - true, - "https://sdb.ap-southeast-1.amazonaws.com", - "https://sns.ap-southeast-1.amazonaws.com", - "https://sqs.ap-southeast-1.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.ap-southeast-1.amazonaws.com", - "https://autoscaling.ap-southeast-1.amazonaws.com", - "https://rds.ap-southeast-1.amazonaws.com", - "https://route53.amazonaws.com", + Name: "ap-southeast-1", + EC2Endpoint: "https://ec2.ap-southeast-1.amazonaws.com", + S3Endpoint: "https://s3-ap-southeast-1.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "https://sdb.ap-southeast-1.amazonaws.com", + SNSEndpoint: "https://sns.ap-southeast-1.amazonaws.com", + SQSEndpoint: "https://sqs.ap-southeast-1.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.ap-southeast-1.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.ap-southeast-1.amazonaws.com", + RdsEndpoint: "https://rds.ap-southeast-1.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var APSoutheast2 = Region{ - "ap-southeast-2", - "https://ec2.ap-southeast-2.amazonaws.com", - "https://s3-ap-southeast-2.amazonaws.com", - "", - true, - true, - "https://sdb.ap-southeast-2.amazonaws.com", - "https://sns.ap-southeast-2.amazonaws.com", - "https://sqs.ap-southeast-2.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.ap-southeast-2.amazonaws.com", - "https://autoscaling.ap-southeast-2.amazonaws.com", - "https://rds.ap-southeast-2.amazonaws.com", - "https://route53.amazonaws.com", + Name: "ap-southeast-2", + EC2Endpoint: "https://ec2.ap-southeast-2.amazonaws.com", + S3Endpoint: "https://s3-ap-southeast-2.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "https://sdb.ap-southeast-2.amazonaws.com", + SNSEndpoint: "https://sns.ap-southeast-2.amazonaws.com", + SQSEndpoint: "https://sqs.ap-southeast-2.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.ap-southeast-2.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.ap-southeast-2.amazonaws.com", + RdsEndpoint: "https://rds.ap-southeast-2.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var APNortheast = Region{ - "ap-northeast-1", - "https://ec2.ap-northeast-1.amazonaws.com", - "https://s3-ap-northeast-1.amazonaws.com", - "", - true, - true, - "https://sdb.ap-northeast-1.amazonaws.com", - "https://sns.ap-northeast-1.amazonaws.com", - "https://sqs.ap-northeast-1.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.ap-northeast-1.amazonaws.com", - "https://autoscaling.ap-northeast-1.amazonaws.com", - "https://rds.ap-northeast-1.amazonaws.com", - "https://route53.amazonaws.com", + Name: "ap-northeast-1", + EC2Endpoint: "https://ec2.ap-northeast-1.amazonaws.com", + S3Endpoint: "https://s3-ap-northeast-1.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "https://sdb.ap-northeast-1.amazonaws.com", + SNSEndpoint: "https://sns.ap-northeast-1.amazonaws.com", + SQSEndpoint: "https://sqs.ap-northeast-1.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.ap-northeast-1.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.ap-northeast-1.amazonaws.com", + RdsEndpoint: "https://rds.ap-northeast-1.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var SAEast = Region{ - "sa-east-1", - "https://ec2.sa-east-1.amazonaws.com", - "https://s3-sa-east-1.amazonaws.com", - "", - true, - true, - "https://sdb.sa-east-1.amazonaws.com", - "https://sns.sa-east-1.amazonaws.com", - "https://sqs.sa-east-1.amazonaws.com", - "https://iam.amazonaws.com", - "https://elasticloadbalancing.sa-east-1.amazonaws.com", - "https://autoscaling.sa-east-1.amazonaws.com", - "https://rds.sa-east-1.amazonaws.com", - "https://route53.amazonaws.com", + Name: "sa-east-1", + EC2Endpoint: "https://ec2.sa-east-1.amazonaws.com", + S3Endpoint: "https://s3-sa-east-1.amazonaws.com", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "https://sdb.sa-east-1.amazonaws.com", + SNSEndpoint: "https://sns.sa-east-1.amazonaws.com", + SQSEndpoint: "https://sqs.sa-east-1.amazonaws.com", + IAMEndpoint: "https://iam.amazonaws.com", + ELBEndpoint: "https://elasticloadbalancing.sa-east-1.amazonaws.com", + AutoScalingEndpoint: "https://autoscaling.sa-east-1.amazonaws.com", + RdsEndpoint: "https://rds.sa-east-1.amazonaws.com", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var CNNorth = Region{ - "cn-north-1", - "https://ec2.cn-north-1.amazonaws.com.cn", - "https://s3.cn-north-1.amazonaws.com.cn", - "", - true, - true, - "", - "https://sns.cn-north-1.amazonaws.com.cn", - "https://sqs.cn-north-1.amazonaws.com.cn", - "https://iam.cn-north-1.amazonaws.com.cn", - "https://elasticloadbalancing.cn-north-1.amazonaws.com.cn", - "https://autoscaling.cn-north-1.amazonaws.com.cn", - "https://rds.cn-north-1.amazonaws.com.cn", - "https://route53.amazonaws.com", + Name: "cn-north-1", + EC2Endpoint: "https://ec2.cn-north-1.amazonaws.com.cn", + S3Endpoint: "https://s3.cn-north-1.amazonaws.com.cn", + S3BucketEndpoint: "", + S3LocationConstraint: true, + S3LowercaseBucket: true, + SDBEndpoint: "", + SNSEndpoint: "https://sns.cn-north-1.amazonaws.com.cn", + SQSEndpoint: "https://sqs.cn-north-1.amazonaws.com.cn", + IAMEndpoint: "https://iam.cn-north-1.amazonaws.com.cn", + ELBEndpoint: "https://elasticloadbalancing.cn-north-1.amazonaws.com.cn", + AutoScalingEndpoint: "https://autoscaling.cn-north-1.amazonaws.com.cn", + RdsEndpoint: "https://rds.cn-north-1.amazonaws.com.cn", + Route53Endpoint: "https://route53.amazonaws.com", + ECSEndpoint: "", } var Regions = map[string]Region{ diff --git a/ecs/ecs.go b/ecs/ecs.go new file mode 100644 index 00000000..1582b23b --- /dev/null +++ b/ecs/ecs.go @@ -0,0 +1,321 @@ +package ecs + +import ( + "encoding/xml" + "io" + "net/http" + "net/url" + + "github.com/mitchellh/goamz/aws" +) + +type ECS struct { + aws.Auth + aws.Region + get func(u string) (io.ReadCloser, error) + private byte // Reserve the right of using private data. +} + +func NewWithClient(auth aws.Auth, region aws.Region, client *http.Client) *ECS { + get := func(u string) (io.ReadCloser, error) { + resp, err := client.Get(u) + if err != nil { + return nil, err + } + return resp.Body, nil + } + + return &ECS{auth, region, get, 0} +} + +func New(auth aws.Auth, region aws.Region) *ECS { + return NewWithClient(auth, region, aws.RetryingClient) +} + +type Cluster struct { + ClusterName string `xml:"clusterName" json:"clusterName"` + ClusterArn string `xml:"clusterArn" json:"clusterArn"` + Status string `xml:"status" json:"status"` +} + +type Container struct { +} + +type ContainerDefinition struct { +} + +type ContainerInstance struct { +} + +type ContainerOverride struct { +} + +type Failure struct { + Reason string `xml:"reason" json:"reason"` + Arn string `xml:"arn" json:"arn"` +} + +type HostVolumeProperties struct { +} + +type KeyValuePair struct { +} + +type MountPoint struct { +} + +type NetworkBinding struct { +} + +type PortMapping struct { +} + +type Resource struct { +} + +type Task struct { +} + +type TaskDefinition struct { +} + +type TaskOverride struct { +} + +type Volume struct { +} + +type VolumeFrom struct { +} + +// ------------------------------------------------------ + +type CreateCluster struct { + ClusterName string `form:"clusterName"` +} + +type CreateClusterResp struct { + RequestId string `xml:"ResponseMetadata>RequestId" json:"requestId"` + Cluster Cluster `xml:"CreateClusterResult>cluster" json:"cluster"` +} + +// See http://goo.gl/JLR5QH for more details +func (e *ECS) CreateCluster(options *CreateCluster) (*CreateClusterResp, error) { + params := makeParams("CreateCluster") + params.Set(options) + + resp := &CreateClusterResp{} + err := e.query(params, resp) + if err != nil { + return nil, err + } + return resp, err +} + +// ------------------------------------------------------ + +type DeleteCluster struct { + Cluster string `form:"cluster"` +} + +type DeleteClusterResp struct { + RequestId string `xml:"ResponseMetadata>RequestId" json:"requestId"` + Cluster Cluster `xml:"DeleteClusterResult>cluster" json:"cluster"` +} + +// See http://goo.gl/JLR5QH for more details +func (e *ECS) DeleteCluster(options *DeleteCluster) (*DeleteClusterResp, error) { + params := makeParams("DeleteCluster") + params.Set(options) + + resp := &DeleteClusterResp{} + err := e.query(params, resp) + if err != nil { + return nil, err + } + return resp, err +} + +// ------------------------------------------------------ + +type DescribeClusters struct { + ClusterName []string `form:"clusters.member"` +} + +type DescribeClustersResp struct { + RequestId string `xml:"ResponseMetadata>RequestId" json:"requestId"` + Clusters []Cluster `xml:"DescribeClustersResult>clusters>member" json:"clusters,omitempty"` + Failures []Failure `xml:"DescribeClustersResult>failures>member" json:"failures,omitempty"` +} + +// See http://goo.gl/X4ayOD for more details +func (e *ECS) DescribeClusters(options *DescribeClusters) (*DescribeClustersResp, error) { + params := makeParams("DescribeClusters") + params.Set(options) + + resp := &DescribeClustersResp{} + err := e.query(params, resp) + if err != nil { + return nil, err + } + return resp, err +} + +// ------------------------------------------------------ + +type ListClusters struct { + MaxResults int `form:"maxResults"` + NextToken string `form:"nextToken"` +} + +type ListClustersResp struct { + RequestId string `xml:"ResponseMetadata>RequestId" json:"requestId"` + ClusterArns []string `xml:"ListClustersResult>clusterArns>member" json:"clusterArns"` + NextToken string `xml:"ListClustersResult>nextToken" json:"nextToken"` +} + +// See http://goo.gl/WXV8cC for more details +func (e *ECS) ListClusters(options *ListClusters) (*ListClustersResp, error) { + params := makeParams("ListClusters") + params.Set(options) + + resp := &ListClustersResp{} + err := e.query(params, resp) + if err != nil { + return nil, err + } + return resp, err +} + +// ------------------------------------------------------ + +type ListContainerInstances struct { + Cluster string `form:"cluster"` + MaxResults int `form:"maxResults"` + NextToken string `form:"nextToken"` +} + +type ListContainerInstancesResp struct { + RequestId string `xml:"ResponseMetadata>RequestId" json:"requestId"` + ContainerInstanceArns []string `xml:"ListContainerInstancesResult>containerInstanceArns>member" json:"containerInstanceArns"` + NextToken string `xml:"ListContainerInstancesResult>nextToken" json:"nextToken"` +} + +// See http://goo.gl/cYoTSL for more details +func (e *ECS) ListContainerInstances(options *ListContainerInstances) (*ListContainerInstancesResp, error) { + params := makeParams("ListContainerInstances") + params.Set(options) + + resp := &ListContainerInstancesResp{} + err := e.query(params, resp) + if err != nil { + return nil, err + } + return resp, err +} + +// ------------------------------------------------------ + +type ListTaskDefinitionFamilies struct { + FamilyPrefix string `form:"familyPrefix"` + MaxResults int `form:"maxResults"` + NextToken string `form:"nextToken"` +} + +type ListTaskDefinitionFamiliesResp struct { + RequestId string `xml:"ResponseMetadata>RequestId" json:"requestId"` + Families []string `xml:"ListTaskDefinitionFamiliesResult>families>member" json:"families"` + NextToken string `xml:"ListTaskDefinitionFamiliesResult>nextToken" json:"nextToken"` +} + +// See http://goo.gl/kZKRR1 for more details +func (e *ECS) ListTaskDefinitionFamilies(options *ListTaskDefinitionFamilies) (*ListTaskDefinitionFamiliesResp, error) { + params := makeParams("ListTaskDefinitionFamilies") + params.Set(options) + + resp := &ListTaskDefinitionFamiliesResp{} + err := e.query(params, resp) + if err != nil { + return nil, err + } + return resp, err +} + +// ------------------------------------------------------ + +type ListTaskDefinitions struct { + FamilyPrefix string `form:"familyPrefix"` + MaxResults int `form:"maxResults"` + NextToken string `form:"nextToken"` +} + +type ListTaskDefinitionsResp struct { + RequestId string `xml:"ResponseMetadata>RequestId" json:"requestId"` + TaskDefinitionArns []string `xml:"ListTaskDefinitionsResult>taskDefinitionArns>member" json:"taskDefinitionArns"` + NextToken string `xml:"ListTaskDefinitionsResult>nextToken" json:"nextToken"` +} + +// See http://goo.gl/7ukY3J for more details +func (e *ECS) ListTaskDefinitions(options *ListTaskDefinitions) (*ListTaskDefinitionsResp, error) { + params := makeParams("ListTaskDefinitions") + params.Set(options) + + resp := &ListTaskDefinitionsResp{} + err := e.query(params, resp) + if err != nil { + return nil, err + } + return resp, err +} + +// ------------------------------------------------------ + +type ListTasks struct { + Cluster string `form:"cluster"` + ContainerInstance string `form:"containerInstance"` + Family string `form:"family"` + MaxResults int `form:"maxResults"` + NextToken string `form:"nextToken"` +} + +type ListTasksResp struct { + RequestId string `xml:"ResponseMetadata>RequestId" json:"requestId"` + TaskArns []string `xml:"ListTasksResult>taskArns>member" json:"taskArns"` + NextToken string `xml:"ListTasksResult>nextToken" json:"nextToken"` +} + +// See http://goo.gl/dc8QGQ for more details +func (e *ECS) ListTasks(options *ListTasks) (*ListTasksResp, error) { + params := makeParams("ListTasks") + params.Set(options) + + resp := &ListTasksResp{} + err := e.query(params, resp) + if err != nil { + return nil, err + } + return resp, err +} + +// ------------------------------------------------------ + +func (e *ECS) query(params parameters, v interface{}) error { + // extract the host from the endpoint + u, err := url.Parse(e.Region.ECSEndpoint) + if err != nil { + return err + } + + // sign the request + sign(e.Auth, e.Region, u.Host, params) + + // make the request + uri := e.Region.ECSEndpoint + "?" + params.encoded() + body, err := e.get(uri) + if err != nil { + return err + } + defer body.Close() + + return xml.NewDecoder(body).Decode(v) +} diff --git a/ecs/ecs_test.go b/ecs/ecs_test.go new file mode 100644 index 00000000..acdaf40e --- /dev/null +++ b/ecs/ecs_test.go @@ -0,0 +1,260 @@ +package ecs + +import ( + "io" + "io/ioutil" + "net/url" + "strings" + + "github.com/mitchellh/goamz/aws" + . "github.com/motain/gocheck" +) + +type MockClient struct { + Url *url.URL + Query url.Values + Body string +} + +func (m *MockClient) Get(u string) (body io.ReadCloser, err error) { + m.Url, err = url.Parse(u) + if err != nil { + return + } + m.Query = m.Url.Query() + body = ioutil.NopCloser(strings.NewReader(m.Body)) + return +} + +var testAuth = aws.Auth{ + AccessKey: "", + SecretKey: "", + Token: "", +} + +func mockClient(body string) (*ECS, *MockClient) { + m := &MockClient{Body: body} + c := New(testAuth, aws.USEast) + c.get = m.Get + return c, m +} + +func (s *S) TestCreateCluster(c *C) { + body := ` + + + My-cluster + arn:aws:ecs:us-east-1:012345678910:cluster/My-cluster + ACTIVE + + + + 123a4b56-7c89-01d2-3ef4-example5678f + +` + client, mock := mockClient(body) + + // When + resp, err := client.CreateCluster(&CreateCluster{ClusterName: "My-cluster"}) + + // Then + c.Assert(err, Equals, nil) + + c.Assert(mock.Query.Get("Action"), Equals, "CreateCluster") + c.Assert(mock.Query.Get("clusterName"), Equals, "My-cluster") + + c.Assert(resp.Cluster.ClusterName, Equals, "My-cluster") + c.Assert(resp.Cluster.ClusterArn, Equals, "arn:aws:ecs:us-east-1:012345678910:cluster/My-cluster") + c.Assert(resp.Cluster.Status, Equals, "ACTIVE") +} + +func (s *S) TestDeleteCluster(c *C) { + body := ` + + + My-cluster + arn:aws:ecs:us-east-1:012345678910:cluster/My-cluster + INACTIVE + + + + 123a4b56-7c89-01d2-3ef4-example5678f + +` + client, mock := mockClient(body) + + // When + resp, err := client.DeleteCluster(&DeleteCluster{Cluster: "My-cluster"}) + + // Then + c.Assert(err, Equals, nil) + + c.Assert(mock.Query.Get("Action"), Equals, "DeleteCluster") + c.Assert(mock.Query.Get("cluster"), Equals, "My-cluster") + + c.Assert(resp.Cluster.ClusterName, Equals, "My-cluster") + c.Assert(resp.Cluster.ClusterArn, Equals, "arn:aws:ecs:us-east-1:012345678910:cluster/My-cluster") + c.Assert(resp.Cluster.Status, Equals, "INACTIVE") +} + +func (s *S) TestDescribeClusters(c *C) { + body := ` + + + + + default + arn:aws:ecs:us-east-1:012345678910:cluster/default + ACTIVE + + + + + 123a4b56-7c89-01d2-3ef4-example5678f + +` + client, mock := mockClient(body) + + // When + resp, err := client.DescribeClusters(&DescribeClusters{ClusterName: []string{"default"}}) + + // Then + c.Assert(err, Equals, nil) + + c.Assert(mock.Query.Get("Action"), Equals, "DescribeClusters") + c.Assert(mock.Query.Get("clusters.member.1"), Equals, "default") + + c.Assert(len(resp.Clusters), Equals, 1) + c.Assert(resp.Clusters[0].ClusterName, Equals, "default") + c.Assert(resp.Clusters[0].ClusterArn, Equals, "arn:aws:ecs:us-east-1:012345678910:cluster/default") + c.Assert(resp.Clusters[0].Status, Equals, "ACTIVE") +} + +func (s *S) TestListClusters(c *C) { + body := ` + + + arn:aws:ecs:us-east-1:012345678910:cluster/default + arn:aws:ecs:us-east-1:012345678910:cluster/ecs-preview + + + + 123a4b56-7c89-01d2-3ef4-example5678f + +` + client, mock := mockClient(body) + + // When + resp, err := client.ListClusters(&ListClusters{}) + + // Then + c.Assert(err, Equals, nil) + + c.Assert(mock.Query.Get("Action"), Equals, "ListClusters") + + c.Assert(resp.ClusterArns, DeepEquals, []string{"arn:aws:ecs:us-east-1:012345678910:cluster/default", "arn:aws:ecs:us-east-1:012345678910:cluster/ecs-preview"}) +} + +func (s *S) TestListContainerInstances(c *C) { + body := ` + + + arn:aws:ecs:us-east-1:012345678910:container-instance/b0d69404-4bba-4ad8-96f7-2fa6b6a79c1c + + + + 123a4b56-7c89-01d2-3ef4-example5678f + +` + client, mock := mockClient(body) + + // When + resp, err := client.ListContainerInstances(&ListContainerInstances{}) + + // Then + c.Assert(err, Equals, nil) + + c.Assert(mock.Query.Get("Action"), Equals, "ListContainerInstances") + + c.Assert(resp.ContainerInstanceArns, DeepEquals, []string{"arn:aws:ecs:us-east-1:012345678910:container-instance/b0d69404-4bba-4ad8-96f7-2fa6b6a79c1c"}) +} + +func (s *S) TestListTaskDefinitionFamilies(c *C) { + body := ` + + + hpcc + hpcc-t2-medium + + + + 526f0836-b6ed-11e4-87f7-b9d2e0bd52a5 + +` + client, mock := mockClient(body) + + // When + resp, err := client.ListTaskDefinitionFamilies(&ListTaskDefinitionFamilies{FamilyPrefix: "hp"}) + + // Then + c.Assert(err, Equals, nil) + + c.Assert(mock.Query.Get("Action"), Equals, "ListTaskDefinitionFamilies") + c.Assert(mock.Query.Get("familyPrefix"), Equals, "hp") + + c.Assert(resp.Families, DeepEquals, []string{"hpcc", "hpcc-t2-medium"}) +} + +func (s *S) TestListTaskDefinitions(c *C) { + body := ` + + + arn:aws:ecs:us-east-1:012345678910:task-definition/hello_world:1 + arn:aws:ecs:us-east-1:012345678910:task-definition/hello_world:2 + + + + 123a4b56-7c89-01d2-3ef4-example5678f + +` + client, mock := mockClient(body) + + // When + resp, err := client.ListTaskDefinitions(&ListTaskDefinitions{FamilyPrefix: "hp"}) + + // Then + c.Assert(err, Equals, nil) + + c.Assert(mock.Query.Get("Action"), Equals, "ListTaskDefinitions") + c.Assert(resp.TaskDefinitionArns, DeepEquals, []string{"arn:aws:ecs:us-east-1:012345678910:task-definition/hello_world:1", "arn:aws:ecs:us-east-1:012345678910:task-definition/hello_world:2"}) +} + +func (s *S) TestListTasks(c *C) { + body := ` + + + arn:aws:ecs:us-east-1:012345678910:task/0c399d42-0b06-4a82-8794-d593fe68411f + + + + 123a4b56-7c89-01d2-3ef4-example5678f + +` + client, mock := mockClient(body) + + // When + resp, err := client.ListTasks(&ListTasks{ + Cluster: "my-cluster", + ContainerInstance: "my-instance", + Family: "my-family", + }) + + // Then + c.Assert(err, Equals, nil) + + c.Assert(mock.Query.Get("Action"), Equals, "ListTasks") + c.Assert(mock.Query.Get("cluster"), Equals, "my-cluster") + c.Assert(mock.Query.Get("containerInstance"), Equals, "my-instance") + c.Assert(mock.Query.Get("family"), Equals, "my-family") + c.Assert(resp.TaskArns, DeepEquals, []string{"arn:aws:ecs:us-east-1:012345678910:task/0c399d42-0b06-4a82-8794-d593fe68411f"}) +} diff --git a/ecs/params.go b/ecs/params.go new file mode 100644 index 00000000..0ac04388 --- /dev/null +++ b/ecs/params.go @@ -0,0 +1,82 @@ +package ecs + +import ( + "fmt" + "reflect" + "sort" + "strconv" + "strings" + + "launchpad.net/goamz/aws" +) + +type parameters map[string]string + +func makeParams(action string) parameters { + return parameters{ + "Action": action, + "Version": "2014-11-13", + } +} + +// #Set uses reflection to assign parameters based on the +// form tag. +func (p parameters) Set(v interface{}) parameters { + r := reflect.TypeOf(v) + formStruct := reflect.ValueOf(v) + + // if we have a pointer, dereference it + if r.Kind() == reflect.Ptr { + return p.Set(formStruct.Elem().Interface()) + } + + // enumerate through each field, search for the form tag + for i := 0; i < r.NumField(); i++ { + typeField := r.Field(i) + if key := typeField.Tag.Get("form"); key != "" { + value := formStruct.Field(i).Interface() + p.assign(key, value) + } + } + + return p +} + +func (p parameters) assign(key string, value interface{}) { + set := func(k, v string) { + if v != "" { + p[k] = v + } + } + + switch v := value.(type) { + case []string: + for i, s := range v { + set(fmt.Sprintf("%s.%d", key, i+1), s) + } + case string: + set(key, v) + case bool: + set(key, strconv.FormatBool(v)) + case int: + if v != 0 { + set(key, strconv.Itoa(v)) + } + } +} + +func (p parameters) encoded() string { + // AWS specifies that the parameters in a signed request must + // be provided in the natural order of the keys. This is distinct + // from the natural order of the encoded value of key=value. + // Percent and equals affect the sorting order. + var keys, sarray []string + for k, _ := range p { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(p[k])) + } + return strings.Join(sarray, "&") +} diff --git a/ecs/params_test.go b/ecs/params_test.go new file mode 100644 index 00000000..bee81c34 --- /dev/null +++ b/ecs/params_test.go @@ -0,0 +1,70 @@ +package ecs + +import ( + "testing" + + . "github.com/motain/gocheck" +) + +func Test(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&S{}) + +type S struct { + ecs *ECS +} + +func (s *S) TestMakeParams(c *C) { + params := makeParams("Sample") + value, found := params["Action"] + c.Assert(value, Equals, "Sample") + c.Assert(found, Equals, true) +} + +type Mock struct { + S1 string `form:"string1"` + S2 string `form:"string1"` + B1 bool `form:"bool1"` + B2 bool `form:"bool2"` + I1 int `form:"int1"` + I2 int `form:"int2"` + SA1 []string `form:"array1"` + SA2 []string `form:"array2"` +} + +func (s *S) TestSetParameters(c *C) { + params := makeParams("blah") + params.Set(Mock{ + S1: "hello", + B1: true, + I1: 1, + SA1: []string{"a", "b", "c"}, + }) + + c.Assert(params["string1"], Equals, "hello") + c.Assert(params["bool1"], Equals, "true") + c.Assert(params["bool2"], Equals, "false") + c.Assert(params["int1"], Equals, "1") + + c.Assert(params["array1.1"], Equals, "a") + c.Assert(params["array1.2"], Equals, "b") + c.Assert(params["array1.3"], Equals, "c") + + var found bool + _, found = params["string2"] + c.Assert(found, Equals, false) + + _, found = params["int2"] + c.Assert(found, Equals, false) + + _, found = params["array2.1"] + c.Assert(found, Equals, false) +} + +func (s *S) TestHexEncode(c *C) { + params := makeParams("ListUsers") + params["Version"] = "2010-05-08" + c.Assert(hexEncode(params.encoded()), Equals, "b6359072c78d70ebee1e81adcbab4f01bf2c23245fa365ef83fe8f1f955085e2") +} diff --git a/ecs/sign.go b/ecs/sign.go new file mode 100644 index 00000000..ee0e4453 --- /dev/null +++ b/ecs/sign.go @@ -0,0 +1,97 @@ +package ecs + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "strings" + "time" + + "github.com/mitchellh/goamz/aws" +) + +const ( + AmzDateTimeFormat = "20060102T150405Z" + AmzDateFormat = "20060102" + AmzAlgorithm = "AWS4-HMAC-SHA256" +) + +const ( + service = "ecs" +) + +// See http://goo.gl/xZjQRs for Version 4 signing details +func sign(auth aws.Auth, region aws.Region, host string, params parameters) { + now := time.Now().UTC() + amzDateTime := now.Format(AmzDateTimeFormat) + amzDate := now.Format(AmzDateFormat) + + // ---- add parameters --------------------------------- + + credentialScope := fmt.Sprintf("%s/%s/%s/aws4_request", amzDate, region.Name, service) + signedHeaders := "host" // only signing host header + + params["X-Amz-Credential"] = auth.AccessKey + "/" + credentialScope + params["X-Amz-Algorithm"] = AmzAlgorithm + params["X-Amz-Date"] = amzDateTime + params["X-Amz-SignedHeaders"] = signedHeaders + + // ---- generate the canonical request ----------------- + + method := "GET" + path := "/" + canonicalQueryString := params.encoded() + canonicalHeaders := fmt.Sprintf("host:%s", host) + requestPayload := hexEncode("") + + canonicalRequest := strings.Join([]string{ + method, + path, + canonicalQueryString, + canonicalHeaders, + "", + signedHeaders, + requestPayload, + }, "\n") + + // ---- create the string to sign ---------------------- + + requestDate := amzDateTime + + stringToSign := strings.Join([]string{ + AmzAlgorithm, + requestDate, + credentialScope, + hexEncode(canonicalRequest), + }, "\n") + + // ---- calculate the signature ------------------------ + + kSigning := getSignatureKey(auth.SecretKey, amzDate, region.Name, service) + kSignature := hmacSHA256(kSigning, stringToSign) + signature := hex.EncodeToString(kSignature) + + // ---- add to request --------------------------------- + + params["X-Amz-Signature"] = signature +} + +func getSignatureKey(secretKey, dateStamp, regionName, serviceName string) []byte { + kDate := hmacSHA256([]byte("AWS4"+secretKey), dateStamp) + kRegion := hmacSHA256(kDate, regionName) + kService := hmacSHA256(kRegion, serviceName) + return hmacSHA256(kService, "aws4_request") +} + +func hmacSHA256(base []byte, plus string) []byte { + hash := hmac.New(sha256.New, base) + hash.Write([]byte(plus)) + return hash.Sum(nil) +} + +func hexEncode(text string) string { + hasher := sha256.New() + hasher.Write([]byte(text)) + return hex.EncodeToString(hasher.Sum(nil)) +} diff --git a/ecs/sign_test.go b/ecs/sign_test.go new file mode 100644 index 00000000..289baa8f --- /dev/null +++ b/ecs/sign_test.go @@ -0,0 +1,36 @@ +package ecs + +import ( + "strings" + + . "github.com/motain/gocheck" +) + +// See http://goo.gl/wtteTH for test values +func (s *S) TestBasicSignature(c *C) { + kSecret := "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" + dateStamp := "20110909" + regionName := "us-east-1" + serviceName := "iam" + kSigning := getSignatureKey(kSecret, dateStamp, regionName, serviceName) + + expected := []byte{ + 152, 241, 216, 137, 254, 196, 244, 66, 26, 220, 82, 43, 171, 12, 225, 248, 46, 105, 41, 194, 98, 237, 21, 229, 169, 76, 144, 239, 209, 227, 176, 231, + } + + c.Assert(kSigning, DeepEquals, expected) +} + +func (s *S) TestHashCanonicalRequest(c *C) { + canonicalString := strings.Join([]string{ + "GET", + "/", + "Action=DescribeClusters&Version=2014-11-13&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJTKX7CK2RJVD5MSA%2F20150223%2Fus-east-1%2Fecs%2Faws4_request&X-Amz-Date=20150223T140640Z&X-Amz-SignedHeaders=host", + "host:ecs.us-east-1.amazonaws.com", + "", + "host", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "\n") + + c.Assert(hexEncode(canonicalString), Equals, "57c130e03223a31650a9a5c762a1024384330c1c132a35029b1c5c468da875f0") +}