Define amazon.API as a simplified and currated interface over AWS SDK

This makes code simpler to read and easier to mock within tests

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-04-24 14:19:14 +02:00 committed by Nicolas De loof
parent 52440a4732
commit 8c0fee5abf
12 changed files with 261 additions and 457 deletions

View File

@ -1,3 +1,6 @@
clean:
rm -rf dist/
build:
go build -v -o dist/docker-ecs cmd/main/main.go

22
ecs/pkg/amazon/api.go Normal file
View File

@ -0,0 +1,22 @@
package amazon
import (
"github.com/awslabs/goformation/v4/cloudformation"
)
type API interface {
ClusterExists(name string) (bool, error)
CreateCluster(name string) (string, error)
DeleteCluster(name string) error
GetDefaultVPC() (string, error)
GetSubNets(vpcId string) ([]string, error)
ListRolesForPolicy(policy string) ([]string, error)
GetRoleArn(name string) (string, error)
StackExists(name string) (bool, error)
CreateStack(name string, template *cloudformation.Template) error
DescribeStackEvents(stack string) error
DeleteStack(name string) error
}

View File

@ -3,18 +3,6 @@ package amazon
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/ecs/ecsiface"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/iam/iamiface"
"github.com/docker/ecs-plugin/pkg/compose"
)
@ -35,26 +23,14 @@ func NewClient(profile string, cluster string, region string) (compose.API, erro
return &client{
Cluster: cluster,
Region: region,
sess: sess,
ECS: ecs.New(sess),
EC2: ec2.New(sess),
ELB: elbv2.New(sess),
CW: cloudwatchlogs.New(sess),
IAM: iam.New(sess),
CF: cloudformation.New(sess),
api: NewAPI(sess),
}, nil
}
type client struct {
Cluster string
Region string
sess *session.Session
ECS ecsiface.ECSAPI
EC2 ec2iface.EC2API
ELB elbv2iface.ELBV2API
CW cloudwatchlogsiface.CloudWatchLogsAPI
IAM iamiface.IAMAPI
CF cloudformationiface.CloudFormationAPI
api API
}
var _ compose.API = &client{}

View File

