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")
+}