mirror of
https://github.com/docker/compose.git
synced 2025-07-25 14:44:29 +02:00
Merge pull request #633 from docker/ec2
This commit is contained in:
commit
767ed0c20d
@ -23,8 +23,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/compose"
|
|
||||||
|
|
||||||
ecsapi "github.com/aws/aws-sdk-go/service/ecs"
|
ecsapi "github.com/aws/aws-sdk-go/service/ecs"
|
||||||
"github.com/aws/aws-sdk-go/service/elbv2"
|
"github.com/aws/aws-sdk-go/service/elbv2"
|
||||||
cloudmapapi "github.com/aws/aws-sdk-go/service/servicediscovery"
|
cloudmapapi "github.com/aws/aws-sdk-go/service/servicediscovery"
|
||||||
@ -36,7 +34,6 @@ import (
|
|||||||
"github.com/awslabs/goformation/v4/cloudformation/logs"
|
"github.com/awslabs/goformation/v4/cloudformation/logs"
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
|
"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
|
||||||
cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
|
cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/tags"
|
|
||||||
"github.com/compose-spec/compose-go/compatibility"
|
"github.com/compose-spec/compose-go/compatibility"
|
||||||
"github.com/compose-spec/compose-go/errdefs"
|
"github.com/compose-spec/compose-go/errdefs"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
@ -52,7 +49,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
|
func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
|
||||||
template, err := b.convert(project)
|
template, networks, err := b.convert(project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -97,11 +94,16 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = b.createCapacityProvider(ctx, project, networks, template)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return marshall(template)
|
return marshall(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a compose project into a CloudFormation template
|
// Convert a compose project into a CloudFormation template
|
||||||
func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, error) { //nolint:gocyclo
|
func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, map[string]string, error) { //nolint:gocyclo
|
||||||
var checker compatibility.Checker = &fargateCompatibilityChecker{
|
var checker compatibility.Checker = &fargateCompatibilityChecker{
|
||||||
compatibility.AllowList{
|
compatibility.AllowList{
|
||||||
Supported: compatibleComposeAttributes,
|
Supported: compatibleComposeAttributes,
|
||||||
@ -116,7 +118,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !compatibility.IsCompatible(checker) {
|
if !compatibility.IsCompatible(checker) {
|
||||||
return nil, fmt.Errorf("compose file is incompatible with Amazon ECS")
|
return nil, nil, fmt.Errorf("compose file is incompatible with Amazon ECS")
|
||||||
}
|
}
|
||||||
|
|
||||||
template := cloudformation.NewTemplate()
|
template := cloudformation.NewTemplate()
|
||||||
@ -152,7 +154,6 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
Description: "Name of the LoadBalancer to connect to (optional)",
|
Description: "Name of the LoadBalancer to connect to (optional)",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Createmount.nfs4: Connection timed out : unsuccessful EFS utils command execution; code: 32 Cluster is `ParameterClusterName` parameter is not set
|
|
||||||
template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(parameterClusterName))
|
template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(parameterClusterName))
|
||||||
|
|
||||||
cluster := createCluster(project, template)
|
cluster := createCluster(project, template)
|
||||||
@ -168,19 +169,14 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
}
|
}
|
||||||
secret, err := ioutil.ReadFile(s.File)
|
secret, err := ioutil.ReadFile(s.File)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
name := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
|
name := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
|
||||||
template.Resources[name] = &secretsmanager.Secret{
|
template.Resources[name] = &secretsmanager.Secret{
|
||||||
Description: "",
|
Description: "",
|
||||||
SecretString: string(secret),
|
SecretString: string(secret),
|
||||||
Tags: []tags.Tag{
|
Tags: projectTags(project),
|
||||||
{
|
|
||||||
Key: compose.ProjectTag,
|
|
||||||
Value: project.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
s.Name = cloudformation.Ref(name)
|
s.Name = cloudformation.Ref(name)
|
||||||
project.Secrets[i] = s
|
project.Secrets[i] = s
|
||||||
@ -197,7 +193,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
|
|
||||||
definition, err := convert(project, service)
|
definition, err := convert(project, service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
taskExecutionRole := createTaskExecutionRole(service, definition, template)
|
taskExecutionRole := createTaskExecutionRole(service, definition, template)
|
||||||
@ -226,10 +222,8 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
for _, port := range service.Ports {
|
for _, port := range service.Ports {
|
||||||
protocol := strings.ToUpper(port.Protocol)
|
protocol := strings.ToUpper(port.Protocol)
|
||||||
if getLoadBalancerType(project) == elbv2.LoadBalancerTypeEnumApplication {
|
if getLoadBalancerType(project) == elbv2.LoadBalancerTypeEnumApplication {
|
||||||
protocol = elbv2.ProtocolEnumHttps
|
// we don't set Https as a certificate must be specified for HTTPS listeners
|
||||||
if port.Published == 80 {
|
protocol = elbv2.ProtocolEnumHttp
|
||||||
protocol = elbv2.ProtocolEnumHttp
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if loadBalancerARN != "" {
|
if loadBalancerARN != "" {
|
||||||
targetGroupName := createTargetGroup(project, service, port, template, protocol)
|
targetGroupName := createTargetGroup(project, service, port, template, protocol)
|
||||||
@ -255,7 +249,16 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
|
|
||||||
minPercent, maxPercent, err := computeRollingUpdateLimits(service)
|
minPercent, maxPercent, err := computeRollingUpdateLimits(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
assignPublicIP := ecsapi.AssignPublicIpEnabled
|
||||||
|
launchType := ecsapi.LaunchTypeFargate
|
||||||
|
platformVersion := "1.4.0" // LATEST which is set to 1.3.0 (?) which doesn’t allow efs volumes.
|
||||||
|
if requireEC2(service) {
|
||||||
|
assignPublicIP = ecsapi.AssignPublicIpDisabled
|
||||||
|
launchType = ecsapi.LaunchTypeEc2
|
||||||
|
platformVersion = "" // The platform version must be null when specifying an EC2 launch type
|
||||||
}
|
}
|
||||||
|
|
||||||
template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
|
template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
|
||||||
@ -269,11 +272,12 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
MaximumPercent: maxPercent,
|
MaximumPercent: maxPercent,
|
||||||
MinimumHealthyPercent: minPercent,
|
MinimumHealthyPercent: minPercent,
|
||||||
},
|
},
|
||||||
LaunchType: ecsapi.LaunchTypeFargate,
|
LaunchType: launchType,
|
||||||
|
// TODO we miss support for https://github.com/aws/containers-roadmap/issues/631 to select a capacity provider
|
||||||
LoadBalancers: serviceLB,
|
LoadBalancers: serviceLB,
|
||||||
NetworkConfiguration: &ecs.Service_NetworkConfiguration{
|
NetworkConfiguration: &ecs.Service_NetworkConfiguration{
|
||||||
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
|
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
|
||||||
AssignPublicIp: ecsapi.AssignPublicIpEnabled,
|
AssignPublicIp: assignPublicIP,
|
||||||
SecurityGroups: serviceSecurityGroups,
|
SecurityGroups: serviceSecurityGroups,
|
||||||
Subnets: []string{
|
Subnets: []string{
|
||||||
cloudformation.Ref(parameterSubnet1Id),
|
cloudformation.Ref(parameterSubnet1Id),
|
||||||
@ -281,24 +285,15 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PlatformVersion: "1.4.0", // LATEST which is set to 1.3.0 (?) which doesn’t allow efs volumes.
|
PlatformVersion: platformVersion,
|
||||||
PropagateTags: ecsapi.PropagateTagsService,
|
PropagateTags: ecsapi.PropagateTagsService,
|
||||||
SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
|
SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
|
||||||
ServiceRegistries: []ecs.Service_ServiceRegistry{serviceRegistry},
|
ServiceRegistries: []ecs.Service_ServiceRegistry{serviceRegistry},
|
||||||
Tags: []tags.Tag{
|
Tags: serviceTags(project, service),
|
||||||
{
|
TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
|
||||||
Key: compose.ProjectTag,
|
|
||||||
Value: project.Name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: compose.ServiceTag,
|
|
||||||
Value: service.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return template, nil
|
return template, networks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createLogGroup(project *types.Project, template *cloudformation.Template) {
|
func createLogGroup(project *types.Project, template *cloudformation.Template) {
|
||||||
@ -413,12 +408,7 @@ func createLoadBalancer(project *types.Project, template *cloudformation.Templat
|
|||||||
cloudformation.Ref(parameterSubnet1Id),
|
cloudformation.Ref(parameterSubnet1Id),
|
||||||
cloudformation.Ref(parameterSubnet2Id),
|
cloudformation.Ref(parameterSubnet2Id),
|
||||||
},
|
},
|
||||||
Tags: []tags.Tag{
|
Tags: projectTags(project),
|
||||||
{
|
|
||||||
Key: compose.ProjectTag,
|
|
||||||
Value: project.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Type: loadBalancerType,
|
Type: loadBalancerType,
|
||||||
AWSCloudFormationCondition: "CreateLoadBalancer",
|
AWSCloudFormationCondition: "CreateLoadBalancer",
|
||||||
}
|
}
|
||||||
@ -462,16 +452,12 @@ func createTargetGroup(project *types.Project, service types.ServiceConfig, port
|
|||||||
port.Published,
|
port.Published,
|
||||||
)
|
)
|
||||||
template.Resources[targetGroupName] = &elasticloadbalancingv2.TargetGroup{
|
template.Resources[targetGroupName] = &elasticloadbalancingv2.TargetGroup{
|
||||||
Port: int(port.Target),
|
HealthCheckEnabled: false,
|
||||||
Protocol: protocol,
|
Port: int(port.Target),
|
||||||
Tags: []tags.Tag{
|
Protocol: protocol,
|
||||||
{
|
Tags: projectTags(project),
|
||||||
Key: compose.ProjectTag,
|
TargetType: elbv2.TargetTypeEnumIp,
|
||||||
Value: project.Name,
|
VpcId: cloudformation.Ref(parameterVPCId),
|
||||||
},
|
|
||||||
},
|
|
||||||
VpcId: cloudformation.Ref(parameterVPCId),
|
|
||||||
TargetType: elbv2.TargetTypeEnumIp,
|
|
||||||
}
|
}
|
||||||
return targetGroupName
|
return targetGroupName
|
||||||
}
|
}
|
||||||
@ -507,7 +493,7 @@ func createTaskExecutionRole(service types.ServiceConfig, definition *ecs.TaskDe
|
|||||||
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
|
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
|
||||||
policies := createPolicies(service, definition)
|
policies := createPolicies(service, definition)
|
||||||
template.Resources[taskExecutionRole] = &iam.Role{
|
template.Resources[taskExecutionRole] = &iam.Role{
|
||||||
AssumeRolePolicyDocument: assumeRolePolicyDocument,
|
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
||||||
Policies: policies,
|
Policies: policies,
|
||||||
ManagedPolicyArns: []string{
|
ManagedPolicyArns: []string{
|
||||||
ecsTaskExecutionPolicy,
|
ecsTaskExecutionPolicy,
|
||||||
@ -535,7 +521,7 @@ func createTaskRole(service types.ServiceConfig, template *cloudformation.Templa
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
template.Resources[taskRole] = &iam.Role{
|
template.Resources[taskRole] = &iam.Role{
|
||||||
AssumeRolePolicyDocument: assumeRolePolicyDocument,
|
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
||||||
Policies: rolePolicies,
|
Policies: rolePolicies,
|
||||||
ManagedPolicyArns: managedPolicies,
|
ManagedPolicyArns: managedPolicies,
|
||||||
}
|
}
|
||||||
@ -544,13 +530,8 @@ func createTaskRole(service types.ServiceConfig, template *cloudformation.Templa
|
|||||||
|
|
||||||
func createCluster(project *types.Project, template *cloudformation.Template) string {
|
func createCluster(project *types.Project, template *cloudformation.Template) string {
|
||||||
template.Resources["Cluster"] = &ecs.Cluster{
|
template.Resources["Cluster"] = &ecs.Cluster{
|
||||||
ClusterName: project.Name,
|
ClusterName: project.Name,
|
||||||
Tags: []tags.Tag{
|
Tags: projectTags(project),
|
||||||
{
|
|
||||||
Key: compose.ProjectTag,
|
|
||||||
Value: project.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AWSCloudFormationCondition: "CreateCluster",
|
AWSCloudFormationCondition: "CreateCluster",
|
||||||
}
|
}
|
||||||
cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(parameterClusterName))
|
cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(parameterClusterName))
|
||||||
@ -580,11 +561,15 @@ func convertNetwork(project *types.Project, net types.NetworkConfig, vpc string,
|
|||||||
for _, service := range project.Services {
|
for _, service := range project.Services {
|
||||||
if _, ok := service.Networks[net.Name]; ok {
|
if _, ok := service.Networks[net.Name]; ok {
|
||||||
for _, port := range service.Ports {
|
for _, port := range service.Ports {
|
||||||
|
protocol := strings.ToUpper(port.Protocol)
|
||||||
|
if protocol == "" {
|
||||||
|
protocol = "-1"
|
||||||
|
}
|
||||||
ingresses = append(ingresses, ec2.SecurityGroup_Ingress{
|
ingresses = append(ingresses, ec2.SecurityGroup_Ingress{
|
||||||
CidrIp: "0.0.0.0/0",
|
CidrIp: "0.0.0.0/0",
|
||||||
Description: fmt.Sprintf("%s:%d/%s", service.Name, port.Target, port.Protocol),
|
Description: fmt.Sprintf("%s:%d/%s", service.Name, port.Target, port.Protocol),
|
||||||
FromPort: int(port.Target),
|
FromPort: int(port.Target),
|
||||||
IpProtocol: strings.ToUpper(port.Protocol),
|
IpProtocol: protocol,
|
||||||
ToPort: int(port.Target),
|
ToPort: int(port.Target),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -598,16 +583,7 @@ func convertNetwork(project *types.Project, net types.NetworkConfig, vpc string,
|
|||||||
GroupName: securityGroup,
|
GroupName: securityGroup,
|
||||||
SecurityGroupIngress: ingresses,
|
SecurityGroupIngress: ingresses,
|
||||||
VpcId: vpc,
|
VpcId: vpc,
|
||||||
Tags: []tags.Tag{
|
Tags: networkTags(project, net),
|
||||||
{
|
|
||||||
Key: compose.ProjectTag,
|
|
||||||
Value: project.Name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: compose.NetworkTag,
|
|
||||||
Value: net.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ingress := securityGroup + "Ingress"
|
ingress := securityGroup + "Ingress"
|
||||||
|
@ -245,6 +245,15 @@ services:
|
|||||||
|
|
||||||
func TestTaskSizeConvert(t *testing.T) {
|
func TestTaskSizeConvert(t *testing.T) {
|
||||||
template := convertYaml(t, `
|
template := convertYaml(t, `
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
image: nginx
|
||||||
|
`)
|
||||||
|
def := template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
|
||||||
|
assert.Equal(t, def.Cpu, "256")
|
||||||
|
assert.Equal(t, def.Memory, "512")
|
||||||
|
|
||||||
|
template = convertYaml(t, `
|
||||||
services:
|
services:
|
||||||
test:
|
test:
|
||||||
image: nginx
|
image: nginx
|
||||||
@ -253,11 +262,8 @@ services:
|
|||||||
limits:
|
limits:
|
||||||
cpus: '0.5'
|
cpus: '0.5'
|
||||||
memory: 2048M
|
memory: 2048M
|
||||||
reservations:
|
|
||||||
cpus: '0.5'
|
|
||||||
memory: 2048M
|
|
||||||
`)
|
`)
|
||||||
def := template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
|
def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
|
||||||
assert.Equal(t, def.Cpu, "512")
|
assert.Equal(t, def.Cpu, "512")
|
||||||
assert.Equal(t, def.Memory, "2048")
|
assert.Equal(t, def.Memory, "2048")
|
||||||
|
|
||||||
@ -270,13 +276,45 @@ services:
|
|||||||
limits:
|
limits:
|
||||||
cpus: '4'
|
cpus: '4'
|
||||||
memory: 8192M
|
memory: 8192M
|
||||||
reservations:
|
|
||||||
cpus: '4'
|
|
||||||
memory: 8192M
|
|
||||||
`)
|
`)
|
||||||
def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
|
def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
|
||||||
assert.Equal(t, def.Cpu, "4096")
|
assert.Equal(t, def.Cpu, "4096")
|
||||||
assert.Equal(t, def.Memory, "8192")
|
assert.Equal(t, def.Memory, "8192")
|
||||||
|
|
||||||
|
template = convertYaml(t, `
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
image: nginx
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '4'
|
||||||
|
memory: 792Mb
|
||||||
|
reservations:
|
||||||
|
generic_resources:
|
||||||
|
- discrete_resource_spec:
|
||||||
|
kind: gpus
|
||||||
|
value: 2
|
||||||
|
`)
|
||||||
|
def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
|
||||||
|
assert.Equal(t, def.Cpu, "4000")
|
||||||
|
assert.Equal(t, def.Memory, "792")
|
||||||
|
|
||||||
|
template = convertYaml(t, `
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
image: nginx
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
generic_resources:
|
||||||
|
- discrete_resource_spec:
|
||||||
|
kind: gpus
|
||||||
|
value: 2
|
||||||
|
`)
|
||||||
|
def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
|
||||||
|
assert.Equal(t, def.Cpu, "")
|
||||||
|
assert.Equal(t, def.Memory, "")
|
||||||
}
|
}
|
||||||
func TestTaskSizeConvertFailure(t *testing.T) {
|
func TestTaskSizeConvertFailure(t *testing.T) {
|
||||||
model := loadConfig(t, `
|
model := loadConfig(t, `
|
||||||
@ -290,7 +328,7 @@ services:
|
|||||||
memory: 2043248M
|
memory: 2043248M
|
||||||
`)
|
`)
|
||||||
backend := &ecsAPIService{}
|
backend := &ecsAPIService{}
|
||||||
_, err := backend.convert(model)
|
_, _, err := backend.convert(model)
|
||||||
assert.ErrorContains(t, err, "the resources requested are not supported by ECS/Fargate")
|
assert.ErrorContains(t, err, "the resources requested are not supported by ECS/Fargate")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +412,7 @@ services:
|
|||||||
|
|
||||||
func convertResultAsString(t *testing.T, project *types.Project) string {
|
func convertResultAsString(t *testing.T, project *types.Project) string {
|
||||||
backend := &ecsAPIService{}
|
backend := &ecsAPIService{}
|
||||||
template, err := backend.convert(project)
|
template, _, err := backend.convert(project)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
resultAsJSON, err := marshall(template)
|
resultAsJSON, err := marshall(template)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
@ -394,7 +432,7 @@ func load(t *testing.T, paths ...string) *types.Project {
|
|||||||
func convertYaml(t *testing.T, yaml string) *cloudformation.Template {
|
func convertYaml(t *testing.T, yaml string) *cloudformation.Template {
|
||||||
project := loadConfig(t, yaml)
|
project := loadConfig(t, yaml)
|
||||||
backend := &ecsAPIService{}
|
backend := &ecsAPIService{}
|
||||||
template, err := backend.convert(project)
|
template, _, err := backend.convert(project)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
139
ecs/convert.go
139
ecs/convert.go
@ -102,6 +102,11 @@ func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reservations *types.Resource
|
||||||
|
if service.Deploy != nil && service.Deploy.Resources.Reservations != nil {
|
||||||
|
reservations = service.Deploy.Resources.Reservations
|
||||||
|
}
|
||||||
|
|
||||||
containers := append(initContainers, ecs.TaskDefinition_ContainerDefinition{
|
containers := append(initContainers, ecs.TaskDefinition_ContainerDefinition{
|
||||||
Command: service.Command,
|
Command: service.Command,
|
||||||
DisableNetworking: service.NetworkMode == "none",
|
DisableNetworking: service.NetworkMode == "none",
|
||||||
@ -129,7 +134,7 @@ func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
|
|||||||
PseudoTerminal: service.Tty,
|
PseudoTerminal: service.Tty,
|
||||||
ReadonlyRootFilesystem: service.ReadOnly,
|
ReadonlyRootFilesystem: service.ReadOnly,
|
||||||
RepositoryCredentials: credential,
|
RepositoryCredentials: credential,
|
||||||
ResourceRequirements: nil,
|
ResourceRequirements: toTaskResourceRequirements(reservations),
|
||||||
StartTimeout: 0,
|
StartTimeout: 0,
|
||||||
StopTimeout: durationToInt(service.StopGracePeriod),
|
StopTimeout: durationToInt(service.StopGracePeriod),
|
||||||
SystemControls: toSystemControls(service.Sysctls),
|
SystemControls: toSystemControls(service.Sysctls),
|
||||||
@ -139,21 +144,44 @@ func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
|
|||||||
WorkingDirectory: service.WorkingDir,
|
WorkingDirectory: service.WorkingDir,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
launchType := ecsapi.LaunchTypeFargate
|
||||||
|
if requireEC2(service) {
|
||||||
|
launchType = ecsapi.LaunchTypeEc2
|
||||||
|
}
|
||||||
|
|
||||||
return &ecs.TaskDefinition{
|
return &ecs.TaskDefinition{
|
||||||
ContainerDefinitions: containers,
|
ContainerDefinitions: containers,
|
||||||
Cpu: cpu,
|
Cpu: cpu,
|
||||||
Family: fmt.Sprintf("%s-%s", project.Name, service.Name),
|
Family: fmt.Sprintf("%s-%s", project.Name, service.Name),
|
||||||
IpcMode: service.Ipc,
|
IpcMode: service.Ipc,
|
||||||
Memory: mem,
|
Memory: mem,
|
||||||
NetworkMode: ecsapi.NetworkModeAwsvpc, // FIXME could be set by service.NetworkMode, Fargate only supports network mode ‘awsvpc’.
|
NetworkMode: ecsapi.NetworkModeAwsvpc, // FIXME could be set by service.NetworkMode, Fargate only supports network mode ‘awsvpc’.
|
||||||
PidMode: service.Pid,
|
PidMode: service.Pid,
|
||||||
PlacementConstraints: toPlacementConstraints(service.Deploy),
|
PlacementConstraints: toPlacementConstraints(service.Deploy),
|
||||||
ProxyConfiguration: nil,
|
ProxyConfiguration: nil,
|
||||||
RequiresCompatibilities: []string{ecsapi.LaunchTypeFargate},
|
RequiresCompatibilities: []string{
|
||||||
Volumes: volumes,
|
launchType,
|
||||||
|
},
|
||||||
|
Volumes: volumes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toTaskResourceRequirements(reservations *types.Resource) []ecs.TaskDefinition_ResourceRequirement {
|
||||||
|
if reservations == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var requirements []ecs.TaskDefinition_ResourceRequirement
|
||||||
|
for _, r := range reservations.GenericResources {
|
||||||
|
if r.DiscreteResourceSpec.Kind == "gpus" {
|
||||||
|
requirements = append(requirements, ecs.TaskDefinition_ResourceRequirement{
|
||||||
|
Type: ecsapi.ResourceTypeGpu,
|
||||||
|
Value: fmt.Sprint(r.DiscreteResourceSpec.Value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requirements
|
||||||
|
}
|
||||||
|
|
||||||
func createSecretsSideCar(project *types.Project, service types.ServiceConfig, logConfiguration *ecs.TaskDefinition_LogConfiguration) (
|
func createSecretsSideCar(project *types.Project, service types.ServiceConfig, logConfiguration *ecs.TaskDefinition_LogConfiguration) (
|
||||||
ecs.TaskDefinition_Volume,
|
ecs.TaskDefinition_Volume,
|
||||||
ecs.TaskDefinition_MountPoint,
|
ecs.TaskDefinition_MountPoint,
|
||||||
@ -295,8 +323,24 @@ func toSystemControls(sysctls types.Mapping) []ecs.TaskDefinition_SystemControl
|
|||||||
const miB = 1024 * 1024
|
const miB = 1024 * 1024
|
||||||
|
|
||||||
func toLimits(service types.ServiceConfig) (string, string, error) {
|
func toLimits(service types.ServiceConfig) (string, string, error) {
|
||||||
|
mem, cpu, err := getConfiguredLimits(service)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if requireEC2(service) {
|
||||||
|
// just return configured limits expressed in Mb and CPU units
|
||||||
|
var cpuLimit, memLimit string
|
||||||
|
if cpu > 0 {
|
||||||
|
cpuLimit = fmt.Sprint(cpu)
|
||||||
|
}
|
||||||
|
if mem > 0 {
|
||||||
|
memLimit = fmt.Sprint(mem / miB)
|
||||||
|
}
|
||||||
|
return cpuLimit, memLimit, nil
|
||||||
|
}
|
||||||
|
|
||||||
// All possible cpu/mem values for Fargate
|
// All possible cpu/mem values for Fargate
|
||||||
cpuToMem := map[int64][]types.UnitBytes{
|
fargateCPUToMem := map[int64][]types.UnitBytes{
|
||||||
256: {512, 1024, 2048},
|
256: {512, 1024, 2048},
|
||||||
512: {1024, 2048, 3072, 4096},
|
512: {1024, 2048, 3072, 4096},
|
||||||
1024: {2048, 3072, 4096, 5120, 6144, 7168, 8192},
|
1024: {2048, 3072, 4096, 5120, 6144, 7168, 8192},
|
||||||
@ -305,37 +349,22 @@ func toLimits(service types.ServiceConfig) (string, string, error) {
|
|||||||
}
|
}
|
||||||
cpuLimit := "256"
|
cpuLimit := "256"
|
||||||
memLimit := "512"
|
memLimit := "512"
|
||||||
|
if mem == 0 && cpu == 0 {
|
||||||
if service.Deploy == nil {
|
|
||||||
return cpuLimit, memLimit, nil
|
return cpuLimit, memLimit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
limits := service.Deploy.Resources.Limits
|
|
||||||
if limits == nil {
|
|
||||||
return cpuLimit, memLimit, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if limits.NanoCPUs == "" {
|
|
||||||
return cpuLimit, memLimit, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := opts.ParseCPUs(limits.NanoCPUs)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var cpus []int64
|
var cpus []int64
|
||||||
for k := range cpuToMem {
|
for k := range fargateCPUToMem {
|
||||||
cpus = append(cpus, k)
|
cpus = append(cpus, k)
|
||||||
}
|
}
|
||||||
sort.Slice(cpus, func(i, j int) bool { return cpus[i] < cpus[j] })
|
sort.Slice(cpus, func(i, j int) bool { return cpus[i] < cpus[j] })
|
||||||
|
|
||||||
for _, cpu := range cpus {
|
for _, fargateCPU := range cpus {
|
||||||
mem := cpuToMem[cpu]
|
options := fargateCPUToMem[fargateCPU]
|
||||||
if v <= cpu*miB {
|
if cpu <= fargateCPU {
|
||||||
for _, m := range mem {
|
for _, m := range options {
|
||||||
if limits.MemoryBytes <= m*miB {
|
if mem <= m*miB {
|
||||||
cpuLimit = strconv.FormatInt(cpu, 10)
|
cpuLimit = strconv.FormatInt(fargateCPU, 10)
|
||||||
memLimit = strconv.FormatInt(int64(m), 10)
|
memLimit = strconv.FormatInt(int64(m), 10)
|
||||||
return cpuLimit, memLimit, nil
|
return cpuLimit, memLimit, nil
|
||||||
}
|
}
|
||||||
@ -345,6 +374,27 @@ func toLimits(service types.ServiceConfig) (string, string, error) {
|
|||||||
return "", "", fmt.Errorf("the resources requested are not supported by ECS/Fargate")
|
return "", "", fmt.Errorf("the resources requested are not supported by ECS/Fargate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getConfiguredLimits(service types.ServiceConfig) (types.UnitBytes, int64, error) {
|
||||||
|
if service.Deploy == nil {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
limits := service.Deploy.Resources.Limits
|
||||||
|
if limits == nil {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if limits.NanoCPUs == "" {
|
||||||
|
return limits.MemoryBytes, 0, nil
|
||||||
|
}
|
||||||
|
v, err := opts.ParseCPUs(limits.NanoCPUs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return limits.MemoryBytes, v / 1e6, nil
|
||||||
|
}
|
||||||
|
|
||||||
func toContainerReservation(service types.ServiceConfig) (string, int) {
|
func toContainerReservation(service types.ServiceConfig) (string, int) {
|
||||||
cpuReservation := ".0"
|
cpuReservation := ".0"
|
||||||
memReservation := 0
|
memReservation := 0
|
||||||
@ -490,3 +540,20 @@ func getRepoCredentials(service types.ServiceConfig) *ecs.TaskDefinition_Reposit
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requireEC2(s types.ServiceConfig) bool {
|
||||||
|
return gpuRequirements(s) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func gpuRequirements(s types.ServiceConfig) int64 {
|
||||||
|
if deploy := s.Deploy; deploy != nil {
|
||||||
|
if reservations := deploy.Resources.Reservations; reservations != nil {
|
||||||
|
for _, resource := range reservations.GenericResources {
|
||||||
|
if resource.DiscreteResourceSpec.Kind == "gpus" {
|
||||||
|
return resource.DiscreteResourceSpec.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
113
ecs/ec2.go
Normal file
113
ecs/ec2.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation"
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation/autoscaling"
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation/iam"
|
||||||
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *types.Project, networks map[string]string, template *cloudformation.Template) error {
|
||||||
|
var ec2 bool
|
||||||
|
for _, s := range project.Services {
|
||||||
|
if requireEC2(s) {
|
||||||
|
ec2 = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ec2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ami, err := b.SDK.GetParameter(ctx, "/aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
machineType, err := guessMachineType(project)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var securityGroups []string
|
||||||
|
for _, r := range networks {
|
||||||
|
securityGroups = append(securityGroups, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
template.Resources["CapacityProvider"] = &ecs.CapacityProvider{
|
||||||
|
AutoScalingGroupProvider: &ecs.CapacityProvider_AutoScalingGroupProvider{
|
||||||
|
AutoScalingGroupArn: cloudformation.Ref("AutoscalingGroup"),
|
||||||
|
ManagedScaling: &ecs.CapacityProvider_ManagedScaling{
|
||||||
|
TargetCapacity: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tags: projectTags(project),
|
||||||
|
AWSCloudFormationCondition: "CreateCluster",
|
||||||
|
}
|
||||||
|
|
||||||
|
template.Resources["AutoscalingGroup"] = &autoscaling.AutoScalingGroup{
|
||||||
|
LaunchConfigurationName: cloudformation.Ref("LaunchConfiguration"),
|
||||||
|
MaxSize: "10", //TODO
|
||||||
|
MinSize: "1",
|
||||||
|
VPCZoneIdentifier: []string{
|
||||||
|
cloudformation.Ref(parameterSubnet1Id),
|
||||||
|
cloudformation.Ref(parameterSubnet2Id),
|
||||||
|
},
|
||||||
|
AWSCloudFormationCondition: "CreateCluster",
|
||||||
|
}
|
||||||
|
|
||||||
|
userData := base64.StdEncoding.EncodeToString([]byte(
|
||||||
|
fmt.Sprintf("#!/bin/bash\necho ECS_CLUSTER=%s >> /etc/ecs/ecs.config", project.Name)))
|
||||||
|
|
||||||
|
template.Resources["LaunchConfiguration"] = &autoscaling.LaunchConfiguration{
|
||||||
|
ImageId: ami,
|
||||||
|
InstanceType: machineType,
|
||||||
|
SecurityGroups: securityGroups,
|
||||||
|
IamInstanceProfile: cloudformation.Ref("EC2InstanceProfile"),
|
||||||
|
UserData: userData,
|
||||||
|
AWSCloudFormationCondition: "CreateCluster",
|
||||||
|
}
|
||||||
|
|
||||||
|
template.Resources["EC2InstanceProfile"] = &iam.InstanceProfile{
|
||||||
|
Roles: []string{cloudformation.Ref("EC2InstanceRole")},
|
||||||
|
AWSCloudFormationCondition: "CreateCluster",
|
||||||
|
}
|
||||||
|
|
||||||
|
template.Resources["EC2InstanceRole"] = &iam.Role{
|
||||||
|
AssumeRolePolicyDocument: ec2InstanceAssumeRolePolicyDocument,
|
||||||
|
ManagedPolicyArns: []string{
|
||||||
|
ecsEC2InstanceRole,
|
||||||
|
},
|
||||||
|
Tags: projectTags(project),
|
||||||
|
AWSCloudFormationCondition: "CreateCluster",
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster := template.Resources["Cluster"].(*ecs.Cluster)
|
||||||
|
cluster.CapacityProviders = []string{
|
||||||
|
cloudformation.Ref("CapacityProvider"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
48
ecs/gpu.go
48
ecs/gpu.go
@ -34,23 +34,47 @@ type machine struct {
|
|||||||
|
|
||||||
type family []machine
|
type family []machine
|
||||||
|
|
||||||
var p3family = family{
|
var gpufamily = family{
|
||||||
{
|
{
|
||||||
id: "p3.2xlarge",
|
id: "g4dn.xlarge",
|
||||||
cpus: 8,
|
cpus: 4,
|
||||||
memory: 64 * units.GiB,
|
memory: 16 * units.GiB,
|
||||||
gpus: 2,
|
gpus: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "p3.8xlarge",
|
id: "g4dn.2xlarge",
|
||||||
|
cpus: 8,
|
||||||
|
memory: 32 * units.GiB,
|
||||||
|
gpus: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "g4dn.4xlarge",
|
||||||
|
cpus: 16,
|
||||||
|
memory: 64 * units.GiB,
|
||||||
|
gpus: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "g4dn.8xlarge",
|
||||||
cpus: 32,
|
cpus: 32,
|
||||||
memory: 244 * units.GiB,
|
memory: 128 * units.GiB,
|
||||||
|
gpus: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "g4dn.12xlarge",
|
||||||
|
cpus: 48,
|
||||||
|
memory: 192 * units.GiB,
|
||||||
gpus: 4,
|
gpus: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "p3.16xlarge",
|
id: "g4dn.16xlarge",
|
||||||
cpus: 64,
|
cpus: 64,
|
||||||
memory: 488 * units.GiB,
|
memory: 256 * units.GiB,
|
||||||
|
gpus: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "g4dn.metal",
|
||||||
|
cpus: 96,
|
||||||
|
memory: 384 * units.GiB,
|
||||||
gpus: 8,
|
gpus: 8,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -82,9 +106,9 @@ func guessMachineType(project *types.Project) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceType, err := p3family.
|
instanceType, err := gpufamily.
|
||||||
filter(func(m machine) bool {
|
filter(func(m machine) bool {
|
||||||
return m.memory >= requirements.memory
|
return m.memory > requirements.memory // actual memory available for ECS tasks < total machine memory
|
||||||
}).
|
}).
|
||||||
filter(func(m machine) bool {
|
filter(func(m machine) bool {
|
||||||
return m.cpus >= requirements.cpus
|
return m.cpus >= requirements.cpus
|
||||||
@ -92,7 +116,7 @@ func guessMachineType(project *types.Project) (string, error) {
|
|||||||
filter(func(m machine) bool {
|
filter(func(m machine) bool {
|
||||||
return m.gpus >= requirements.gpus
|
return m.gpus >= requirements.gpus
|
||||||
}).
|
}).
|
||||||
firstOrError("none of the Amazon EC2 P3 instance types meet the requirements for memory:%d cpu:%f gpus:%d", requirements.memory, requirements.cpus, requirements.gpus)
|
firstOrError("none of the Amazon EC2 G4 instance types meet the requirements for memory:%d cpu:%f gpus:%d", requirements.memory, requirements.cpus, requirements.gpus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ services:
|
|||||||
kind: gpus
|
kind: gpus
|
||||||
value: 1
|
value: 1
|
||||||
`,
|
`,
|
||||||
want: "p3.2xlarge",
|
want: "g4dn.xlarge",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -58,7 +58,7 @@ services:
|
|||||||
kind: gpus
|
kind: gpus
|
||||||
value: 4
|
value: 4
|
||||||
`,
|
`,
|
||||||
want: "p3.8xlarge",
|
want: "g4dn.12xlarge",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -76,7 +76,7 @@ services:
|
|||||||
kind: gpus
|
kind: gpus
|
||||||
value: 2
|
value: 2
|
||||||
`,
|
`,
|
||||||
want: "p3.16xlarge",
|
want: "g4dn.metal",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -95,7 +95,7 @@ services:
|
|||||||
kind: gpus
|
kind: gpus
|
||||||
value: 2
|
value: 2
|
||||||
`,
|
`,
|
||||||
want: "p3.8xlarge",
|
want: "g4dn.12xlarge",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
16
ecs/iam.go
16
ecs/iam.go
@ -19,13 +19,14 @@ package ecs
|
|||||||
const (
|
const (
|
||||||
ecsTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
|
ecsTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
|
||||||
ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
|
ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
|
||||||
|
ecsEC2InstanceRole = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
|
||||||
|
|
||||||
actionGetSecretValue = "secretsmanager:GetSecretValue"
|
actionGetSecretValue = "secretsmanager:GetSecretValue"
|
||||||
actionGetParameters = "ssm:GetParameters"
|
actionGetParameters = "ssm:GetParameters"
|
||||||
actionDecrypt = "kms:Decrypt"
|
actionDecrypt = "kms:Decrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var assumeRolePolicyDocument = PolicyDocument{
|
var ecsTaskAssumeRolePolicyDocument = PolicyDocument{
|
||||||
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
|
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
|
||||||
Statement: []PolicyStatement{
|
Statement: []PolicyStatement{
|
||||||
{
|
{
|
||||||
@ -38,6 +39,19 @@ var assumeRolePolicyDocument = PolicyDocument{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ec2InstanceAssumeRolePolicyDocument = PolicyDocument{
|
||||||
|
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
|
||||||
|
Statement: []PolicyStatement{
|
||||||
|
{
|
||||||
|
Effect: "Allow",
|
||||||
|
Principal: PolicyPrincipal{
|
||||||
|
Service: "ec2.amazonaws.com",
|
||||||
|
},
|
||||||
|
Action: []string{"sts:AssumeRole"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// PolicyDocument describes an IAM policy document
|
// PolicyDocument describes an IAM policy document
|
||||||
// could alternatively depend on https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/master/cmd/clusterawsadm/api/iam/v1alpha1/types.go
|
// could alternatively depend on https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/master/cmd/clusterawsadm/api/iam/v1alpha1/types.go
|
||||||
type PolicyDocument struct {
|
type PolicyDocument struct {
|
||||||
|
33
ecs/sdk.go
33
ecs/sdk.go
@ -18,10 +18,14 @@ package ecs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/service/ssm"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/compose"
|
"github.com/docker/compose-cli/api/compose"
|
||||||
"github.com/docker/compose-cli/api/secrets"
|
"github.com/docker/compose-cli/api/secrets"
|
||||||
|
|
||||||
@ -56,6 +60,7 @@ type sdk struct {
|
|||||||
IAM iamiface.IAMAPI
|
IAM iamiface.IAMAPI
|
||||||
CF cloudformationiface.CloudFormationAPI
|
CF cloudformationiface.CloudFormationAPI
|
||||||
SM secretsmanageriface.SecretsManagerAPI
|
SM secretsmanageriface.SecretsManagerAPI
|
||||||
|
SSM ssmiface.SSMAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSDK(sess *session.Session) sdk {
|
func newSDK(sess *session.Session) sdk {
|
||||||
@ -71,6 +76,7 @@ func newSDK(sess *session.Session) sdk {
|
|||||||
IAM: iam.New(sess),
|
IAM: iam.New(sess),
|
||||||
CF: cloudformation.New(sess),
|
CF: cloudformation.New(sess),
|
||||||
SM: secretsmanager.New(sess),
|
SM: secretsmanager.New(sess),
|
||||||
|
SSM: ssm.New(sess),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +188,7 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
|
|||||||
StackName: aws.String(name),
|
StackName: aws.String(name),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), fmt.Sprintf("ValidationError: Stack with id %s does not exist", name)) {
|
if strings.HasPrefix(err.Error(), fmt.Sprintf("ValidationError: Stack with ID %s does not exist", name)) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -688,3 +694,28 @@ func (s sdk) WithVolumeSecurityGroups(ctx context.Context, id string, fn func(se
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s sdk) GetParameter(ctx context.Context, name string) (string, error) {
|
||||||
|
parameter, err := s.SSM.GetParameterWithContext(ctx, &ssm.GetParameterInput{
|
||||||
|
Name: aws.String(name),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := *parameter.Parameter.Value
|
||||||
|
var ami struct {
|
||||||
|
SchemaVersion int `json:"schema_version"`
|
||||||
|
ImageName string `json:"image_name"`
|
||||||
|
ImageID string `json:"image_id"`
|
||||||
|
OS string `json:"os"`
|
||||||
|
ECSRuntimeVersion string `json:"ecs_runtime_verion"`
|
||||||
|
ECSAgentVersion string `json:"ecs_agent_version"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(value), &ami)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ami.ImageID, nil
|
||||||
|
}
|
||||||
|
58
ecs/tags.go
Normal file
58
ecs/tags.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation/tags"
|
||||||
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
"github.com/docker/compose-cli/api/compose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func projectTags(project *types.Project) []tags.Tag {
|
||||||
|
return []tags.Tag{
|
||||||
|
{
|
||||||
|
Key: compose.ProjectTag,
|
||||||
|
Value: project.Name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceTags(project *types.Project, service types.ServiceConfig) []tags.Tag {
|
||||||
|
return []tags.Tag{
|
||||||
|
{
|
||||||
|
Key: compose.ProjectTag,
|
||||||
|
Value: project.Name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: compose.ServiceTag,
|
||||||
|
Value: service.Name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func networkTags(project *types.Project, net types.NetworkConfig) []tags.Tag {
|
||||||
|
return []tags.Tag{
|
||||||
|
{
|
||||||
|
Key: compose.ProjectTag,
|
||||||
|
Value: project.Name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: compose.NetworkTag,
|
||||||
|
Value: net.Name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user