@ -2,6 +2,8 @@ package amazon
import (
"fmt"
"github.com/compose-spec/compose-go/types"
"github.com/sirupsen/logrus"
"strings"
ecsapi "github.com/aws/aws-sdk-go/service/ecs"
@ -14,12 +16,12 @@ import (
func (c client) Convert(project *compose.Project, loadBalancerArn *string) (*cloudformation.Template, error) {
template := cloudformation.NewTemplate()
vpc, err := c.GetDefaultVPC()
vpc, err := c.api.GetDefaultVPC()
if err != nil {
return nil, err
}
subnets, err := c.GetSubNets(vpc)
subnets, err := c.api.GetSubNets(vpc)
if err != nil {
return nil, err
}
@ -42,7 +44,7 @@ func (c client) Convert(project *compose.Project, loadBalancerArn *string) (*clo
GroupDescription: securityGroup,
GroupName: securityGroup,
SecurityGroupIngress: ingresses,
VpcId: *vpc,
VpcId: vpc,
}
for _, service := range project.Services {
@ -55,7 +57,7 @@ func (c client) Convert(project *compose.Project, loadBalancerArn *string) (*clo
if err != nil {
return nil, err
}
definition.TaskRoleArn = *role
definition.TaskRoleArn = role
taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name)
template.Resources[taskDefinition] = definition
@ -78,3 +80,35 @@ func (c client) Convert(project *compose.Project, loadBalancerArn *string) (*clo
}
return template, nil
}
const ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
var defaultTaskExecutionRole string
// GetEcsTaskExecutionRole retrieve the role ARN to apply for task execution
func (c client) GetEcsTaskExecutionRole(spec types.ServiceConfig) (string, error) {
if arn, ok := spec.Extras["x-ecs-TaskExecutionRole"]; ok {
return arn.(string), nil
}
if defaultTaskExecutionRole != "" {
return defaultTaskExecutionRole, nil
}
logrus.Debug("Retrieve Task Execution Role")
entities, err := c.api.ListRolesForPolicy(ECSTaskExecutionPolicy)
if err != nil {
return "", err
}
if len(entities) == 0 {
return "", fmt.Errorf("no Role is attached to AmazonECSTaskExecutionRole Policy, please provide an explicit task execution role")
}
if len(entities) > 1 {
return "", fmt.Errorf("multiple Roles are attached to AmazonECSTaskExecutionRole Policy, please provide an explicit task execution role")
}
arn, err := c.api.GetRoleArn(entities[0])
if err != nil {
return "", err
}
defaultTaskExecutionRole = arn
return arn, nil
}

View File

@ -2,30 +2,22 @@ package amazon
import (
"fmt"
"github.com/aws/aws-sdk-go/service/cloudformation"
cf "github.com/aws/aws-sdk-go/service/cloudformation"
)
func (c *client) ComposeDown(projectName *string, keepLoadBalancer, deleteCluster bool) error {
_, err := c.CF.DeleteStack(&cloudformation.DeleteStackInput{
StackName: projectName,
})
err := c.api.DeleteStack(projectName)
if err != nil {
return err
}
fmt.Printf("Delete stack ")
if err = c.CF.WaitUntilStackDeleteComplete(&cf.DescribeStacksInput{StackName: projectName}); err != nil {
return err
}
fmt.Printf("... done.\n")
if !deleteCluster {
return nil
}
fmt.Printf("Delete cluster %s", c.Cluster)
if err = c.DeleteCluster(); err != nil {
if err = c.api.DeleteCluster(c.Cluster); err != nil {
return err
}
fmt.Printf("... done. \n")

View File

@ -1,50 +0,0 @@
package amazon
import (
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/sirupsen/logrus"
)
func (c client) RegisterTaskDefinition(task *ecs.RegisterTaskDefinitionInput) (*string, error) {
logrus.Debug("Register Task Definition")
def, err := c.ECS.RegisterTaskDefinition(task)
if err != nil {
return nil, err
}
return def.TaskDefinition.TaskDefinitionArn, err
}
func (c client) CreateCluster() (*string, error) {
logrus.Debug("Create cluster ", c.Cluster)
response, err := c.ECS.CreateCluster(&ecs.CreateClusterInput{ClusterName: &c.Cluster})
if err != nil {
return nil, err
}
return response.Cluster.Status, nil
}
func (c client) DeleteCluster() error {
logrus.Debug("Delete cluster ", c.Cluster)
response, err := c.ECS.DeleteCluster(&ecs.DeleteClusterInput{Cluster: &c.Cluster})
if err != nil {
return err
}
if *response.Cluster.Status == "INACTIVE" {
return nil
}
return errors.New("Failed to delete cluster, status: " + *response.Cluster.Status)
}
func (c client) ClusterExists() (bool, error) {
logrus.Debug("Check if cluster was already created: ", c.Cluster)
clusters, err := c.ECS.DescribeClusters(&ecs.DescribeClustersInput{
Clusters: []*string{aws.String(c.Cluster)},
})
if err != nil {
return false, err
}
return len(clusters.Clusters) > 0, nil
}

View File

@ -1,136 +0,0 @@
package amazon
import (
"fmt"
"strings"
"github.com/docker/ecs-plugin/pkg/compose"
"github.com/sirupsen/logrus"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/compose-spec/compose-go/types"
)
func (c client) CreateLoadBalancer(project *compose.Project, subnets []*string) (*string, error) {
logrus.Debug("Create Load Balancer")
alb, err := c.ELB.CreateLoadBalancer(&elbv2.CreateLoadBalancerInput{
IpAddressType: nil,
Name: aws.String(fmt.Sprintf("%s-LoadBalancer", project.Name)),
Subnets: subnets,
Type: aws.String(elbv2.LoadBalancerTypeEnumNetwork),
Tags: []*elbv2.Tag{
{
Key: aws.String("com.docker.compose.project"),
Value: aws.String(project.Name),
},
},
})
if err != nil {
return nil, err
}
return alb.LoadBalancers[0].LoadBalancerArn, nil
}
func (c client) DeleteLoadBalancer(project *compose.Project, keepLoadBalancer bool) error {
logrus.Debug("Delete Load Balancer")
// FIXME We can tag LoadBalancer but not search by tag ?
loadBalancer, err := c.ELB.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
Names: aws.StringSlice([]string{fmt.Sprintf("%s-LoadBalancer", project.Name)}),
})
if err != nil {
return err
}
arn := loadBalancer.LoadBalancers[0].LoadBalancerArn
err = c.DeleteListeners(arn)
if err != nil {
return err
}
err = c.DeleteTargetGroups(arn)
if err != nil {
return err
}
if !keepLoadBalancer {
_, err = c.ELB.DeleteLoadBalancer(&elbv2.DeleteLoadBalancerInput{LoadBalancerArn: arn})
}
return err
}
func (c client) CreateTargetGroup(name string, vpc *string, port types.ServicePortConfig) (*string, error) {
logrus.Debugf("Create Target Group %d/%s\n", port.Target, port.Protocol)
group, err := c.ELB.CreateTargetGroup(&elbv2.CreateTargetGroupInput{
Name: aws.String(name),
Port: aws.Int64(int64(port.Target)),
Protocol: aws.String(strings.ToUpper(port.Protocol)),
TargetType: aws.String("ip"),
VpcId: vpc,
})
if err != nil {
return nil, err
}
arn := group.TargetGroups[0].TargetGroupArn
return arn, nil
}
func (c client) DeleteTargetGroups(loadBalancer *string) error {
groups, err := c.ELB.DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{
LoadBalancerArn: loadBalancer,
})
if err != nil {
return err
}
for _, group := range groups.TargetGroups {
logrus.Debugf("Delete Target Group %s\n", *group.TargetGroupArn)
_, err := c.ELB.DeleteTargetGroup(&elbv2.DeleteTargetGroupInput{
TargetGroupArn: group.TargetGroupArn,
})
if err != nil {
return err
}
}
return nil
}
func (c client) CreateListener(port types.ServicePortConfig, arn *string, target *string) error {
logrus.Debugf("Create Listener %d\n", port.Published)
_, err := c.ELB.CreateListener(&elbv2.CreateListenerInput{
DefaultActions: []*elbv2.Action{
{
ForwardConfig: &elbv2.ForwardActionConfig{
TargetGroups: []*elbv2.TargetGroupTuple{
{
TargetGroupArn: target,
},
},
},
Type: aws.String(elbv2.ActionTypeEnumForward),
},
},
LoadBalancerArn: arn,
Port: aws.Int64(int64(port.Published)),
Protocol: aws.String(strings.ToUpper(port.Protocol)),
})
return err
}
func (c client) DeleteListeners(loadBalancer *string) error {
listeners, err := c.ELB.DescribeListeners(&elbv2.DescribeListenersInput{
LoadBalancerArn: loadBalancer,
})
if err != nil {
return err
}
for _, listener := range listeners.Listeners {
logrus.Debugf("Delete Listener %s\n", *listener.ListenerArn)
_, err := c.ELB.DeleteListener(&elbv2.DeleteListenerInput{
ListenerArn: listener.ListenerArn,
})
if err != nil {
return err
}
}
return nil
}

View File

@ -1,28 +0,0 @@
package amazon
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/docker/ecs-plugin/pkg/compose"
"github.com/sirupsen/logrus"
)
// GetOrCreateLogGroup retrieve a pre-existing log group for project or create one
func (c client) GetOrCreateLogGroup(project *compose.Project) (*string, error) {
logrus.Debug("Create Log Group")
logGroup := fmt.Sprintf("/ecs/%s", project.Name)
_, err := c.CW.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
LogGroupName: aws.String(logGroup),
Tags: map[string]*string{
ProjectTag: aws.String(project.Name),
},
})
if err != nil {
if _, ok := err.(*cloudwatchlogs.ResourceAlreadyExistsException); !ok {
return nil, err
}
}
return &logGroup, nil
}

View File

@ -1,112 +0,0 @@
package amazon
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/compose-spec/compose-go/types"
"github.com/docker/ecs-plugin/pkg/compose"
"github.com/sirupsen/logrus"
)
// GetDefaultVPC retrieve the default VPC for AWS account
func (c client) GetDefaultVPC() (*string, error) {
logrus.Debug("Retrieve default VPC")
vpcs, err := c.EC2.DescribeVpcs(&ec2.DescribeVpcsInput{
Filters: []*ec2.Filter{
{
Name: aws.String("isDefault"),
Values: []*string{aws.String("true")},
},
},
})
if err != nil {
return nil, err
}
if len(vpcs.Vpcs) == 0 {
return nil, fmt.Errorf("account has not default VPC")
}
return vpcs.Vpcs[0].VpcId, nil
}
// GetSubNets retrieve default subnets for a VPC
func (c client) GetSubNets(vpc *string) ([]string, error) {
logrus.Debug("Retrieve SubNets")
subnets, err := c.EC2.DescribeSubnets(&ec2.DescribeSubnetsInput{
DryRun: nil,
Filters: []*ec2.Filter{
{
Name: aws.String("vpc-id"),
Values: []*string{vpc},
},
{
Name: aws.String("default-for-az"),
Values: []*string{aws.String("true")},
},
},
})
if err != nil {
return nil, err
}
ids := []string{}
for _, subnet := range subnets.Subnets {
ids = append(ids, *subnet.SubnetId)
}
return ids, nil
}
// CreateSecurityGroup create a security group for the project
func (c client) CreateSecurityGroup(project *compose.Project, vpc *string) (*string, error) {
logrus.Debug("Create Security Group")
name := fmt.Sprintf("%s Security Group", project.Name)
securityGroup, err := c.EC2.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
Description: aws.String(name),
GroupName: aws.String(name),
VpcId: vpc,
})
if err != nil {
return nil, err
}
_, err = c.EC2.CreateTags(&ec2.CreateTagsInput{
Resources: []*string{securityGroup.GroupId},
Tags: []*ec2.Tag{
{
Key: aws.String("Name"),
Value: aws.String(name),
},
{
Key: aws.String(ProjectTag),
Value: aws.String(project.Name),
},
},
})
if err != nil {
return nil, err
}
return securityGroup.GroupId, nil
}
func (c *client) ExposePort(securityGroup *string, port types.ServicePortConfig) error {
logrus.Debugf("Authorize ingress port %d/%s\n", port.Published, port.Protocol)
_, err := c.EC2.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
GroupId: securityGroup,
IpPermissions: []*ec2.IpPermission{
{
IpProtocol: aws.String(strings.ToUpper(port.Protocol)),
IpRanges: []*ec2.IpRange{
{
CidrIp: aws.String("0.0.0.0/0"),
},
},
FromPort: aws.Int64(int64(port.Target)),
ToPort: aws.Int64(int64(port.Target)),
},
},
})
return err
}

