mirror of https://github.com/docker/compose.git
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:
parent
52440a4732
commit
8c0fee5abf
|
@ -1,3 +1,6 @@
|
|||
clean:
|
||||
rm -rf dist/
|
||||
|
||||
build:
|
||||
go build -v -o dist/docker-ecs cmd/main/main.go
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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{}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue