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-15 10:38:19 +02:00
|
|
|
|
|
|
|
|
|
import (
|
2020-08-10 19:02:27 +02:00
|
|
|
|
"encoding/json"
|
2020-05-12 09:41:29 +02:00
|
|
|
|
"fmt"
|
2020-08-11 15:48:12 +02:00
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2020-07-24 10:17:48 +02:00
|
|
|
|
"sort"
|
2020-05-06 18:36:22 +02:00
|
|
|
|
"strconv"
|
2020-04-21 11:38:52 +02:00
|
|
|
|
"strings"
|
2020-04-15 10:38:19 +02:00
|
|
|
|
"time"
|
|
|
|
|
|
2020-09-28 13:36:12 +02:00
|
|
|
|
"github.com/docker/compose-cli/ecs/secrets"
|
2020-09-24 16:11:08 +02:00
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
ecsapi "github.com/aws/aws-sdk-go/service/ecs"
|
2020-05-04 15:09:08 +02:00
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation"
|
2020-04-21 11:38:52 +02:00
|
|
|
|
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
2020-04-15 10:38:19 +02:00
|
|
|
|
"github.com/compose-spec/compose-go/types"
|
|
|
|
|
"github.com/docker/cli/opts"
|
2020-08-11 15:48:12 +02:00
|
|
|
|
"github.com/joho/godotenv"
|
2020-04-15 10:38:19 +02:00
|
|
|
|
)
|
|
|
|
|
|
2020-08-04 18:04:06 +02:00
|
|
|
|
const secretsInitContainerImage = "docker/ecs-secrets-sidecar"
|
2020-09-28 13:36:12 +02:00
|
|
|
|
const searchDomainInitContainerImage = "docker/ecs-searchdomain-sidecar"
|
2020-08-04 18:04:06 +02:00
|
|
|
|
|
2020-09-28 13:36:12 +02:00
|
|
|
|
func (b *ecsAPIService) createTaskDefinition(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) {
|
2020-05-06 18:36:22 +02:00
|
|
|
|
cpu, mem, err := toLimits(service)
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-08-18 16:56:42 +02:00
|
|
|
|
_, memReservation := toContainerReservation(service)
|
2020-05-06 15:15:46 +02:00
|
|
|
|
credential := getRepoCredentials(service)
|
|
|
|
|
|
2020-08-11 15:48:12 +02:00
|
|
|
|
logConfiguration := getLogConfiguration(service, project)
|
2020-08-04 18:04:06 +02:00
|
|
|
|
|
|
|
|
|
var (
|
2020-08-18 16:25:26 +02:00
|
|
|
|
initContainers []ecs.TaskDefinition_ContainerDefinition
|
2020-08-04 18:04:06 +02:00
|
|
|
|
volumes []ecs.TaskDefinition_Volume
|
|
|
|
|
mounts []ecs.TaskDefinition_MountPoint
|
|
|
|
|
)
|
|
|
|
|
if len(service.Secrets) > 0 {
|
2020-08-18 16:25:26 +02:00
|
|
|
|
secretsVolume, secretsMount, secretsSideCar, err := createSecretsSideCar(project, service, logConfiguration)
|
2020-08-10 19:02:27 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2020-08-04 18:04:06 +02:00
|
|
|
|
}
|
2020-08-18 16:25:26 +02:00
|
|
|
|
initContainers = append(initContainers, secretsSideCar)
|
|
|
|
|
volumes = append(volumes, secretsVolume)
|
|
|
|
|
mounts = append(mounts, secretsMount)
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:36:12 +02:00
|
|
|
|
initContainers = append(initContainers, ecs.TaskDefinition_ContainerDefinition{
|
|
|
|
|
Name: fmt.Sprintf("%s_ResolvConf_InitContainer", normalizeResourceName(service.Name)),
|
|
|
|
|
Image: searchDomainInitContainerImage,
|
|
|
|
|
Essential: false,
|
|
|
|
|
Command: []string{b.Region + ".compute.internal", project.Name + ".local"},
|
|
|
|
|
LogConfiguration: logConfiguration,
|
|
|
|
|
})
|
|
|
|
|
|
2020-08-18 16:25:26 +02:00
|
|
|
|
var dependencies []ecs.TaskDefinition_ContainerDependency
|
|
|
|
|
for _, c := range initContainers {
|
|
|
|
|
dependencies = append(dependencies, ecs.TaskDefinition_ContainerDependency{
|
|
|
|
|
Condition: ecsapi.ContainerConditionSuccess,
|
|
|
|
|
ContainerName: c.Name,
|
2020-08-04 18:04:06 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-08 10:33:06 +02:00
|
|
|
|
for _, v := range service.Volumes {
|
|
|
|
|
source := project.Volumes[v.Source]
|
|
|
|
|
volumes = append(volumes, ecs.TaskDefinition_Volume{
|
|
|
|
|
EFSVolumeConfiguration: &ecs.TaskDefinition_EFSVolumeConfiguration{
|
|
|
|
|
FilesystemId: source.Name,
|
|
|
|
|
RootDirectory: source.DriverOpts["root_directory"],
|
|
|
|
|
},
|
|
|
|
|
Name: v.Source,
|
|
|
|
|
})
|
|
|
|
|
mounts = append(mounts, ecs.TaskDefinition_MountPoint{
|
|
|
|
|
ContainerPath: v.Target,
|
|
|
|
|
ReadOnly: v.ReadOnly,
|
|
|
|
|
SourceVolume: v.Source,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-11 15:48:12 +02:00
|
|
|
|
pairs, err := createEnvironment(project, service)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-07 11:20:41 +02:00
|
|
|
|
var reservations *types.Resource
|
|
|
|
|
if service.Deploy != nil && service.Deploy.Resources.Reservations != nil {
|
|
|
|
|
reservations = service.Deploy.Resources.Reservations
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 16:25:26 +02:00
|
|
|
|
containers := append(initContainers, ecs.TaskDefinition_ContainerDefinition{
|
2020-08-04 18:04:06 +02:00
|
|
|
|
Command: service.Command,
|
|
|
|
|
DisableNetworking: service.NetworkMode == "none",
|
2020-08-18 16:25:26 +02:00
|
|
|
|
DependsOnProp: dependencies,
|
2020-08-04 18:04:06 +02:00
|
|
|
|
DnsSearchDomains: service.DNSSearch,
|
|
|
|
|
DnsServers: service.DNS,
|
|
|
|
|
DockerSecurityOptions: service.SecurityOpt,
|
|
|
|
|
EntryPoint: service.Entrypoint,
|
2020-08-11 15:48:12 +02:00
|
|
|
|
Environment: pairs,
|
2020-08-04 18:04:06 +02:00
|
|
|
|
Essential: true,
|
|
|
|
|
ExtraHosts: toHostEntryPtr(service.ExtraHosts),
|
|
|
|
|
FirelensConfiguration: nil,
|
|
|
|
|
HealthCheck: toHealthCheck(service.HealthCheck),
|
|
|
|
|
Hostname: service.Hostname,
|
|
|
|
|
Image: service.Image,
|
|
|
|
|
Interactive: false,
|
|
|
|
|
Links: nil,
|
|
|
|
|
LinuxParameters: toLinuxParameters(service),
|
|
|
|
|
LogConfiguration: logConfiguration,
|
|
|
|
|
MemoryReservation: memReservation,
|
|
|
|
|
MountPoints: mounts,
|
|
|
|
|
Name: service.Name,
|
|
|
|
|
PortMappings: toPortMappings(service.Ports),
|
|
|
|
|
Privileged: service.Privileged,
|
|
|
|
|
PseudoTerminal: service.Tty,
|
|
|
|
|
ReadonlyRootFilesystem: service.ReadOnly,
|
|
|
|
|
RepositoryCredentials: credential,
|
2020-09-07 11:20:41 +02:00
|
|
|
|
ResourceRequirements: toTaskResourceRequirements(reservations),
|
2020-08-04 18:04:06 +02:00
|
|
|
|
StartTimeout: 0,
|
|
|
|
|
StopTimeout: durationToInt(service.StopGracePeriod),
|
|
|
|
|
SystemControls: toSystemControls(service.Sysctls),
|
|
|
|
|
Ulimits: toUlimits(service.Ulimits),
|
|
|
|
|
User: service.User,
|
|
|
|
|
VolumesFrom: nil,
|
|
|
|
|
WorkingDirectory: service.WorkingDir,
|
|
|
|
|
})
|
|
|
|
|
|
2020-09-07 11:20:41 +02:00
|
|
|
|
launchType := ecsapi.LaunchTypeFargate
|
|
|
|
|
if requireEC2(service) {
|
|
|
|
|
launchType = ecsapi.LaunchTypeEc2
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-04 18:04:06 +02:00
|
|
|
|
return &ecs.TaskDefinition{
|
2020-09-07 11:20:41 +02:00
|
|
|
|
ContainerDefinitions: containers,
|
|
|
|
|
Cpu: cpu,
|
|
|
|
|
Family: fmt.Sprintf("%s-%s", project.Name, service.Name),
|
|
|
|
|
IpcMode: service.Ipc,
|
|
|
|
|
Memory: mem,
|
|
|
|
|
NetworkMode: ecsapi.NetworkModeAwsvpc, // FIXME could be set by service.NetworkMode, Fargate only supports network mode ‘awsvpc’.
|
|
|
|
|
PidMode: service.Pid,
|
|
|
|
|
PlacementConstraints: toPlacementConstraints(service.Deploy),
|
|
|
|
|
ProxyConfiguration: nil,
|
|
|
|
|
RequiresCompatibilities: []string{
|
|
|
|
|
launchType,
|
|
|
|
|
},
|
|
|
|
|
Volumes: volumes,
|
2020-04-15 10:38:19 +02:00
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-07 11:20:41 +02:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 16:56:42 +02:00
|
|
|
|
func createSecretsSideCar(project *types.Project, service types.ServiceConfig, logConfiguration *ecs.TaskDefinition_LogConfiguration) (
|
|
|
|
|
ecs.TaskDefinition_Volume,
|
|
|
|
|
ecs.TaskDefinition_MountPoint,
|
|
|
|
|
ecs.TaskDefinition_ContainerDefinition,
|
|
|
|
|
error) {
|
2020-08-18 16:25:26 +02:00
|
|
|
|
initContainerName := fmt.Sprintf("%s_Secrets_InitContainer", normalizeResourceName(service.Name))
|
|
|
|
|
secretsVolume := ecs.TaskDefinition_Volume{
|
|
|
|
|
Name: "secrets",
|
|
|
|
|
}
|
|
|
|
|
secretsMount := ecs.TaskDefinition_MountPoint{
|
|
|
|
|
ContainerPath: "/run/secrets/",
|
|
|
|
|
ReadOnly: true,
|
|
|
|
|
SourceVolume: "secrets",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
args []secrets.Secret
|
|
|
|
|
taskSecrets []ecs.TaskDefinition_Secret
|
|
|
|
|
)
|
|
|
|
|
for _, s := range service.Secrets {
|
|
|
|
|
secretConfig := project.Secrets[s.Source]
|
|
|
|
|
if s.Target == "" {
|
|
|
|
|
s.Target = s.Source
|
|
|
|
|
}
|
|
|
|
|
taskSecrets = append(taskSecrets, ecs.TaskDefinition_Secret{
|
|
|
|
|
Name: s.Target,
|
|
|
|
|
ValueFrom: secretConfig.Name,
|
|
|
|
|
})
|
|
|
|
|
var keys []string
|
2020-08-18 16:56:42 +02:00
|
|
|
|
if ext, ok := secretConfig.Extensions[extensionKeys]; ok {
|
2020-08-18 16:25:26 +02:00
|
|
|
|
if key, ok := ext.(string); ok {
|
|
|
|
|
keys = append(keys, key)
|
|
|
|
|
} else {
|
|
|
|
|
for _, k := range ext.([]interface{}) {
|
|
|
|
|
keys = append(keys, k.(string))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
args = append(args, secrets.Secret{
|
|
|
|
|
Name: s.Target,
|
|
|
|
|
Keys: keys,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
command, err := json.Marshal(args)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return ecs.TaskDefinition_Volume{}, ecs.TaskDefinition_MountPoint{}, ecs.TaskDefinition_ContainerDefinition{}, err
|
|
|
|
|
}
|
|
|
|
|
secretsSideCar := ecs.TaskDefinition_ContainerDefinition{
|
|
|
|
|
Name: initContainerName,
|
|
|
|
|
Image: secretsInitContainerImage,
|
|
|
|
|
Command: []string{string(command)},
|
|
|
|
|
Essential: false, // FIXME this will be ignored, see https://github.com/awslabs/goformation/issues/61#issuecomment-625139607
|
|
|
|
|
LogConfiguration: logConfiguration,
|
|
|
|
|
MountPoints: []ecs.TaskDefinition_MountPoint{
|
|
|
|
|
{
|
|
|
|
|
ContainerPath: "/run/secrets/",
|
|
|
|
|
ReadOnly: false,
|
|
|
|
|
SourceVolume: "secrets",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Secrets: taskSecrets,
|
|
|
|
|
}
|
|
|
|
|
return secretsVolume, secretsMount, secretsSideCar, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-11 15:48:12 +02:00
|
|
|
|
func createEnvironment(project *types.Project, service types.ServiceConfig) ([]ecs.TaskDefinition_KeyValuePair, error) {
|
|
|
|
|
environment := map[string]*string{}
|
|
|
|
|
for _, f := range service.EnvFile {
|
|
|
|
|
if !filepath.IsAbs(f) {
|
|
|
|
|
f = filepath.Join(project.WorkingDir, f)
|
|
|
|
|
}
|
|
|
|
|
if _, err := os.Stat(f); os.IsNotExist(err) {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
file, err := os.Open(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-08-18 11:38:23 +02:00
|
|
|
|
defer file.Close() // nolint:errcheck
|
2020-08-11 15:48:12 +02:00
|
|
|
|
|
|
|
|
|
env, err := godotenv.Parse(file)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
for k, v := range env {
|
|
|
|
|
environment[k] = &v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for k, v := range service.Environment {
|
|
|
|
|
environment[k] = v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pairs []ecs.TaskDefinition_KeyValuePair
|
|
|
|
|
for k, v := range environment {
|
|
|
|
|
name := k
|
|
|
|
|
var value string
|
|
|
|
|
if v != nil {
|
|
|
|
|
value = *v
|
|
|
|
|
}
|
|
|
|
|
pairs = append(pairs, ecs.TaskDefinition_KeyValuePair{
|
|
|
|
|
Name: name,
|
|
|
|
|
Value: value,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return pairs, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-11 15:48:12 +02:00
|
|
|
|
func getLogConfiguration(service types.ServiceConfig, project *types.Project) *ecs.TaskDefinition_LogConfiguration {
|
|
|
|
|
options := map[string]string{
|
|
|
|
|
"awslogs-region": cloudformation.Ref("AWS::Region"),
|
|
|
|
|
"awslogs-group": cloudformation.Ref("LogGroup"),
|
|
|
|
|
"awslogs-stream-prefix": project.Name,
|
|
|
|
|
}
|
|
|
|
|
if service.Logging != nil {
|
|
|
|
|
for k, v := range service.Logging.Options {
|
|
|
|
|
if strings.HasPrefix(k, "awslogs-") {
|
|
|
|
|
options[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
logConfiguration := &ecs.TaskDefinition_LogConfiguration{
|
|
|
|
|
LogDriver: ecsapi.LogDriverAwslogs,
|
|
|
|
|
Options: options,
|
|
|
|
|
}
|
|
|
|
|
return logConfiguration
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 15:27:11 +02:00
|
|
|
|
func toSystemControls(sysctls types.Mapping) []ecs.TaskDefinition_SystemControl {
|
|
|
|
|
sys := []ecs.TaskDefinition_SystemControl{}
|
|
|
|
|
for k, v := range sysctls {
|
|
|
|
|
sys = append(sys, ecs.TaskDefinition_SystemControl{
|
|
|
|
|
Namespace: k,
|
|
|
|
|
Value: v,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return sys
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 16:56:42 +02:00
|
|
|
|
const miB = 1024 * 1024
|
2020-07-23 14:37:05 +02:00
|
|
|
|
|
2020-05-06 18:36:22 +02:00
|
|
|
|
func toLimits(service types.ServiceConfig) (string, string, error) {
|
2020-09-22 12:15:55 +02:00
|
|
|
|
mem, cpu, err := getConfiguredLimits(service)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", "", err
|
|
|
|
|
}
|
|
|
|
|
if requireEC2(service) {
|
|
|
|
|
// just return configured limits expressed in Mb and CPU units
|
2020-09-23 10:04:19 +02:00
|
|
|
|
var cpuLimit, memLimit string
|
|
|
|
|
if cpu > 0 {
|
|
|
|
|
cpuLimit = fmt.Sprint(cpu)
|
|
|
|
|
}
|
|
|
|
|
if mem > 0 {
|
|
|
|
|
memLimit = fmt.Sprint(mem / miB)
|
|
|
|
|
}
|
|
|
|
|
return cpuLimit, memLimit, nil
|
2020-09-22 12:15:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All possible cpu/mem values for Fargate
|
|
|
|
|
fargateCPUToMem := map[int64][]types.UnitBytes{
|
2020-05-06 18:36:22 +02:00
|
|
|
|
256: {512, 1024, 2048},
|
|
|
|
|
512: {1024, 2048, 3072, 4096},
|
|
|
|
|
1024: {2048, 3072, 4096, 5120, 6144, 7168, 8192},
|
|
|
|
|
2048: {4096, 5120, 6144, 7168, 8192, 9216, 10240, 11264, 12288, 13312, 14336, 15360, 16384},
|
|
|
|
|
4096: {8192, 9216, 10240, 11264, 12288, 13312, 14336, 15360, 16384, 17408, 18432, 19456, 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672, 29696, 30720},
|
|
|
|
|
}
|
|
|
|
|
cpuLimit := "256"
|
|
|
|
|
memLimit := "512"
|
2020-09-22 12:15:55 +02:00
|
|
|
|
if mem == 0 && cpu == 0 {
|
2020-05-06 18:36:22 +02:00
|
|
|
|
return cpuLimit, memLimit, nil
|
2020-04-15 10:38:19 +02:00
|
|
|
|
}
|
2020-05-06 18:36:22 +02:00
|
|
|
|
|
2020-07-24 10:17:48 +02:00
|
|
|
|
var cpus []int64
|
2020-09-22 12:15:55 +02:00
|
|
|
|
for k := range fargateCPUToMem {
|
2020-07-24 10:17:48 +02:00
|
|
|
|
cpus = append(cpus, k)
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(cpus, func(i, j int) bool { return cpus[i] < cpus[j] })
|
|
|
|
|
|
2020-09-22 12:15:55 +02:00
|
|
|
|
for _, fargateCPU := range cpus {
|
|
|
|
|
options := fargateCPUToMem[fargateCPU]
|
|
|
|
|
if cpu <= fargateCPU {
|
|
|
|
|
for _, m := range options {
|
|
|
|
|
if mem <= m*miB {
|
|
|
|
|
cpuLimit = strconv.FormatInt(fargateCPU, 10)
|
2020-05-06 18:36:22 +02:00
|
|
|
|
memLimit = strconv.FormatInt(int64(m), 10)
|
|
|
|
|
return cpuLimit, memLimit, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-15 10:38:19 +02:00
|
|
|
|
}
|
2020-07-24 10:17:48 +02:00
|
|
|
|
return "", "", fmt.Errorf("the resources requested are not supported by ECS/Fargate")
|
2020-04-15 10:38:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-22 12:15:55 +02:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 16:56:42 +02:00
|
|
|
|
func toContainerReservation(service types.ServiceConfig) (string, int) {
|
2020-07-23 14:37:05 +02:00
|
|
|
|
cpuReservation := ".0"
|
|
|
|
|
memReservation := 0
|
|
|
|
|
|
|
|
|
|
if service.Deploy == nil {
|
2020-08-18 16:56:42 +02:00
|
|
|
|
return cpuReservation, memReservation
|
2020-07-23 14:37:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reservations := service.Deploy.Resources.Reservations
|
|
|
|
|
if reservations == nil {
|
2020-08-18 16:56:42 +02:00
|
|
|
|
return cpuReservation, memReservation
|
2020-07-23 14:37:05 +02:00
|
|
|
|
}
|
2020-08-18 16:56:42 +02:00
|
|
|
|
return reservations.NanoCPUs, int(reservations.MemoryBytes / miB)
|
2020-07-23 14:37:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func toPlacementConstraints(deploy *types.DeployConfig) []ecs.TaskDefinition_TaskDefinitionPlacementConstraint {
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if deploy == nil || deploy.Placement.Constraints == nil || len(deploy.Placement.Constraints) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-04-21 11:38:52 +02:00
|
|
|
|
pl := []ecs.TaskDefinition_TaskDefinitionPlacementConstraint{}
|
2020-04-15 10:38:19 +02:00
|
|
|
|
for _, c := range deploy.Placement.Constraints {
|
2020-04-21 11:38:52 +02:00
|
|
|
|
pl = append(pl, ecs.TaskDefinition_TaskDefinitionPlacementConstraint{
|
|
|
|
|
Expression: c,
|
|
|
|
|
Type: "",
|
2020-04-15 10:38:19 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return pl
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func toPortMappings(ports []types.ServicePortConfig) []ecs.TaskDefinition_PortMapping {
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if len(ports) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-04-21 11:38:52 +02:00
|
|
|
|
m := []ecs.TaskDefinition_PortMapping{}
|
2020-04-15 10:38:19 +02:00
|
|
|
|
for _, p := range ports {
|
2020-04-21 11:38:52 +02:00
|
|
|
|
m = append(m, ecs.TaskDefinition_PortMapping{
|
|
|
|
|
ContainerPort: int(p.Target),
|
|
|
|
|
HostPort: int(p.Published),
|
|
|
|
|
Protocol: p.Protocol,
|
2020-04-15 10:38:19 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return m
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func toUlimits(ulimits map[string]*types.UlimitsConfig) []ecs.TaskDefinition_Ulimit {
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if len(ulimits) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-04-21 11:38:52 +02:00
|
|
|
|
u := []ecs.TaskDefinition_Ulimit{}
|
2020-04-15 10:38:19 +02:00
|
|
|
|
for k, v := range ulimits {
|
2020-04-21 11:38:52 +02:00
|
|
|
|
u = append(u, ecs.TaskDefinition_Ulimit{
|
|
|
|
|
Name: k,
|
|
|
|
|
SoftLimit: v.Soft,
|
|
|
|
|
HardLimit: v.Hard,
|
2020-04-15 10:38:19 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return u
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func toLinuxParameters(service types.ServiceConfig) *ecs.TaskDefinition_LinuxParameters {
|
|
|
|
|
return &ecs.TaskDefinition_LinuxParameters{
|
2020-04-15 10:38:19 +02:00
|
|
|
|
Capabilities: toKernelCapabilities(service.CapAdd, service.CapDrop),
|
|
|
|
|
Devices: nil,
|
2020-04-21 11:38:52 +02:00
|
|
|
|
InitProcessEnabled: service.Init != nil && *service.Init,
|
|
|
|
|
MaxSwap: 0,
|
2020-04-15 10:38:19 +02:00
|
|
|
|
// FIXME SharedMemorySize: service.ShmSize,
|
2020-04-21 11:38:52 +02:00
|
|
|
|
Swappiness: 0,
|
2020-04-15 10:38:19 +02:00
|
|
|
|
Tmpfs: toTmpfs(service.Tmpfs),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func toTmpfs(tmpfs types.StringList) []ecs.TaskDefinition_Tmpfs {
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if tmpfs == nil || len(tmpfs) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-04-21 11:38:52 +02:00
|
|
|
|
o := []ecs.TaskDefinition_Tmpfs{}
|
|
|
|
|
for _, path := range tmpfs {
|
|
|
|
|
o = append(o, ecs.TaskDefinition_Tmpfs{
|
|
|
|
|
ContainerPath: path,
|
2020-05-12 09:41:29 +02:00
|
|
|
|
Size: 100, // size is required on ECS, unlimited by the compose spec
|
2020-04-15 10:38:19 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return o
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func toKernelCapabilities(add []string, drop []string) *ecs.TaskDefinition_KernelCapabilities {
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if len(add) == 0 && len(drop) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-04-21 11:38:52 +02:00
|
|
|
|
return &ecs.TaskDefinition_KernelCapabilities{
|
|
|
|
|
Add: add,
|
|
|
|
|
Drop: drop,
|
2020-04-15 10:38:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func toHealthCheck(check *types.HealthCheckConfig) *ecs.TaskDefinition_HealthCheck {
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if check == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-04-21 11:38:52 +02:00
|
|
|
|
retries := 0
|
|
|
|
|
if check.Retries != nil {
|
|
|
|
|
retries = int(*check.Retries)
|
|
|
|
|
}
|
|
|
|
|
return &ecs.TaskDefinition_HealthCheck{
|
|
|
|
|
Command: check.Test,
|
|
|
|
|
Interval: durationToInt(check.Interval),
|
|
|
|
|
Retries: retries,
|
|
|
|
|
StartPeriod: durationToInt(check.StartPeriod),
|
|
|
|
|
Timeout: durationToInt(check.Timeout),
|
2020-04-15 10:38:19 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func durationToInt(interval *types.Duration) int {
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if interval == nil {
|
2020-04-21 11:38:52 +02:00
|
|
|
|
return 0
|
2020-04-15 10:38:19 +02:00
|
|
|
|
}
|
2020-04-21 11:38:52 +02:00
|
|
|
|
v := int(time.Duration(*interval).Seconds())
|
|
|
|
|
return v
|
2020-04-15 10:38:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 11:38:52 +02:00
|
|
|
|
func toHostEntryPtr(hosts types.HostsList) []ecs.TaskDefinition_HostEntry {
|
2020-04-15 10:38:19 +02:00
|
|
|
|
if hosts == nil || len(hosts) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-04-21 11:38:52 +02:00
|
|
|
|
e := []ecs.TaskDefinition_HostEntry{}
|
2020-04-15 10:38:19 +02:00
|
|
|
|
for _, h := range hosts {
|
2020-04-21 11:38:52 +02:00
|
|
|
|
parts := strings.SplitN(h, ":", 2) // FIXME this should be handled by compose-go
|
|
|
|
|
e = append(e, ecs.TaskDefinition_HostEntry{
|
|
|
|
|
Hostname: parts[0],
|
|
|
|
|
IpAddress: parts[1],
|
2020-04-15 10:38:19 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-06 15:15:46 +02:00
|
|
|
|
func getRepoCredentials(service types.ServiceConfig) *ecs.TaskDefinition_RepositoryCredentials {
|
2020-09-22 21:57:24 +02:00
|
|
|
|
if value, ok := service.Extensions[extensionPullCredentials]; ok {
|
|
|
|
|
return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)}
|
2020-04-30 17:31:25 +02:00
|
|
|
|
}
|
2020-05-06 15:15:46 +02:00
|
|
|
|
return nil
|
2020-04-30 17:31:25 +02:00
|
|
|
|
}
|
2020-09-07 11:20:41 +02:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|