View File

@ -1,49 +0,0 @@
package amazon
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/compose-spec/compose-go/types"
"github.com/sirupsen/logrus"
)
const ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
var defaultTaskExecutionRole *string
// GetEcsTaskExecutionRole retrieve the role ARN to apply for task execution
func (c client) GetEcsTaskExecutionRole(spec types.ServiceConfig) (*string, error) {
if arn, ok := spec.Extras["x-ecs-TaskExecutionRole"]; ok {
s := arn.(string)
return &s, nil
}
if defaultTaskExecutionRole != nil {
return defaultTaskExecutionRole, nil
}
logrus.Debug("Retrieve Task Execution Role")
entities, err := c.IAM.ListEntitiesForPolicy(&iam.ListEntitiesForPolicyInput{
EntityFilter: aws.String("Role"),
PolicyArn: aws.String(ECSTaskExecutionPolicy),
})
if err != nil {
return nil, err
}
if len(entities.PolicyRoles) == 0 {
return nil, fmt.Errorf("no Role is attached to AmazonECSTaskExecutionRole Policy, please provide an explicit task execution role")
}
if len(entities.PolicyRoles) > 1 {
return nil, fmt.Errorf("multiple Roles are attached to AmazonECSTaskExecutionRole Policy, please provide an explicit task execution role")
}
role, err := c.IAM.GetRole(&iam.GetRoleInput{
RoleName: entities.PolicyRoles[0].RoleName,
})
if err != nil {
return nil, err
}
defaultTaskExecutionRole = role.Role.Arn
return role.Role.Arn, nil
}

