API calls to register services matching compose.yaml

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-04-14 18:03:33 +02:00
parent 91daf0dcc0
commit 4542e05ddf
6 changed files with 267 additions and 2 deletions

View File

@ -3,6 +3,10 @@ package amazon
import ( import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/compose"
) )
@ -25,6 +29,10 @@ func NewClient(profile string, cluster string, region string) (compose.API, erro
Cluster: cluster, Cluster: cluster,
Region: region, Region: region,
sess: sess, sess: sess,
ECS: ecs.New(sess),
EC2: ec2.New(sess),
CW: cloudwatchlogs.New(sess),
IAM: iam.New(sess),
}, nil }, nil
} }
@ -32,6 +40,10 @@ type client struct {
Cluster string Cluster string
Region string Region string
sess *session.Session sess *session.Session
ECS *ecs.ECS
EC2 *ec2.EC2
CW *cloudwatchlogs.CloudWatchLogs
IAM *iam.IAM
} }
var _ compose.API = &client{} var _ compose.API = &client{}

View File

@ -1,11 +1,79 @@
package amazon package amazon
import ( import (
"fmt" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/compose-spec/compose-go/types"
"github.com/docker/ecs-plugin/pkg/compose" "github.com/docker/ecs-plugin/pkg/compose"
) )
func (c *client) ComposeUp(project *compose.Project) error { func (c *client) ComposeUp(project *compose.Project) error {
fmt.Println("TODO Up") vpc, err := c.GetDefaultVPC()
if err != nil {
return err
}
subnets, err := c.GetSubNets(vpc)
if err != nil {
return err
}
securityGroup, err := c.CreateSecurityGroup(project, vpc)
if err != nil {
return err
}
logGroup, err := c.GetOrCreateLogGroup(project.Name)
if err != nil {
return err
}
for _, service := range project.Services {
err = c.CreateService(service, securityGroup, subnets, logGroup)
if err != nil {
return err
}
}
return nil return nil
} }
func (c *client) CreateService(service types.ServiceConfig, securityGroup *string, subnets []*string, logGroup *string) error {
task, err := ConvertToTaskDefinition(service)
if err != nil {
return err
}
role, err := c.GetEcsTaskExecutionRole(service)
if err != nil {
return err
}
task.ExecutionRoleArn = role
for _, def := range task.ContainerDefinitions {
def.LogConfiguration.Options["awslogs-group"] = logGroup
def.LogConfiguration.Options["awslogs-stream-prefix"] = aws.String(service.Name)
def.LogConfiguration.Options["awslogs-region"] = aws.String(c.Region)
}
arn, err := c.RegisterTaskDefinition(task)
if err != nil {
return err
}
_, err = c.ECS.CreateService(&ecs.CreateServiceInput{
Cluster: aws.String(c.Cluster),
DesiredCount: aws.Int64(1), // FIXME get from deploy options
LaunchType: aws.String(ecs.LaunchTypeFargate), //FIXME use service.Isolation tro select EC2 vs Fargate
NetworkConfiguration: &ecs.NetworkConfiguration{
AwsvpcConfiguration: &ecs.AwsVpcConfiguration{
AssignPublicIp: aws.String(ecs.AssignPublicIpEnabled),
SecurityGroups: []*string{securityGroup},
Subnets: subnets,
},
},
ServiceName: aws.String(service.Name),
SchedulingStrategy: aws.String(ecs.SchedulingStrategyReplica),
TaskDefinition: arn,
})
return err
}

21
ecs/pkg/amazon/ecs.go Normal file
View File

@ -0,0 +1,21 @@
package amazon
import (
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/compose-spec/compose-go/types"
"github.com/sirupsen/logrus"
)
func ConvertToTaskDefinition(service types.ServiceConfig) (*ecs.RegisterTaskDefinitionInput, error) {
panic("Please implement me")
}
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
}

26
ecs/pkg/amazon/logs.go Normal file
View File

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

90
ecs/pkg/amazon/network.go Normal file
View File

@ -0,0 +1,90 @@
package amazon
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"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)
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
}

48
ecs/pkg/amazon/roles.go Normal file
View File

@ -0,0 +1,48 @@
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
}