2020-08-18 11:38:23 +02:00
|
|
|
|
/*
|
2020-09-22 12:13:00 +02:00
|
|
|
|
Copyright 2020 Docker Compose CLI authors
|
2020-08-18 11:38:23 +02:00
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-08-17 17:48:52 +02:00
|
|
|
|
package ecs
|
2020-04-21 11:38:52 +02:00
|
|
|
|
|
|
|
|
|
import (
|
2020-08-18 08:58:37 +02:00
|
|
|
|
"context"
|
2020-04-21 11:38:52 +02:00
|
|
|
|
"fmt"
|
2020-08-04 18:04:06 +02:00
|
|
|
|
"io/ioutil"
|
2020-05-27 16:58:58 +02:00
|
|
|
|
"regexp"
|
2020-04-27 10:04:07 +02:00
|
|
|
|
"strings"
|
|
|
|
|
|
2020-06-17 14:48:42 +02:00
|
|
|
|
ecsapi "github.com/aws/aws-sdk-go/service/ecs"
|
2020-06-03 11:11:57 +02:00
|
|
|
|
"github.com/aws/aws-sdk-go/service/elbv2"
|
2020-05-05 12:06:32 +02:00
|
|
|
|
cloudmapapi "github.com/aws/aws-sdk-go/service/servicediscovery"
|
2020-04-21 11:38:52 +02:00
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation"
|
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation/ec2"
|
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
2020-06-03 11:11:57 +02:00
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
|
2020-05-04 15:09:08 +02:00
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation/iam"
|
2020-05-05 12:06:32 +02:00
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation/logs"
|
2020-08-10 16:15:46 +02:00
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
|
2020-05-05 00:58:20 +02:00
|
|
|
|
cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
|
2021-01-08 16:05:17 +01:00
|
|
|
|
"github.com/cnabio/cnab-to-oci/remotes"
|
2020-06-17 14:48:42 +02:00
|
|
|
|
"github.com/compose-spec/compose-go/types"
|
2021-02-17 17:51:14 +01:00
|
|
|
|
"github.com/distribution/distribution/v3/reference"
|
2021-01-08 16:05:17 +01:00
|
|
|
|
cliconfig "github.com/docker/cli/cli/config"
|
2021-06-14 16:26:14 +02:00
|
|
|
|
"github.com/docker/compose-cli/api/config"
|
|
|
|
|
"github.com/docker/compose-cli/pkg/api"
|
2021-02-17 17:51:14 +01:00
|
|
|
|
"github.com/opencontainers/go-digest"
|
2021-01-08 16:05:17 +01:00
|
|
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
|
|
|
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
|
2020-05-13 10:34:23 +02:00
|
|
|
|
)
|
|
|
|
|
|
2021-06-14 16:26:14 +02:00
|
|
|
|
func (b *ecsAPIService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error {
|
|
|
|
|
return api.ErrNotImplemented
|
2021-01-31 18:42:13 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-14 16:26:14 +02:00
|
|
|
|
func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) {
|
2021-01-11 12:17:42 +01:00
|
|
|
|
err := b.resolveServiceImagesDigests(ctx, project)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
template, err := b.convert(ctx, project)
|
2020-09-24 10:28:32 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-08 16:05:17 +01:00
|
|
|
|
bytes, err := marshall(template, options.Format)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x, ok := project.Extensions[extensionCloudFormation]
|
|
|
|
|
if !ok {
|
|
|
|
|
return bytes, nil
|
|
|
|
|
}
|
|
|
|
|
if options.Format != "yaml" {
|
|
|
|
|
return nil, fmt.Errorf("format %q with overlays is not supported", options.Format)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nodes, err := yaml.Parse(string(bytes))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytes, err = yaml.Marshal(x)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
overlay, err := yaml.Parse(string(bytes))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
nodes, err = merge2.Merge(overlay, nodes, yaml.MergeOptions{
|
|
|
|
|
ListIncreaseDirection: yaml.MergeOptionsListPrepend,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s, err := nodes.String()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
bytes = []byte(s)
|
|
|
|
|
return bytes, err
|
2020-10-12 16:21:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-11 12:17:42 +01:00
|
|
|
|
func (b *ecsAPIService) resolveServiceImagesDigests(ctx context.Context, project *types.Project) error {
|
2021-03-11 11:43:40 +01:00
|
|
|
|
configFile, err := cliconfig.Load(config.Dir())
|
2021-01-11 12:17:42 +01:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resolver := remotes.CreateResolver(configFile)
|
2021-02-17 17:51:14 +01:00
|
|
|
|
return project.ResolveImages(func(named reference.Named) (digest.Digest, error) {
|
|
|
|
|
_, desc, err := resolver.Resolve(ctx, named.String())
|
|
|
|
|
return desc.Digest, err
|
|
|
|
|
})
|
2021-01-11 12:17:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*cloudformation.Template, error) {
|
|
|
|
|
err := b.checkCompatibility(project)
|
2020-09-24 16:11:08 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-16 10:48:19 +02:00
|
|
|
|
template := cloudformation.NewTemplate()
|
|
|
|
|
resources, err := b.parse(ctx, project, template)
|
2020-08-18 08:58:37 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-09-08 10:33:06 +02:00
|
|
|
|
|
2020-10-14 11:04:50 +02:00
|
|
|
|
err = b.ensureResources(&resources, project, template)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-10-12 16:21:48 +02:00
|
|
|
|
|
|
|
|
|
for name, secret := range project.Secrets {
|
|
|
|
|
err := b.createSecret(project, name, secret, template)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b.createLogGroup(project, template)
|
|
|
|
|
|
|
|
|
|
// Private DNS namespace will allow DNS name for the services to be <service>.<project>.local
|
|
|
|
|
b.createCloudMap(project, template, resources.vpc)
|
|
|
|
|
|
2020-10-09 08:18:39 +02:00
|
|
|
|
b.createNFSMountTarget(project, resources, template)
|
|
|
|
|
|
2020-10-19 09:48:23 +02:00
|
|
|
|
b.createAccessPoints(project, resources, template)
|
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
for _, service := range project.Services {
|
|
|
|
|
err := b.createService(project, service, template, resources)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-20 10:27:36 +02:00
|
|
|
|
err = b.createAutoscalingPolicy(project, resources, template, service)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-10-12 16:21:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 08:31:11 +02:00
|
|
|
|
err = b.createCapacityProvider(ctx, project, template, resources)
|
2020-09-07 11:20:41 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
return template, nil
|
2020-08-18 08:58:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
func (b *ecsAPIService) createService(project *types.Project, service types.ServiceConfig, template *cloudformation.Template, resources awsResources) error {
|
|
|
|
|
taskExecutionRole := b.createTaskExecutionRole(project, service, template)
|
2020-10-19 09:48:23 +02:00
|
|
|
|
taskRole := b.createTaskRole(project, service, template, resources)
|
2020-05-13 10:34:23 +02:00
|
|
|
|
|
2020-10-09 08:18:39 +02:00
|
|
|
|
definition, err := b.createTaskDefinition(project, service, resources)
|
2020-10-12 16:21:48 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
|
|
|
|
|
if taskRole != "" {
|
|
|
|
|
definition.TaskRoleArn = cloudformation.Ref(taskRole)
|
2020-08-04 18:04:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
taskDefinition := fmt.Sprintf("%sTaskDefinition", normalizeResourceName(service.Name))
|
|
|
|
|
template.Resources[taskDefinition] = definition
|
2020-05-04 15:09:08 +02:00
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
var healthCheck *cloudmap.Service_HealthCheckConfig
|
|
|
|
|
serviceRegistry := b.createServiceRegistry(service, template, healthCheck)
|
2020-06-11 19:22:12 +02:00
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
var (
|
|
|
|
|
dependsOn []string
|
|
|
|
|
serviceLB []ecs.Service_LoadBalancer
|
|
|
|
|
)
|
|
|
|
|
for _, port := range service.Ports {
|
|
|
|
|
for net := range service.Networks {
|
|
|
|
|
b.createIngress(service, net, port, template, resources)
|
2020-09-08 15:18:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
protocol := strings.ToUpper(port.Protocol)
|
|
|
|
|
if resources.loadBalancerType == elbv2.LoadBalancerTypeEnumApplication {
|
|
|
|
|
// we don't set Https as a certificate must be specified for HTTPS listeners
|
|
|
|
|
protocol = elbv2.ProtocolEnumHttp
|
2020-06-03 11:11:57 +02:00
|
|
|
|
}
|
2020-10-12 16:21:48 +02:00
|
|
|
|
targetGroupName := b.createTargetGroup(project, service, port, template, protocol, resources.vpc)
|
|
|
|
|
listenerName := b.createListener(service, port, template, targetGroupName, resources.loadBalancer, protocol)
|
|
|
|
|
dependsOn = append(dependsOn, listenerName)
|
|
|
|
|
serviceLB = append(serviceLB, ecs.Service_LoadBalancer{
|
|
|
|
|
ContainerName: service.Name,
|
|
|
|
|
ContainerPort: int(port.Target),
|
|
|
|
|
TargetGroupArn: cloudformation.Ref(targetGroupName),
|
|
|
|
|
})
|
|
|
|
|
}
|
2020-06-03 11:11:57 +02:00
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
desiredCount := 1
|
|
|
|
|
if service.Deploy != nil && service.Deploy.Replicas != nil {
|
|
|
|
|
desiredCount = int(*service.Deploy.Replicas)
|
|
|
|
|
}
|
2020-06-02 13:54:33 +02:00
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
for dependency := range service.DependsOn {
|
|
|
|
|
dependsOn = append(dependsOn, serviceResourceName(dependency))
|
|
|
|
|
}
|
2020-08-06 10:22:55 +02:00
|
|
|
|
|
2020-10-09 08:18:39 +02:00
|
|
|
|
for _, s := range service.Volumes {
|
|
|
|
|
dependsOn = append(dependsOn, b.mountTargets(s.Source, resources)...)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
minPercent, maxPercent, err := computeRollingUpdateLimits(service)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2020-09-07 11:20:41 +02:00
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
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
|
|
|
|
|
}
|
2020-08-11 11:19:58 +02:00
|
|
|
|
|
2020-10-12 16:21:48 +02:00
|
|
|
|
template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
|
|
|
|
|
AWSCloudFormationDependsOn: dependsOn,
|
2020-10-19 09:48:23 +02:00
|
|
|
|
Cluster: resources.cluster.ARN(),
|
2020-10-12 16:21:48 +02:00
|
|
|
|
DesiredCount: desiredCount,
|
|
|
|
|
DeploymentController: &ecs.Service_DeploymentController{
|
|
|
|
|
Type: ecsapi.DeploymentControllerTypeEcs,
|
|
|
|
|
},
|
|
|
|
|
DeploymentConfiguration: &ecs.Service_DeploymentConfiguration{
|
|
|
|
|
MaximumPercent: maxPercent,
|
|
|
|
|
MinimumHealthyPercent: minPercent,
|
|
|
|
|
},
|
|
|
|
|
LaunchType: launchType,
|
|
|
|
|
// TODO we miss support for https://github.com/aws/containers-roadmap/issues/631 to select a capacity provider
|
|
|
|
|
LoadBalancers: serviceLB,
|
|
|
|
|
NetworkConfiguration: &ecs.Service_NetworkConfiguration{
|
|
|
|
|
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
|
|
|
|
|
AssignPublicIp: assignPublicIP,
|
|
|
|
|
SecurityGroups: resources.serviceSecurityGroups(service),
|
2020-10-19 09:48:23 +02:00
|
|
|
|
Subnets: resources.subnetsIDs(),
|
2020-04-21 11:38:52 +02:00
|
|
|
|
},
|
2020-10-12 16:21:48 +02:00
|
|
|
|
},
|
|
|
|
|
PlatformVersion: platformVersion,
|
|
|
|
|
PropagateTags: ecsapi.PropagateTagsService,
|
|
|
|
|
SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
|
|
|
|
|
ServiceRegistries: []ecs.Service_ServiceRegistry{serviceRegistry},
|
|
|
|
|
Tags: serviceTags(project, service),
|
|
|
|
|
TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
|
2020-04-21 11:38:52 +02:00
|
|
|
|
}
|
2020-10-12 16:21:48 +02:00
|
|
|
|
return nil
|
2020-09-24 16:11:08 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 08:31:11 +02:00
|
|
|
|
const allProtocols = "-1"
|
|
|
|
|
|
|
|
|
|
func (b *ecsAPIService) createIngress(service types.ServiceConfig, net string, port types.ServicePortConfig, template *cloudformation.Template, resources awsResources) {
|
2020-09-24 16:11:08 +02:00
|
|
|
|
protocol := strings.ToUpper(port.Protocol)
|
|
|
|
|
if protocol == "" {
|
2020-09-29 08:31:11 +02:00
|
|
|
|
protocol = allProtocols
|
2020-09-24 16:11:08 +02:00
|
|
|
|
}
|
|
|
|
|
ingress := fmt.Sprintf("%s%dIngress", normalizeResourceName(net), port.Target)
|
|
|
|
|
template.Resources[ingress] = &ec2.SecurityGroupIngress{
|
|
|
|
|
CidrIp: "0.0.0.0/0",
|
2020-10-28 23:39:43 +01:00
|
|
|
|
Description: fmt.Sprintf("%s:%d/%s on %s network", service.Name, port.Target, port.Protocol, net),
|
2020-09-29 08:31:11 +02:00
|
|
|
|
GroupId: resources.securityGroups[net],
|
2020-09-24 16:11:08 +02:00
|
|
|
|
FromPort: int(port.Target),
|
|
|
|
|
IpProtocol: protocol,
|
|
|
|
|
ToPort: int(port.Target),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *ecsAPIService) createSecret(project *types.Project, name string, s types.SecretConfig, template *cloudformation.Template) error {
|
|
|
|
|
if s.External.External {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
sensitiveData, err := ioutil.ReadFile(s.File)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resource := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
|
|
|
|
|
template.Resources[resource] = &secretsmanager.Secret{
|
|
|
|
|
Description: fmt.Sprintf("Secret %s", s.Name),
|
|
|
|
|
SecretString: string(sensitiveData),
|
|
|
|
|
Tags: projectTags(project),
|
|
|
|
|
}
|
|
|
|
|
s.Name = cloudformation.Ref(resource)
|
|
|
|
|
project.Secrets[name] = s
|
|
|
|
|
return nil
|
2020-04-21 11:38:52 +02:00
|
|
|
|
}
|
2020-04-24 14:19:14 +02:00
|
|
|
|
|
2020-09-24 16:11:08 +02:00
|
|
|
|
func (b *ecsAPIService) createLogGroup(project *types.Project, template *cloudformation.Template) {
|
2020-08-11 15:48:12 +02:00
|
|
|
|
retention := 0
|
2020-08-18 16:56:42 +02:00
|
|
|
|
if v, ok := project.Extensions[extensionRetention]; ok {
|
2020-08-11 15:48:12 +02:00
|
|
|
|
retention = v.(int)
|
|
|
|
|
}
|
|
|
|
|
logGroup := fmt.Sprintf("/docker-compose/%s", project.Name)
|
|
|
|
|
template.Resources["LogGroup"] = &logs.LogGroup{
|
|
|
|
|
LogGroupName: logGroup,
|
|
|
|
|
RetentionInDays: retention,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) {
|
2020-08-11 11:19:58 +02:00
|
|
|
|
maxPercent := 200
|
|
|
|
|
minPercent := 100
|
|
|
|
|
if service.Deploy == nil || service.Deploy.UpdateConfig == nil {
|
|
|
|
|
return minPercent, maxPercent, nil
|
|
|
|
|
}
|
|
|
|
|
updateConfig := service.Deploy.UpdateConfig
|
2020-08-18 16:56:42 +02:00
|
|
|
|
min, okMin := updateConfig.Extensions[extensionMinPercent]
|
2020-08-11 11:19:58 +02:00
|
|
|
|
if okMin {
|
|
|
|
|
minPercent = min.(int)
|
|
|
|
|
}
|
2020-08-18 16:56:42 +02:00
|
|
|
|
max, okMax := updateConfig.Extensions[extensionMaxPercent]
|
2020-08-11 11:19:58 +02:00
|
|
|
|
if okMax {
|
|
|
|
|
maxPercent = max.(int)
|
|
|
|
|
}
|
|
|
|
|
if okMin && okMax {
|
|
|
|
|
return minPercent, maxPercent, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if updateConfig.Parallelism != nil {
|
|
|
|
|
parallelism := int(*updateConfig.Parallelism)
|
|
|
|
|
if service.Deploy.Replicas == nil {
|
|
|
|
|
return minPercent, maxPercent,
|
|
|
|
|
fmt.Errorf("rolling update configuration require deploy.replicas to be set")
|
|
|
|
|
}
|
|
|
|
|
replicas := int(*service.Deploy.Replicas)
|
|
|
|
|
if replicas < parallelism {
|
|
|
|
|
return minPercent, maxPercent,
|
|
|
|
|
fmt.Errorf("deploy.replicas (%d) must be greater than deploy.update_config.parallelism (%d)", replicas, parallelism)
|
|
|
|
|
}
|
|
|
|
|
if !okMin {
|
|
|
|
|
minPercent = (replicas - parallelism) * 100 / replicas
|
|
|
|
|
}
|
|
|
|
|
if !okMax {
|
|
|
|
|
maxPercent = (replicas + parallelism) * 100 / replicas
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return minPercent, maxPercent, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 16:11:08 +02:00
|
|
|
|
func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.ServicePortConfig,
|
|
|
|
|
template *cloudformation.Template,
|
2020-10-19 09:48:23 +02:00
|
|
|
|
targetGroupName string, loadBalancer awsResource, protocol string) string {
|
2020-06-04 16:10:34 +02:00
|
|
|
|
listenerName := fmt.Sprintf(
|
2020-06-04 16:20:21 +02:00
|
|
|
|
"%s%s%dListener",
|
2020-06-04 16:10:34 +02:00
|
|
|
|
normalizeResourceName(service.Name),
|
|
|
|
|
strings.ToUpper(port.Protocol),
|
2020-06-25 08:14:54 +02:00
|
|
|
|
port.Target,
|
2020-06-04 16:10:34 +02:00
|
|
|
|
)
|
|
|
|
|
//add listener to dependsOn
|
|
|
|
|
//https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer
|
|
|
|
|
template.Resources[listenerName] = &elasticloadbalancingv2.Listener{
|
|
|
|
|
DefaultActions: []elasticloadbalancingv2.Listener_Action{
|
|
|
|
|
{
|
|
|
|
|
ForwardConfig: &elasticloadbalancingv2.Listener_ForwardConfig{
|
|
|
|
|
TargetGroups: []elasticloadbalancingv2.Listener_TargetGroupTuple{
|
|
|
|
|
{
|
|
|
|
|
TargetGroupArn: cloudformation.Ref(targetGroupName),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Type: elbv2.ActionTypeEnumForward,
|
|
|
|
|
},
|
|
|
|
|
},
|
2020-10-19 09:48:23 +02:00
|
|
|
|
LoadBalancerArn: loadBalancer.ARN(),
|
2020-06-04 16:10:34 +02:00
|
|
|
|
Protocol: protocol,
|
2020-06-25 08:14:54 +02:00
|
|
|
|
Port: int(port.Target),
|
2020-06-04 16:10:34 +02:00
|
|
|
|
}
|
|
|
|
|
return listenerName
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 08:31:11 +02:00
|
|
|
|
func (b *ecsAPIService) createTargetGroup(project *types.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string, vpc string) string {
|
2020-06-04 16:10:34 +02:00
|
|
|
|
targetGroupName := fmt.Sprintf(
|
2020-06-04 16:20:21 +02:00
|
|
|
|
"%s%s%dTargetGroup",
|
2020-06-04 16:10:34 +02:00
|
|
|
|
normalizeResourceName(service.Name),
|
|
|
|
|
strings.ToUpper(port.Protocol),
|
2020-06-04 16:20:21 +02:00
|
|
|
|
port.Published,
|
2020-06-04 16:10:34 +02:00
|
|
|
|
)
|
|
|
|
|
template.Resources[targetGroupName] = &elasticloadbalancingv2.TargetGroup{
|
2020-10-09 08:18:39 +02:00
|
|
|
|
Port: int(port.Target),
|
|
|
|
|
Protocol: protocol,
|
|
|
|
|
Tags: projectTags(project),
|
|
|
|
|
TargetType: elbv2.TargetTypeEnumIp,
|
|
|
|
|
VpcId: vpc,
|
2020-06-04 16:10:34 +02:00
|
|
|
|
}
|
|
|
|
|
return targetGroupName
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 16:11:08 +02:00
|
|
|
|
func (b *ecsAPIService) createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry {
|
2020-06-04 16:10:34 +02:00
|
|
|
|
serviceRegistration := fmt.Sprintf("%sServiceDiscoveryEntry", normalizeResourceName(service.Name))
|
|
|
|
|
serviceRegistry := ecs.Service_ServiceRegistry{
|
|
|
|
|
RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template.Resources[serviceRegistration] = &cloudmap.Service{
|
|
|
|
|
Description: fmt.Sprintf("%q service discovery entry in Cloud Map", service.Name),
|
|
|
|
|
HealthCheckConfig: healthCheck,
|
2020-06-15 09:27:12 +02:00
|
|
|
|
HealthCheckCustomConfig: &cloudmap.Service_HealthCheckCustomConfig{
|
|
|
|
|
FailureThreshold: 1,
|
|
|
|
|
},
|
|
|
|
|
Name: service.Name,
|
|
|
|
|
NamespaceId: cloudformation.Ref("CloudMap"),
|
2020-06-04 16:10:34 +02:00
|
|
|
|
DnsConfig: &cloudmap.Service_DnsConfig{
|
|
|
|
|
DnsRecords: []cloudmap.Service_DnsRecord{
|
|
|
|
|
{
|
|
|
|
|
TTL: 60,
|
|
|
|
|
Type: cloudmapapi.RecordTypeA,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
RoutingPolicy: cloudmapapi.RoutingPolicyMultivalue,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return serviceRegistry
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 16:11:08 +02:00
|
|
|
|
func (b *ecsAPIService) createTaskExecutionRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template) string {
|
2020-06-04 16:10:34 +02:00
|
|
|
|
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
|
2020-09-24 16:11:08 +02:00
|
|
|
|
policies := b.createPolicies(project, service)
|
2020-09-08 15:18:02 +02:00
|
|
|
|
template.Resources[taskExecutionRole] = &iam.Role{
|
2020-09-07 11:20:41 +02:00
|
|
|
|
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
2020-09-08 15:18:02 +02:00
|
|
|
|
Policies: policies,
|
|
|
|
|
ManagedPolicyArns: []string{
|
|
|
|
|
ecsTaskExecutionPolicy,
|
|
|
|
|
ecrReadOnlyPolicy,
|
|
|
|
|
},
|
2020-09-30 14:35:07 +02:00
|
|
|
|
Tags: serviceTags(project, service),
|
2020-08-13 15:43:24 +02:00
|
|
|
|
}
|
2020-09-08 15:18:02 +02:00
|
|
|
|
return taskExecutionRole
|
|
|
|
|
}
|
2020-06-04 16:10:34 +02:00
|
|
|
|
|
2020-10-19 09:48:23 +02:00
|
|
|
|
func (b *ecsAPIService) createTaskRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template, resources awsResources) string {
|
2020-09-08 15:18:02 +02:00
|
|
|
|
taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
|
|
|
|
|
rolePolicies := []iam.Role_Policy{}
|
2020-08-18 16:56:42 +02:00
|
|
|
|
if roles, ok := service.Extensions[extensionRole]; ok {
|
2020-08-13 15:43:24 +02:00
|
|
|
|
rolePolicies = append(rolePolicies, iam.Role_Policy{
|
2020-11-10 15:47:16 +01:00
|
|
|
|
PolicyName: fmt.Sprintf("%sPolicy", normalizeResourceName(service.Name)),
|
2020-08-13 15:43:24 +02:00
|
|
|
|
PolicyDocument: roles,
|
|
|
|
|
})
|
|
|
|
|
}
|
2020-10-19 09:48:23 +02:00
|
|
|
|
for _, vol := range service.Volumes {
|
|
|
|
|
rolePolicies = append(rolePolicies, iam.Role_Policy{
|
2020-11-10 15:47:16 +01:00
|
|
|
|
PolicyName: fmt.Sprintf("%s%sVolumeMountPolicy", normalizeResourceName(service.Name), normalizeResourceName(vol.Source)),
|
2020-10-19 09:48:23 +02:00
|
|
|
|
PolicyDocument: volumeMountPolicyDocument(vol.Source, resources.filesystems[vol.Source].ARN()),
|
|
|
|
|
})
|
|
|
|
|
}
|
2020-09-08 15:18:02 +02:00
|
|
|
|
managedPolicies := []string{}
|
2020-08-18 16:56:42 +02:00
|
|
|
|
if v, ok := service.Extensions[extensionManagedPolicies]; ok {
|
2020-08-13 15:43:24 +02:00
|
|
|
|
for _, s := range v.([]interface{}) {
|
|
|
|
|
managedPolicies = append(managedPolicies, s.(string))
|
|
|
|
|
}
|
2020-06-04 16:10:34 +02:00
|
|
|
|
}
|
2020-09-08 15:18:02 +02:00
|
|
|
|
if len(rolePolicies) == 0 && len(managedPolicies) == 0 {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
template.Resources[taskRole] = &iam.Role{
|
2020-09-07 11:20:41 +02:00
|
|
|
|
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
2020-06-04 16:10:34 +02:00
|
|
|
|
Policies: rolePolicies,
|
2020-08-13 15:43:24 +02:00
|
|
|
|
ManagedPolicyArns: managedPolicies,
|
2020-09-30 14:35:07 +02:00
|
|
|
|
Tags: serviceTags(project, service),
|
2020-06-04 16:10:34 +02:00
|
|
|
|
}
|
2020-09-08 15:18:02 +02:00
|
|
|
|
return taskRole
|
2020-06-04 16:10:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 08:31:11 +02:00
|
|
|
|
func (b *ecsAPIService) createCloudMap(project *types.Project, template *cloudformation.Template, vpc string) {
|
2020-06-04 16:10:34 +02:00
|
|
|
|
template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
|
|
|
|
|
Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
|
|
|
|
|
Name: fmt.Sprintf("%s.local", project.Name),
|
2020-09-29 08:31:11 +02:00
|
|
|
|
Vpc: vpc,
|
2020-05-05 14:45:34 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 16:11:08 +02:00
|
|
|
|
func (b *ecsAPIService) createPolicies(project *types.Project, service types.ServiceConfig) []iam.Role_Policy {
|
2020-09-22 21:57:24 +02:00
|
|
|
|
var arns []string
|
|
|
|
|
if value, ok := service.Extensions[extensionPullCredentials]; ok {
|
|
|
|
|
arns = append(arns, value.(string))
|
|
|
|
|
}
|
|
|
|
|
for _, secret := range service.Secrets {
|
|
|
|
|
arns = append(arns, project.Secrets[secret.Source].Name)
|
2020-05-05 18:55:03 +02:00
|
|
|
|
}
|
|
|
|
|
if len(arns) > 0 {
|
2020-09-08 15:18:02 +02:00
|
|
|
|
return []iam.Role_Policy{
|
|
|
|
|
{
|
|
|
|
|
PolicyDocument: &PolicyDocument{
|
|
|
|
|
Statement: []PolicyStatement{
|
|
|
|
|
{
|
|
|
|
|
Effect: "Allow",
|
|
|
|
|
Action: []string{actionGetSecretValue, actionGetParameters, actionDecrypt},
|
|
|
|
|
Resource: arns,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
PolicyName: fmt.Sprintf("%sGrantAccessToSecrets", service.Name),
|
|
|
|
|
},
|
2020-08-18 16:56:42 +02:00
|
|
|
|
}
|
2020-05-05 18:55:03 +02:00
|
|
|
|
}
|
2020-08-18 16:56:42 +02:00
|
|
|
|
return nil
|
2020-05-05 18:55:03 +02:00
|
|
|
|
}
|
2020-06-05 10:32:30 +02:00
|
|
|
|
|
2020-09-24 16:11:08 +02:00
|
|
|
|
func networkResourceName(network string) string {
|
|
|
|
|
return fmt.Sprintf("%sNetwork", normalizeResourceName(network))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func serviceResourceName(service string) string {
|
|
|
|
|
return fmt.Sprintf("%sService", normalizeResourceName(service))
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-14 11:04:50 +02:00
|
|
|
|
func volumeResourceName(service string) string {
|
|
|
|
|
return fmt.Sprintf("%sFilesystem", normalizeResourceName(service))
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 16:11:08 +02:00
|
|
|
|
func normalizeResourceName(s string) string {
|
|
|
|
|
return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
|
2020-06-05 10:32:30 +02:00
|
|
|
|
}
|