187
ecs/pkg/amazon/sdk.go Normal file
View File

@ -0,0 +1,187 @@
package amazon
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/ecs/ecsiface"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/iam/iamiface"
cf "github.com/awslabs/goformation/v4/cloudformation"
"github.com/sirupsen/logrus"
)
type sdk struct {
sess *session.Session
ECS ecsiface.ECSAPI
EC2 ec2iface.EC2API
ELB elbv2iface.ELBV2API
CW cloudwatchlogsiface.CloudWatchLogsAPI
IAM iamiface.IAMAPI
CF cloudformationiface.CloudFormationAPI
}
func NewAPI(sess *session.Session) API {
return sdk{
ECS: ecs.New(sess),
EC2: ec2.New(sess),
ELB: elbv2.New(sess),
CW: cloudwatchlogs.New(sess),
IAM: iam.New(sess),
CF: cloudformation.New(sess),
}
}
func (s sdk) ClusterExists(name string) (bool, error) {
logrus.Debug("Check if cluster was already created: ", name)
clusters, err := s.ECS.DescribeClusters(&ecs.DescribeClustersInput{
Clusters: []*string{aws.String(name)},
})
if err != nil {
return false, err
}
return len(clusters.Clusters) > 0, nil
}
func (s sdk) CreateCluster(name string) (string, error) {
logrus.Debug("Create cluster ", name)
response, err := s.ECS.CreateCluster(&ecs.CreateClusterInput{ClusterName: aws.String(name)})
if err != nil {
return "", err
}
return *response.Cluster.Status, nil
}
func (s sdk) DeleteCluster(name string) error {
logrus.Debug("Delete cluster ", name)
response, err := s.ECS.DeleteCluster(&ecs.DeleteClusterInput{Cluster: aws.String(name)})
if err != nil {
return err
}
if *response.Cluster.Status == "INACTIVE" {
return nil
}
return fmt.Errorf("Failed to delete cluster, status: %s" + *response.Cluster.Status)
}
func (s sdk) GetDefaultVPC() (string, error) {
logrus.Debug("Retrieve default VPC")
vpcs, err := s.EC2.DescribeVpcs(&ec2.DescribeVpcsInput{
Filters: []*ec2.Filter{
{
Name: aws.String("isDefault"),
Values: []*string{aws.String("true")},
},
},
})
if err != nil {
return "", err
}
if len(vpcs.Vpcs) == 0 {
return "", fmt.Errorf("account has not default VPC")
}
return *vpcs.Vpcs[0].VpcId, nil
}
func (s sdk) GetSubNets(vpc string) ([]string, error) {
logrus.Debug("Retrieve SubNets")
subnets, err := s.EC2.DescribeSubnets(&ec2.DescribeSubnetsInput{
DryRun: nil,
Filters: []*ec2.Filter{
{
Name: aws.String("vpc-id"),
Values: []*string{ aws.String(vpc)},
},
{
Name: aws.String("default-for-az"),
Values: []*string{aws.String("true")},
},
},
})
if err != nil {
return nil, err
}
ids := []string{}
for _, subnet := range subnets.Subnets {
ids = append(ids, *subnet.SubnetId)
}
return ids, nil
}
func (s sdk) ListRolesForPolicy(policy string) ([]string, error) {
entities, err := s.IAM.ListEntitiesForPolicy(&iam.ListEntitiesForPolicyInput{
EntityFilter: aws.String("Role"),
PolicyArn: aws.String(policy),
})
if err != nil {
return nil, err
}
roles := []string{}
for _, e := range entities.PolicyRoles {
roles = append(roles, *e.RoleName)
}
return roles, nil
}
func (s sdk) GetRoleArn(name string) (string, error) {
role, err := s.IAM.GetRole(&iam.GetRoleInput{
RoleName: aws.String(name),
})
if err != nil {
return "", err
}
return *role.Role.Arn, nil
}
func (s sdk) StackExists(name string) (bool, error) {
stacks, err := s.CF.DescribeStacks(&cloudformation.DescribeStacksInput{
StackName: aws.String(name),
})
if err != nil {
// FIXME doesn't work as expected
return false, nil
}
return len(stacks.Stacks) > 0, nil
}
func (s sdk) CreateStack(name string, template *cf.Template) error {
logrus.Debug("Create CloudFormation stack")
json, err := template.JSON()
if err != nil {
return err
}
_, err = s.CF.CreateStack(&cloudformation.CreateStackInput{
OnFailure: aws.String("DELETE"),
StackName: aws.String(name),
TemplateBody: aws.String(string(json)),
TimeoutInMinutes: aws.Int64(10),
})
return err
}
func (s sdk) DescribeStackEvents(name string) error {
// Fixme implement Paginator on Events and return as a chan(events)
_, err := s.CF.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{
StackName: aws.String(name),
})
return err
}
func (s sdk) DeleteStack(name string) error {
logrus.Debug("Delete CloudFormation stack")
_, err := s.CF.DeleteStack(&cloudformation.DeleteStackInput{
StackName: aws.String(name),
})
return err
}

View File

@ -2,26 +2,19 @@ package amazon
import (
"fmt"
"os"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/docker/ecs-plugin/pkg/compose"
)
func (c *client) ComposeUp(project *compose.Project, loadBalancerArn *string) error {
ok, err := c.ClusterExists()
ok, err := c.api.ClusterExists(c.Cluster)
if err != nil {
return err
}
if !ok {
c.CreateCluster()
c.api.CreateCluster(c.Cluster)
}
_, err = c.CF.DescribeStacks(&cloudformation.DescribeStacksInput{
StackName: aws.String(project.Name),
})
if err == nil {
// FIXME no ErrNotFound err type here
update, err := c.api.StackExists(project.Name)
if update {
return fmt.Errorf("we do not (yet) support updating an existing CloudFormation stack")
}
@ -30,40 +23,12 @@ func (c *client) ComposeUp(project *compose.Project, loadBalancerArn *string) er
return err
}
json, err := template.JSON()
err = c.api.CreateStack(project.Name, template)
if err != nil {
return err
}
_, err = c.CF.ValidateTemplate(&cloudformation.ValidateTemplateInput{
TemplateBody: aws.String(string(json)),
})
if err != nil {
return err
}
_, err = c.CF.CreateStack(&cloudformation.CreateStackInput{
OnFailure: aws.String("DELETE"),
StackName: aws.String(project.Name),
TemplateBody: aws.String(string(json)),
TimeoutInMinutes: aws.Int64(10),
})
if err != nil {
return err
}
events, err := c.CF.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{
StackName: aws.String(project.Name),
})
if err != nil {
return err
}
for _, event := range events.StackEvents {
fmt.Printf("%s %s\n", *event.LogicalResourceId, *event.ResourceStatus)
if *event.ResourceStatus == "CREATE_FAILED" {
fmt.Fprintln(os.Stderr, event.ResourceStatusReason)
}
}
err = c.api.DescribeStackEvents(project.Name)
// TODO monitor progress
return nil