mirror of https://github.com/docker/compose.git
Merge pull request #638 from docker/clean
This commit is contained in:
commit
3685cbbf8d
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
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"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/elbv2"
|
||||
"github.com/awslabs/goformation/v4/cloudformation/ec2"
|
||||
"github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
|
||||
|
||||
"github.com/awslabs/goformation/v4/cloudformation"
|
||||
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// awsResources hold the AWS component being used or created to support services definition
|
||||
type awsResources struct {
|
||||
vpc string
|
||||
subnets []string
|
||||
cluster string
|
||||
loadBalancer string
|
||||
loadBalancerType string
|
||||
securityGroups map[string]string
|
||||
}
|
||||
|
||||
func (r *awsResources) serviceSecurityGroups(service types.ServiceConfig) []string {
|
||||
var groups []string
|
||||
for net := range service.Networks {
|
||||
groups = append(groups, r.securityGroups[net])
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func (r *awsResources) allSecurityGroups() []string {
|
||||
var securityGroups []string
|
||||
for _, r := range r.securityGroups {
|
||||
securityGroups = append(securityGroups, r)
|
||||
}
|
||||
return securityGroups
|
||||
}
|
||||
|
||||
// parse look into compose project for configured resource to use, and check they are valid
|
||||
func (b *ecsAPIService) parse(ctx context.Context, project *types.Project) (awsResources, error) {
|
||||
r := awsResources{}
|
||||
var err error
|
||||
r.cluster, err = b.parseClusterExtension(ctx, project)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r.vpc, r.subnets, err = b.parseVPCExtension(ctx, project)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r.loadBalancer, r.loadBalancerType, err = b.parseLoadBalancerExtension(ctx, project)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r.securityGroups, err = b.parseSecurityGroupExtension(ctx, project)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) parseClusterExtension(ctx context.Context, project *types.Project) (string, error) {
|
||||
if x, ok := project.Extensions[extensionCluster]; ok {
|
||||
cluster := x.(string)
|
||||
ok, err := b.SDK.ClusterExists(ctx, cluster)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !ok {
|
||||
return "", fmt.Errorf("cluster does not exist: %s", cluster)
|
||||
}
|
||||
return cluster, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) parseVPCExtension(ctx context.Context, project *types.Project) (string, []string, error) {
|
||||
var vpc string
|
||||
if x, ok := project.Extensions[extensionVPC]; ok {
|
||||
vpc = x.(string)
|
||||
err := b.SDK.CheckVPC(ctx, vpc)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
defaultVPC, err := b.SDK.GetDefaultVPC(ctx)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
vpc = defaultVPC
|
||||
}
|
||||
|
||||
subNets, err := b.SDK.GetSubNets(ctx, vpc)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if len(subNets) < 2 {
|
||||
return "", nil, fmt.Errorf("VPC %s should have at least 2 associated subnets in different availability zones", vpc)
|
||||
}
|
||||
return vpc, subNets, nil
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) parseLoadBalancerExtension(ctx context.Context, project *types.Project) (string, string, error) {
|
||||
if x, ok := project.Extensions[extensionLoadBalancer]; ok {
|
||||
loadBalancer := x.(string)
|
||||
loadBalancerType, err := b.SDK.LoadBalancerType(ctx, loadBalancer)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
required := getRequiredLoadBalancerType(project)
|
||||
if loadBalancerType != required {
|
||||
return "", "", fmt.Errorf("load balancer %s is of type %s, project require a %s", loadBalancer, loadBalancerType, required)
|
||||
}
|
||||
|
||||
return loadBalancer, loadBalancerType, nil
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) parseSecurityGroupExtension(ctx context.Context, project *types.Project) (map[string]string, error) {
|
||||
securityGroups := make(map[string]string, len(project.Networks))
|
||||
for name, net := range project.Networks {
|
||||
var sg string
|
||||
if net.External.External {
|
||||
sg = net.Name
|
||||
}
|
||||
if x, ok := net.Extensions[extensionSecurityGroup]; ok {
|
||||
logrus.Warn("to use an existing security-group, use `network.external` and `network.name` in your compose file")
|
||||
logrus.Debugf("Security Group for network %q set by user to %q", net.Name, x)
|
||||
sg = x.(string)
|
||||
}
|
||||
exists, err := b.SDK.SecurityGroupExists(ctx, sg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("security group %s doesn't exist", sg)
|
||||
}
|
||||
securityGroups[name] = sg
|
||||
}
|
||||
return securityGroups, nil
|
||||
}
|
||||
|
||||
// ensureResources create required resources in template if not yet defined
|
||||
func (b *ecsAPIService) ensureResources(resources *awsResources, project *types.Project, template *cloudformation.Template) {
|
||||
b.ensureCluster(resources, project, template)
|
||||
b.ensureNetworks(resources, project, template)
|
||||
b.ensureLoadBalancer(resources, project, template)
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) ensureCluster(r *awsResources, project *types.Project, template *cloudformation.Template) {
|
||||
if r.cluster != "" {
|
||||
return
|
||||
}
|
||||
template.Resources["Cluster"] = &ecs.Cluster{
|
||||
ClusterName: project.Name,
|
||||
Tags: projectTags(project),
|
||||
}
|
||||
r.cluster = cloudformation.Ref("Cluster")
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) ensureNetworks(r *awsResources, project *types.Project, template *cloudformation.Template) {
|
||||
if r.securityGroups == nil {
|
||||
r.securityGroups = make(map[string]string, len(project.Networks))
|
||||
}
|
||||
for name, net := range project.Networks {
|
||||
securityGroup := networkResourceName(name)
|
||||
template.Resources[securityGroup] = &ec2.SecurityGroup{
|
||||
GroupDescription: fmt.Sprintf("%s Security Group for %s network", project.Name, name),
|
||||
GroupName: securityGroup,
|
||||
VpcId: r.vpc,
|
||||
Tags: networkTags(project, net),
|
||||
}
|
||||
|
||||
ingress := securityGroup + "Ingress"
|
||||
template.Resources[ingress] = &ec2.SecurityGroupIngress{
|
||||
Description: fmt.Sprintf("Allow communication within network %s", name),
|
||||
IpProtocol: allProtocols,
|
||||
GroupId: cloudformation.Ref(securityGroup),
|
||||
SourceSecurityGroupId: cloudformation.Ref(securityGroup),
|
||||
}
|
||||
|
||||
r.securityGroups[name] = cloudformation.Ref(securityGroup)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) ensureLoadBalancer(r *awsResources, project *types.Project, template *cloudformation.Template) {
|
||||
if r.loadBalancer != "" {
|
||||
return
|
||||
}
|
||||
if allServices(project.Services, func(it types.ServiceConfig) bool {
|
||||
return len(it.Ports) == 0
|
||||
}) {
|
||||
logrus.Debug("Application does not expose any public port, so no need for a LoadBalancer")
|
||||
return
|
||||
}
|
||||
|
||||
balancerType := getRequiredLoadBalancerType(project)
|
||||
template.Resources["LoadBalancer"] = &elasticloadbalancingv2.LoadBalancer{
|
||||
Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing,
|
||||
SecurityGroups: r.getLoadBalancerSecurityGroups(project),
|
||||
Subnets: r.subnets,
|
||||
Tags: projectTags(project),
|
||||
Type: balancerType,
|
||||
}
|
||||
r.loadBalancer = cloudformation.Ref("LoadBalancer")
|
||||
r.loadBalancerType = balancerType
|
||||
}
|
||||
|
||||
func (r *awsResources) getLoadBalancerSecurityGroups(project *types.Project) []string {
|
||||
securityGroups := []string{}
|
||||
for name, network := range project.Networks {
|
||||
if !network.Internal {
|
||||
securityGroups = append(securityGroups, r.securityGroups[name])
|
||||
}
|
||||
}
|
||||
return securityGroups
|
||||
}
|
||||
|
||||
func getRequiredLoadBalancerType(project *types.Project) string {
|
||||
loadBalancerType := elbv2.LoadBalancerTypeEnumNetwork
|
||||
if allServices(project.Services, func(it types.ServiceConfig) bool {
|
||||
return allPorts(it.Ports, portIsHTTP)
|
||||
}) {
|
||||
loadBalancerType = elbv2.LoadBalancerTypeEnumApplication
|
||||
}
|
||||
return loadBalancerType
|
||||
}
|
||||
|
||||
func portIsHTTP(it types.ServicePortConfig) bool {
|
||||
if v, ok := it.Extensions[extensionProtocol]; ok {
|
||||
protocol := v.(string)
|
||||
return protocol == "http" || protocol == "https"
|
||||
}
|
||||
return it.Target == 80 || it.Target == 443
|
||||
}
|
||||
|
||||
// predicate[types.ServiceConfig]
|
||||
type servicePredicate func(it types.ServiceConfig) bool
|
||||
|
||||
// all[types.ServiceConfig]
|
||||
func allServices(services types.Services, p servicePredicate) bool {
|
||||
for _, s := range services {
|
||||
if !p(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// predicate[types.ServicePortConfig]
|
||||
type portPredicate func(it types.ServicePortConfig) bool
|
||||
|
||||
// all[types.ServicePortConfig]
|
||||
func allPorts(ports []types.ServicePortConfig, p portPredicate) bool {
|
||||
for _, s := range ports {
|
||||
if !p(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -73,10 +73,11 @@ func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
sdk := newSDK(sess)
|
||||
return &ecsAPIService{
|
||||
ctx: ecsCtx,
|
||||
Region: ecsCtx.Region,
|
||||
SDK: newSDK(sess),
|
||||
SDK: sdk,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -34,22 +34,21 @@ import (
|
|||
"github.com/awslabs/goformation/v4/cloudformation/logs"
|
||||
"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
|
||||
cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
|
||||
"github.com/compose-spec/compose-go/compatibility"
|
||||
"github.com/compose-spec/compose-go/errdefs"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
parameterClusterName = "ParameterClusterName"
|
||||
parameterVPCId = "ParameterVPCId"
|
||||
parameterSubnet1Id = "ParameterSubnet1Id"
|
||||
parameterSubnet2Id = "ParameterSubnet2Id"
|
||||
parameterLoadBalancerARN = "ParameterLoadBalancerARN"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
|
||||
template, networks, err := b.convert(project)
|
||||
err := b.checkCompatibility(project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources, err := b.parse(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template, err := b.convert(project, resources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -58,43 +57,14 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
|
|||
// as "source security group" use an arbitrary network attached to service(s) who mounts target volume
|
||||
for n, vol := range project.Volumes {
|
||||
err := b.SDK.WithVolumeSecurityGroups(ctx, vol.Name, func(securityGroups []string) error {
|
||||
target := securityGroups[0]
|
||||
for _, s := range project.Services {
|
||||
for _, v := range s.Volumes {
|
||||
if v.Source != n {
|
||||
continue
|
||||
}
|
||||
var source string
|
||||
for net := range s.Networks {
|
||||
network := project.Networks[net]
|
||||
if ext, ok := network.Extensions[extensionSecurityGroup]; ok {
|
||||
source = ext.(string)
|
||||
} else {
|
||||
source = networkResourceName(project, net)
|
||||
}
|
||||
break
|
||||
}
|
||||
name := fmt.Sprintf("%sNFSMount%s", s.Name, n)
|
||||
template.Resources[name] = &ec2.SecurityGroupIngress{
|
||||
Description: fmt.Sprintf("Allow NFS mount for %s on %s", s.Name, n),
|
||||
GroupId: target,
|
||||
SourceSecurityGroupId: cloudformation.Ref(source),
|
||||
IpProtocol: "tcp",
|
||||
FromPort: 2049,
|
||||
ToPort: 2049,
|
||||
}
|
||||
service := template.Resources[serviceResourceName(s.Name)].(*ecs.Service)
|
||||
service.AWSCloudFormationDependsOn = append(service.AWSCloudFormationDependsOn, name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return b.createNFSmountIngress(securityGroups, project, n, template)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = b.createCapacityProvider(ctx, project, networks, template)
|
||||
err = b.createCapacityProvider(ctx, project, template, resources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -103,103 +73,31 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
|
|||
}
|
||||
|
||||
// Convert a compose project into a CloudFormation template
|
||||
func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, map[string]string, error) { //nolint:gocyclo
|
||||
var checker compatibility.Checker = &fargateCompatibilityChecker{
|
||||
compatibility.AllowList{
|
||||
Supported: compatibleComposeAttributes,
|
||||
},
|
||||
}
|
||||
compatibility.Check(project, checker)
|
||||
for _, err := range checker.Errors() {
|
||||
if errdefs.IsIncompatibleError(err) {
|
||||
logrus.Error(err.Error())
|
||||
} else {
|
||||
logrus.Warn(err.Error())
|
||||
}
|
||||
}
|
||||
if !compatibility.IsCompatible(checker) {
|
||||
return nil, nil, fmt.Errorf("compose file is incompatible with Amazon ECS")
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) convert(project *types.Project, resources awsResources) (*cloudformation.Template, error) {
|
||||
template := cloudformation.NewTemplate()
|
||||
template.Description = "CloudFormation template created by Docker for deploying applications on Amazon ECS"
|
||||
template.Parameters[parameterClusterName] = cloudformation.Parameter{
|
||||
Type: "String",
|
||||
Description: "Name of the ECS cluster to deploy to (optional)",
|
||||
}
|
||||
b.ensureResources(&resources, project, template)
|
||||
|
||||
template.Parameters[parameterVPCId] = cloudformation.Parameter{
|
||||
Type: "AWS::EC2::VPC::Id",
|
||||
Description: "ID of the VPC",
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME can't set subnets: Ref("SubnetIds") see https://github.com/awslabs/goformation/issues/282
|
||||
template.Parameters["SubnetIds"] = cloudformation.Parameter{
|
||||
Type: "List<AWS::EC2::Subnet::Id>",
|
||||
Description: "The list of SubnetIds, for at least two Availability Zones in the region in your VPC",
|
||||
}
|
||||
*/
|
||||
template.Parameters[parameterSubnet1Id] = cloudformation.Parameter{
|
||||
Type: "AWS::EC2::Subnet::Id",
|
||||
Description: "SubnetId, for Availability Zone 1 in the region in your VPC",
|
||||
}
|
||||
template.Parameters[parameterSubnet2Id] = cloudformation.Parameter{
|
||||
Type: "AWS::EC2::Subnet::Id",
|
||||
Description: "SubnetId, for Availability Zone 2 in the region in your VPC",
|
||||
}
|
||||
|
||||
template.Parameters[parameterLoadBalancerARN] = cloudformation.Parameter{
|
||||
Type: "String",
|
||||
Description: "Name of the LoadBalancer to connect to (optional)",
|
||||
}
|
||||
|
||||
template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(parameterClusterName))
|
||||
|
||||
cluster := createCluster(project, template)
|
||||
|
||||
networks := map[string]string{}
|
||||
for _, net := range project.Networks {
|
||||
networks[net.Name] = convertNetwork(project, net, cloudformation.Ref(parameterVPCId), template)
|
||||
}
|
||||
|
||||
for i, s := range project.Secrets {
|
||||
if s.External.External {
|
||||
continue
|
||||
}
|
||||
secret, err := ioutil.ReadFile(s.File)
|
||||
for name, secret := range project.Secrets {
|
||||
err := b.createSecret(project, name, secret, template)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
|
||||
template.Resources[name] = &secretsmanager.Secret{
|
||||
Description: "",
|
||||
SecretString: string(secret),
|
||||
Tags: projectTags(project),
|
||||
}
|
||||
s.Name = cloudformation.Ref(name)
|
||||
project.Secrets[i] = s
|
||||
}
|
||||
|
||||
createLogGroup(project, template)
|
||||
b.createLogGroup(project, template)
|
||||
|
||||
// Private DNS namespace will allow DNS name for the services to be <service>.<project>.local
|
||||
createCloudMap(project, template)
|
||||
|
||||
loadBalancerARN := createLoadBalancer(project, template)
|
||||
b.createCloudMap(project, template, resources.vpc)
|
||||
|
||||
for _, service := range project.Services {
|
||||
taskExecutionRole := b.createTaskExecutionRole(project, service, template)
|
||||
taskRole := b.createTaskRole(service, template)
|
||||
|
||||
definition, err := convert(project, service)
|
||||
definition, err := b.createTaskExecution(project, service)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
taskExecutionRole := createTaskExecutionRole(service, definition, template)
|
||||
definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
|
||||
|
||||
taskRole := createTaskRole(service, template)
|
||||
if taskRole != "" {
|
||||
definition.TaskRoleArn = cloudformation.Ref(taskRole)
|
||||
}
|
||||
|
@ -208,34 +106,30 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||
template.Resources[taskDefinition] = definition
|
||||
|
||||
var healthCheck *cloudmap.Service_HealthCheckConfig
|
||||
serviceRegistry := b.createServiceRegistry(service, template, healthCheck)
|
||||
|
||||
serviceRegistry := createServiceRegistry(service, template, healthCheck)
|
||||
|
||||
serviceSecurityGroups := []string{}
|
||||
for net := range service.Networks {
|
||||
serviceSecurityGroups = append(serviceSecurityGroups, networks[net])
|
||||
}
|
||||
|
||||
dependsOn := []string{}
|
||||
serviceLB := []ecs.Service_LoadBalancer{}
|
||||
if len(service.Ports) > 0 {
|
||||
for _, port := range service.Ports {
|
||||
protocol := strings.ToUpper(port.Protocol)
|
||||
if getLoadBalancerType(project) == elbv2.LoadBalancerTypeEnumApplication {
|
||||
// we don't set Https as a certificate must be specified for HTTPS listeners
|
||||
protocol = elbv2.ProtocolEnumHttp
|
||||
}
|
||||
if loadBalancerARN != "" {
|
||||
targetGroupName := createTargetGroup(project, service, port, template, protocol)
|
||||
listenerName := createListener(service, port, template, targetGroupName, loadBalancerARN, protocol)
|
||||
dependsOn = append(dependsOn, listenerName)
|
||||
serviceLB = append(serviceLB, ecs.Service_LoadBalancer{
|
||||
ContainerName: service.Name,
|
||||
ContainerPort: int(port.Target),
|
||||
TargetGroupArn: cloudformation.Ref(targetGroupName),
|
||||
})
|
||||
}
|
||||
var (
|
||||
dependsOn []string
|
||||
serviceLB []ecs.Service_LoadBalancer
|
||||
)
|
||||
for _, port := range service.Ports {
|
||||
for net := range service.Networks {
|
||||
b.createIngress(service, net, port, template, resources)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
||||
desiredCount := 1
|
||||
|
@ -249,7 +143,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||
|
||||
minPercent, maxPercent, err := computeRollingUpdateLimits(service)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
assignPublicIP := ecsapi.AssignPublicIpEnabled
|
||||
|
@ -263,7 +157,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||
|
||||
template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
|
||||
AWSCloudFormationDependsOn: dependsOn,
|
||||
Cluster: cluster,
|
||||
Cluster: resources.cluster,
|
||||
DesiredCount: desiredCount,
|
||||
DeploymentController: &ecs.Service_DeploymentController{
|
||||
Type: ecsapi.DeploymentControllerTypeEcs,
|
||||
|
@ -278,11 +172,8 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||
NetworkConfiguration: &ecs.Service_NetworkConfiguration{
|
||||
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
|
||||
AssignPublicIp: assignPublicIP,
|
||||
SecurityGroups: serviceSecurityGroups,
|
||||
Subnets: []string{
|
||||
cloudformation.Ref(parameterSubnet1Id),
|
||||
cloudformation.Ref(parameterSubnet2Id),
|
||||
},
|
||||
SecurityGroups: resources.serviceSecurityGroups(service),
|
||||
Subnets: resources.subnets,
|
||||
},
|
||||
},
|
||||
PlatformVersion: platformVersion,
|
||||
|
@ -293,10 +184,48 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||
TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
|
||||
}
|
||||
}
|
||||
return template, networks, nil
|
||||
return template, nil
|
||||
}
|
||||
|
||||
func createLogGroup(project *types.Project, template *cloudformation.Template) {
|
||||
const allProtocols = "-1"
|
||||
|
||||
func (b *ecsAPIService) createIngress(service types.ServiceConfig, net string, port types.ServicePortConfig, template *cloudformation.Template, resources awsResources) {
|
||||
protocol := strings.ToUpper(port.Protocol)
|
||||
if protocol == "" {
|
||||
protocol = allProtocols
|
||||
}
|
||||
ingress := fmt.Sprintf("%s%dIngress", normalizeResourceName(net), port.Target)
|
||||
template.Resources[ingress] = &ec2.SecurityGroupIngress{
|
||||
CidrIp: "0.0.0.0/0",
|
||||
Description: fmt.Sprintf("%s:%d/%s on %s nextwork", service.Name, port.Target, port.Protocol, net),
|
||||
GroupId: resources.securityGroups[net],
|
||||
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
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) createLogGroup(project *types.Project, template *cloudformation.Template) {
|
||||
retention := 0
|
||||
if v, ok := project.Extensions[extensionRetention]; ok {
|
||||
retention = v.(int)
|
||||
|
@ -348,74 +277,9 @@ func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) {
|
|||
return minPercent, maxPercent, nil
|
||||
}
|
||||
|
||||
func getLoadBalancerType(project *types.Project) string {
|
||||
for _, service := range project.Services {
|
||||
for _, port := range service.Ports {
|
||||
protocol := port.Protocol
|
||||
v, ok := port.Extensions[extensionProtocol]
|
||||
if ok {
|
||||
protocol = v.(string)
|
||||
}
|
||||
if protocol == "http" || protocol == "https" {
|
||||
continue
|
||||
}
|
||||
if port.Published != 80 && port.Published != 443 {
|
||||
return elbv2.LoadBalancerTypeEnumNetwork
|
||||
}
|
||||
}
|
||||
}
|
||||
return elbv2.LoadBalancerTypeEnumApplication
|
||||
}
|
||||
|
||||
func getLoadBalancerSecurityGroups(project *types.Project, template *cloudformation.Template) []string {
|
||||
securityGroups := []string{}
|
||||
for _, network := range project.Networks {
|
||||
if !network.Internal {
|
||||
net := convertNetwork(project, network, cloudformation.Ref(parameterVPCId), template)
|
||||
securityGroups = append(securityGroups, net)
|
||||
}
|
||||
}
|
||||
return uniqueStrings(securityGroups)
|
||||
}
|
||||
|
||||
func createLoadBalancer(project *types.Project, template *cloudformation.Template) string {
|
||||
ports := 0
|
||||
for _, service := range project.Services {
|
||||
ports += len(service.Ports)
|
||||
}
|
||||
if ports == 0 {
|
||||
// Project do not expose any port (batch jobs?)
|
||||
// So no need to create a PortPublisher
|
||||
return ""
|
||||
}
|
||||
|
||||
// load balancer names are limited to 32 characters total
|
||||
loadBalancerName := fmt.Sprintf("%.32s", fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name)))
|
||||
// Create PortPublisher if `ParameterLoadBalancerName` is not set
|
||||
template.Conditions["CreateLoadBalancer"] = cloudformation.Equals("", cloudformation.Ref(parameterLoadBalancerARN))
|
||||
|
||||
loadBalancerType := getLoadBalancerType(project)
|
||||
securityGroups := []string{}
|
||||
if loadBalancerType == elbv2.LoadBalancerTypeEnumApplication {
|
||||
securityGroups = getLoadBalancerSecurityGroups(project, template)
|
||||
}
|
||||
|
||||
template.Resources[loadBalancerName] = &elasticloadbalancingv2.LoadBalancer{
|
||||
Name: loadBalancerName,
|
||||
Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing,
|
||||
SecurityGroups: securityGroups,
|
||||
Subnets: []string{
|
||||
cloudformation.Ref(parameterSubnet1Id),
|
||||
cloudformation.Ref(parameterSubnet2Id),
|
||||
},
|
||||
Tags: projectTags(project),
|
||||
Type: loadBalancerType,
|
||||
AWSCloudFormationCondition: "CreateLoadBalancer",
|
||||
}
|
||||
return cloudformation.If("CreateLoadBalancer", cloudformation.Ref(loadBalancerName), cloudformation.Ref(parameterLoadBalancerARN))
|
||||
}
|
||||
|
||||
func createListener(service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, targetGroupName string, loadBalancerARN string, protocol string) string {
|
||||
func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.ServicePortConfig,
|
||||
template *cloudformation.Template,
|
||||
targetGroupName string, loadBalancerARN string, protocol string) string {
|
||||
listenerName := fmt.Sprintf(
|
||||
"%s%s%dListener",
|
||||
normalizeResourceName(service.Name),
|
||||
|
@ -444,7 +308,7 @@ func createListener(service types.ServiceConfig, port types.ServicePortConfig, t
|
|||
return listenerName
|
||||
}
|
||||
|
||||
func createTargetGroup(project *types.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string) string {
|
||||
func (b *ecsAPIService) createTargetGroup(project *types.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string, vpc string) string {
|
||||
targetGroupName := fmt.Sprintf(
|
||||
"%s%s%dTargetGroup",
|
||||
normalizeResourceName(service.Name),
|
||||
|
@ -457,12 +321,12 @@ func createTargetGroup(project *types.Project, service types.ServiceConfig, port
|
|||
Protocol: protocol,
|
||||
Tags: projectTags(project),
|
||||
TargetType: elbv2.TargetTypeEnumIp,
|
||||
VpcId: cloudformation.Ref(parameterVPCId),
|
||||
VpcId: vpc,
|
||||
}
|
||||
return targetGroupName
|
||||
}
|
||||
|
||||
func createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry {
|
||||
func (b *ecsAPIService) createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry {
|
||||
serviceRegistration := fmt.Sprintf("%sServiceDiscoveryEntry", normalizeResourceName(service.Name))
|
||||
serviceRegistry := ecs.Service_ServiceRegistry{
|
||||
RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"),
|
||||
|
@ -489,9 +353,9 @@ func createServiceRegistry(service types.ServiceConfig, template *cloudformation
|
|||
return serviceRegistry
|
||||
}
|
||||
|
||||
func createTaskExecutionRole(service types.ServiceConfig, definition *ecs.TaskDefinition, template *cloudformation.Template) string {
|
||||
func (b *ecsAPIService) createTaskExecutionRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template) string {
|
||||
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
|
||||
policies := createPolicies(service, definition)
|
||||
policies := b.createPolicies(project, service)
|
||||
template.Resources[taskExecutionRole] = &iam.Role{
|
||||
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
||||
Policies: policies,
|
||||
|
@ -503,7 +367,7 @@ func createTaskExecutionRole(service types.ServiceConfig, definition *ecs.TaskDe
|
|||
return taskExecutionRole
|
||||
}
|
||||
|
||||
func createTaskRole(service types.ServiceConfig, template *cloudformation.Template) string {
|
||||
func (b *ecsAPIService) createTaskRole(service types.ServiceConfig, template *cloudformation.Template) string {
|
||||
taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
|
||||
rolePolicies := []iam.Role_Policy{}
|
||||
if roles, ok := service.Extensions[extensionRole]; ok {
|
||||
|
@ -528,99 +392,21 @@ func createTaskRole(service types.ServiceConfig, template *cloudformation.Templa
|
|||
return taskRole
|
||||
}
|
||||
|
||||
func createCluster(project *types.Project, template *cloudformation.Template) string {
|
||||
template.Resources["Cluster"] = &ecs.Cluster{
|
||||
ClusterName: project.Name,
|
||||
Tags: projectTags(project),
|
||||
AWSCloudFormationCondition: "CreateCluster",
|
||||
}
|
||||
cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(parameterClusterName))
|
||||
return cluster
|
||||
}
|
||||
|
||||
func createCloudMap(project *types.Project, template *cloudformation.Template) {
|
||||
func (b *ecsAPIService) createCloudMap(project *types.Project, template *cloudformation.Template, vpc string) {
|
||||
template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
|
||||
Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
|
||||
Name: fmt.Sprintf("%s.local", project.Name),
|
||||
Vpc: cloudformation.Ref(parameterVPCId),
|
||||
Vpc: vpc,
|
||||
}
|
||||
}
|
||||
|
||||
func convertNetwork(project *types.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string {
|
||||
if net.External.External {
|
||||
return net.Name
|
||||
func (b *ecsAPIService) createPolicies(project *types.Project, service types.ServiceConfig) []iam.Role_Policy {
|
||||
var arns []string
|
||||
if value, ok := service.Extensions[extensionPullCredentials]; ok {
|
||||
arns = append(arns, value.(string))
|
||||
}
|
||||
if sg, ok := net.Extensions[extensionSecurityGroup]; ok {
|
||||
logrus.Warn("to use an existing security-group, set `network.external` and `network.name` in your compose file")
|
||||
logrus.Debugf("Security Group for network %q set by user to %q", net.Name, sg)
|
||||
return sg.(string)
|
||||
}
|
||||
|
||||
var ingresses []ec2.SecurityGroup_Ingress
|
||||
if !net.Internal {
|
||||
for _, service := range project.Services {
|
||||
if _, ok := service.Networks[net.Name]; ok {
|
||||
for _, port := range service.Ports {
|
||||
protocol := strings.ToUpper(port.Protocol)
|
||||
if protocol == "" {
|
||||
protocol = "-1"
|
||||
}
|
||||
ingresses = append(ingresses, ec2.SecurityGroup_Ingress{
|
||||
CidrIp: "0.0.0.0/0",
|
||||
Description: fmt.Sprintf("%s:%d/%s", service.Name, port.Target, port.Protocol),
|
||||
FromPort: int(port.Target),
|
||||
IpProtocol: protocol,
|
||||
ToPort: int(port.Target),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
securityGroup := networkResourceName(project, net.Name)
|
||||
template.Resources[securityGroup] = &ec2.SecurityGroup{
|
||||
GroupDescription: fmt.Sprintf("%s %s Security Group", project.Name, net.Name),
|
||||
GroupName: securityGroup,
|
||||
SecurityGroupIngress: ingresses,
|
||||
VpcId: vpc,
|
||||
Tags: networkTags(project, net),
|
||||
}
|
||||
|
||||
ingress := securityGroup + "Ingress"
|
||||
template.Resources[ingress] = &ec2.SecurityGroupIngress{
|
||||
Description: fmt.Sprintf("Allow communication within network %s", net.Name),
|
||||
IpProtocol: "-1", // all protocols
|
||||
GroupId: cloudformation.Ref(securityGroup),
|
||||
SourceSecurityGroupId: cloudformation.Ref(securityGroup),
|
||||
}
|
||||
|
||||
return cloudformation.Ref(securityGroup)
|
||||
}
|
||||
|
||||
func networkResourceName(project *types.Project, network string) string {
|
||||
return fmt.Sprintf("%s%sNetwork", normalizeResourceName(project.Name), normalizeResourceName(network))
|
||||
}
|
||||
|
||||
func serviceResourceName(service string) string {
|
||||
return fmt.Sprintf("%sService", normalizeResourceName(service))
|
||||
}
|
||||
|
||||
func normalizeResourceName(s string) string {
|
||||
return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
|
||||
}
|
||||
|
||||
func createPolicies(service types.ServiceConfig, taskDef *ecs.TaskDefinition) []iam.Role_Policy {
|
||||
arns := []string{}
|
||||
for _, container := range taskDef.ContainerDefinitions {
|
||||
if container.RepositoryCredentials != nil {
|
||||
arns = append(arns, container.RepositoryCredentials.CredentialsParameter)
|
||||
}
|
||||
if len(container.Secrets) > 0 {
|
||||
for _, s := range container.Secrets {
|
||||
arns = append(arns, s.ValueFrom)
|
||||
}
|
||||
}
|
||||
|
||||
for _, secret := range service.Secrets {
|
||||
arns = append(arns, project.Secrets[secret.Source].Name)
|
||||
}
|
||||
if len(arns) > 0 {
|
||||
return []iam.Role_Policy{
|
||||
|
@ -641,14 +427,14 @@ func createPolicies(service types.ServiceConfig, taskDef *ecs.TaskDefinition) []
|
|||
return nil
|
||||
}
|
||||
|
||||
func uniqueStrings(items []string) []string {
|
||||
keys := make(map[string]bool)
|
||||
unique := []string{}
|
||||
for _, item := range items {
|
||||
if _, val := keys[item]; !val {
|
||||
keys[item] = true
|
||||
unique = append(unique, item)
|
||||
}
|
||||
}
|
||||
return unique
|
||||
func networkResourceName(network string) string {
|
||||
return fmt.Sprintf("%sNetwork", normalizeResourceName(network))
|
||||
}
|
||||
|
||||
func serviceResourceName(service string) string {
|
||||
return fmt.Sprintf("%sService", normalizeResourceName(service))
|
||||
}
|
||||
|
||||
func normalizeResourceName(s string) string {
|
||||
return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
|
||||
}
|
||||
|
|
|
@ -170,13 +170,13 @@ networks:
|
|||
back-tier:
|
||||
internal: true
|
||||
`)
|
||||
assert.Check(t, template.Resources["TestPublicNetwork"] != nil)
|
||||
assert.Check(t, template.Resources["TestBacktierNetwork"] != nil)
|
||||
assert.Check(t, template.Resources["TestBacktierNetworkIngress"] != nil)
|
||||
i := template.Resources["TestPublicNetworkIngress"]
|
||||
assert.Check(t, template.Resources["FronttierNetwork"] != nil)
|
||||
assert.Check(t, template.Resources["BacktierNetwork"] != nil)
|
||||
assert.Check(t, template.Resources["BacktierNetworkIngress"] != nil)
|
||||
i := template.Resources["FronttierNetworkIngress"]
|
||||
assert.Check(t, i != nil)
|
||||
ingress := *i.(*ec2.SecurityGroupIngress)
|
||||
assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("TestPublicNetwork"))
|
||||
assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("FronttierNetwork"))
|
||||
|
||||
}
|
||||
|
||||
|
@ -187,13 +187,6 @@ func TestLoadBalancerTypeApplication(t *testing.T) {
|
|||
image: nginx
|
||||
ports:
|
||||
- 80:80
|
||||
`,
|
||||
`services:
|
||||
test:
|
||||
image: nginx
|
||||
ports:
|
||||
- target: 8080
|
||||
protocol: http
|
||||
`,
|
||||
`services:
|
||||
test:
|
||||
|
@ -205,7 +198,7 @@ func TestLoadBalancerTypeApplication(t *testing.T) {
|
|||
}
|
||||
for _, y := range cases {
|
||||
template := convertYaml(t, y)
|
||||
lb := template.Resources["TestLoadBalancer"]
|
||||
lb := template.Resources["LoadBalancer"]
|
||||
assert.Check(t, lb != nil)
|
||||
loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
|
||||
assert.Check(t, len(loadBalancer.Name) <= 32)
|
||||
|
@ -328,7 +321,7 @@ services:
|
|||
memory: 2043248M
|
||||
`)
|
||||
backend := &ecsAPIService{}
|
||||
_, _, err := backend.convert(model)
|
||||
_, err := backend.convert(model, awsResources{})
|
||||
assert.ErrorContains(t, err, "the resources requested are not supported by ECS/Fargate")
|
||||
}
|
||||
|
||||
|
@ -341,7 +334,7 @@ services:
|
|||
- 80:80
|
||||
- 88:88
|
||||
`)
|
||||
lb := template.Resources["TestLoadBalancer"]
|
||||
lb := template.Resources["LoadBalancer"]
|
||||
assert.Check(t, lb != nil)
|
||||
loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
|
||||
assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumNetwork)
|
||||
|
@ -412,7 +405,10 @@ services:
|
|||
|
||||
func convertResultAsString(t *testing.T, project *types.Project) string {
|
||||
backend := &ecsAPIService{}
|
||||
template, _, err := backend.convert(project)
|
||||
template, err := backend.convert(project, awsResources{
|
||||
vpc: "vpcID",
|
||||
subnets: []string{"subnet1", "subnet2"},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
resultAsJSON, err := marshall(template)
|
||||
assert.NilError(t, err)
|
||||
|
@ -432,7 +428,7 @@ func load(t *testing.T, paths ...string) *types.Project {
|
|||
func convertYaml(t *testing.T, yaml string) *cloudformation.Template {
|
||||
project := loadConfig(t, yaml)
|
||||
backend := &ecsAPIService{}
|
||||
template, _, err := backend.convert(project)
|
||||
template, err := backend.convert(project, awsResources{})
|
||||
assert.NilError(t, err)
|
||||
return template
|
||||
}
|
||||
|
|
|
@ -17,10 +17,33 @@
|
|||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/compose-spec/compose-go/compatibility"
|
||||
"github.com/compose-spec/compose-go/errdefs"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) checkCompatibility(project *types.Project) error {
|
||||
var checker compatibility.Checker = &fargateCompatibilityChecker{
|
||||
compatibility.AllowList{
|
||||
Supported: compatibleComposeAttributes,
|
||||
},
|
||||
}
|
||||
compatibility.Check(project, checker)
|
||||
for _, err := range checker.Errors() {
|
||||
if errdefs.IsIncompatibleError(err) {
|
||||
return err
|
||||
}
|
||||
logrus.Warn(err.Error())
|
||||
}
|
||||
if !compatibility.IsCompatible(checker) {
|
||||
return fmt.Errorf("compose file is incompatible with Amazon ECS")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type fargateCompatibilityChecker struct {
|
||||
compatibility.AllowList
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
|
||||
ecsapi "github.com/aws/aws-sdk-go/service/ecs"
|
||||
"github.com/awslabs/goformation/v4/cloudformation"
|
||||
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
||||
|
@ -39,7 +40,7 @@ import (
|
|||
|
||||
const secretsInitContainerImage = "docker/ecs-secrets-sidecar"
|
||||
|
||||
func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) {
|
||||
func (b *ecsAPIService) createTaskExecution(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) {
|
||||
cpu, mem, err := toLimits(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -532,11 +533,8 @@ func toHostEntryPtr(hosts types.HostsList) []ecs.TaskDefinition_HostEntry {
|
|||
}
|
||||
|
||||
func getRepoCredentials(service types.ServiceConfig) *ecs.TaskDefinition_RepositoryCredentials {
|
||||
// extract registry and namespace string from image name
|
||||
for key, value := range service.Extensions {
|
||||
if key == extensionPullCredentials {
|
||||
return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)}
|
||||
}
|
||||
if value, ok := service.Extensions[extensionPullCredentials]; ok {
|
||||
return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: value.(string)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
33
ecs/ec2.go
33
ecs/ec2.go
|
@ -28,7 +28,7 @@ import (
|
|||
"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 {
|
||||
func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *types.Project, template *cloudformation.Template, resources awsResources) error {
|
||||
var ec2 bool
|
||||
for _, s := range project.Services {
|
||||
if requireEC2(s) {
|
||||
|
@ -51,11 +51,6 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
|
|||
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"),
|
||||
|
@ -63,36 +58,29 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
|
|||
TargetCapacity: 100,
|
||||
},
|
||||
},
|
||||
Tags: projectTags(project),
|
||||
AWSCloudFormationCondition: "CreateCluster",
|
||||
Tags: projectTags(project),
|
||||
}
|
||||
|
||||
template.Resources["AutoscalingGroup"] = &autoscaling.AutoScalingGroup{
|
||||
LaunchConfigurationName: cloudformation.Ref("LaunchConfiguration"),
|
||||
MaxSize: "10", //TODO
|
||||
MinSize: "1",
|
||||
VPCZoneIdentifier: []string{
|
||||
cloudformation.Ref(parameterSubnet1Id),
|
||||
cloudformation.Ref(parameterSubnet2Id),
|
||||
},
|
||||
AWSCloudFormationCondition: "CreateCluster",
|
||||
VPCZoneIdentifier: resources.subnets,
|
||||
}
|
||||
|
||||
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",
|
||||
ImageId: ami,
|
||||
InstanceType: machineType,
|
||||
SecurityGroups: resources.allSecurityGroups(),
|
||||
IamInstanceProfile: cloudformation.Ref("EC2InstanceProfile"),
|
||||
UserData: userData,
|
||||
}
|
||||
|
||||
template.Resources["EC2InstanceProfile"] = &iam.InstanceProfile{
|
||||
Roles: []string{cloudformation.Ref("EC2InstanceRole")},
|
||||
AWSCloudFormationCondition: "CreateCluster",
|
||||
Roles: []string{cloudformation.Ref("EC2InstanceRole")},
|
||||
}
|
||||
|
||||
template.Resources["EC2InstanceRole"] = &iam.Role{
|
||||
|
@ -100,8 +88,7 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
|
|||
ManagedPolicyArns: []string{
|
||||
ecsEC2InstanceRole,
|
||||
},
|
||||
Tags: projectTags(project),
|
||||
AWSCloudFormationCondition: "CreateCluster",
|
||||
Tags: projectTags(project),
|
||||
}
|
||||
|
||||
cluster := template.Resources["Cluster"].(*ecs.Cluster)
|
||||
|
|
12
ecs/ps.go
12
ecs/ps.go
|
@ -25,18 +25,15 @@ import (
|
|||
)
|
||||
|
||||
func (b *ecsAPIService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
|
||||
parameters, err := b.SDK.ListStackParameters(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cluster := parameters[parameterClusterName]
|
||||
|
||||
resources, err := b.SDK.ListStackResources(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
servicesARN := []string{}
|
||||
var (
|
||||
cluster = project
|
||||
servicesARN []string
|
||||
)
|
||||
for _, r := range resources {
|
||||
switch r.Type {
|
||||
case "AWS::ECS::Service":
|
||||
|
@ -45,6 +42,7 @@ func (b *ecsAPIService) Ps(ctx context.Context, project string) ([]compose.Servi
|
|||
cluster = r.ARN
|
||||
}
|
||||
}
|
||||
|
||||
if len(servicesARN) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
43
ecs/sdk.go
43
ecs/sdk.go
|
@ -196,22 +196,13 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
|
|||
return len(stacks.Stacks) > 0, nil
|
||||
}
|
||||
|
||||
func (s sdk) CreateStack(ctx context.Context, name string, template []byte, parameters map[string]string) error {
|
||||
func (s sdk) CreateStack(ctx context.Context, name string, template []byte) error {
|
||||
logrus.Debug("Create CloudFormation stack")
|
||||
|
||||
param := []*cloudformation.Parameter{}
|
||||
for name, value := range parameters {
|
||||
param = append(param, &cloudformation.Parameter{
|
||||
ParameterKey: aws.String(name),
|
||||
ParameterValue: aws.String(value),
|
||||
})
|
||||
}
|
||||
|
||||
_, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
|
||||
OnFailure: aws.String("DELETE"),
|
||||
StackName: aws.String(name),
|
||||
TemplateBody: aws.String(string(template)),
|
||||
Parameters: param,
|
||||
TimeoutInMinutes: nil,
|
||||
Capabilities: []*string{
|
||||
aws.String(cloudformation.CapabilityCapabilityIam),
|
||||
|
@ -226,24 +217,15 @@ func (s sdk) CreateStack(ctx context.Context, name string, template []byte, para
|
|||
return err
|
||||
}
|
||||
|
||||
func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte, parameters map[string]string) (string, error) {
|
||||
func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte) (string, error) {
|
||||
logrus.Debug("Create CloudFormation Changeset")
|
||||
|
||||
param := []*cloudformation.Parameter{}
|
||||
for name := range parameters {
|
||||
param = append(param, &cloudformation.Parameter{
|
||||
ParameterKey: aws.String(name),
|
||||
UsePreviousValue: aws.Bool(true),
|
||||
})
|
||||
}
|
||||
|
||||
update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05"))
|
||||
changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{
|
||||
ChangeSetName: aws.String(update),
|
||||
ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
|
||||
StackName: aws.String(name),
|
||||
TemplateBody: aws.String(string(template)),
|
||||
Parameters: param,
|
||||
Capabilities: []*string{
|
||||
aws.String(cloudformation.CapabilityCapabilityIam),
|
||||
},
|
||||
|
@ -647,15 +629,18 @@ func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string
|
|||
return publicIPs, nil
|
||||
}
|
||||
|
||||
func (s sdk) LoadBalancerExists(ctx context.Context, arn string) (bool, error) {
|
||||
logrus.Debug("CheckRequirements if PortPublisher exists: ", arn)
|
||||
func (s sdk) LoadBalancerType(ctx context.Context, arn string) (string, error) {
|
||||
logrus.Debug("Check if LoadBalancer exists: ", arn)
|
||||
lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
|
||||
LoadBalancerArns: []*string{aws.String(arn)},
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
return "", err
|
||||
}
|
||||
return len(lbs.LoadBalancers) > 0, nil
|
||||
if len(lbs.LoadBalancers) == 0 {
|
||||
return "", fmt.Errorf("load balancer does not exist: %s", arn)
|
||||
}
|
||||
return aws.StringValue(lbs.LoadBalancers[0].Type), nil
|
||||
}
|
||||
|
||||
func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) {
|
||||
|
@ -719,3 +704,13 @@ func (s sdk) GetParameter(ctx context.Context, name string) (string, error) {
|
|||
|
||||
return ami.ImageID, nil
|
||||
}
|
||||
|
||||
func (s sdk) SecurityGroupExists(ctx context.Context, sg string) (bool, error) {
|
||||
desc, err := s.EC2.DescribeSecurityGroupsWithContext(ctx, &ec2.DescribeSecurityGroupsInput{
|
||||
GroupIds: aws.StringSlice([]string{sg}),
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(desc.SecurityGroups) > 0, nil
|
||||
}
|
||||
|
|
|
@ -1,59 +1,15 @@
|
|||
{
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Conditions": {
|
||||
"CreateCluster": {
|
||||
"Fn::Equals": [
|
||||
"",
|
||||
{
|
||||
"Ref": "ParameterClusterName"
|
||||
}
|
||||
]
|
||||
},
|
||||
"CreateLoadBalancer": {
|
||||
"Fn::Equals": [
|
||||
"",
|
||||
{
|
||||
"Ref": "ParameterLoadBalancerARN"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Description": "CloudFormation template created by Docker for deploying applications on Amazon ECS",
|
||||
"Parameters": {
|
||||
"ParameterClusterName": {
|
||||
"Description": "Name of the ECS cluster to deploy to (optional)",
|
||||
"Type": "String"
|
||||
},
|
||||
"ParameterLoadBalancerARN": {
|
||||
"Description": "Name of the LoadBalancer to connect to (optional)",
|
||||
"Type": "String"
|
||||
},
|
||||
"ParameterSubnet1Id": {
|
||||
"Description": "SubnetId, for Availability Zone 1 in the region in your VPC",
|
||||
"Type": "AWS::EC2::Subnet::Id"
|
||||
},
|
||||
"ParameterSubnet2Id": {
|
||||
"Description": "SubnetId, for Availability Zone 2 in the region in your VPC",
|
||||
"Type": "AWS::EC2::Subnet::Id"
|
||||
},
|
||||
"ParameterVPCId": {
|
||||
"Description": "ID of the VPC",
|
||||
"Type": "AWS::EC2::VPC::Id"
|
||||
}
|
||||
},
|
||||
"Resources": {
|
||||
"CloudMap": {
|
||||
"Properties": {
|
||||
"Description": "Service Map for Docker Compose project TestSimpleConvert",
|
||||
"Name": "TestSimpleConvert.local",
|
||||
"Vpc": {
|
||||
"Ref": "ParameterVPCId"
|
||||
}
|
||||
"Vpc": "vpcID"
|
||||
},
|
||||
"Type": "AWS::ServiceDiscovery::PrivateDnsNamespace"
|
||||
},
|
||||
"Cluster": {
|
||||
"Condition": "CreateCluster",
|
||||
"Properties": {
|
||||
"ClusterName": "TestSimpleConvert",
|
||||
"Tags": [
|
||||
|
@ -65,6 +21,72 @@
|
|||
},
|
||||
"Type": "AWS::ECS::Cluster"
|
||||
},
|
||||
"Default80Ingress": {
|
||||
"Properties": {
|
||||
"CidrIp": "0.0.0.0/0",
|
||||
"Description": "simple:80/tcp on default nextwork",
|
||||
"FromPort": 80,
|
||||
"GroupId": {
|
||||
"Ref": "DefaultNetwork"
|
||||
},
|
||||
"IpProtocol": "TCP",
|
||||
"ToPort": 80
|
||||
},
|
||||
"Type": "AWS::EC2::SecurityGroupIngress"
|
||||
},
|
||||
"DefaultNetwork": {
|
||||
"Properties": {
|
||||
"GroupDescription": "TestSimpleConvert Security Group for default network",
|
||||
"GroupName": "DefaultNetwork",
|
||||
"Tags": [
|
||||
{
|
||||
"Key": "com.docker.compose.project",
|
||||
"Value": "TestSimpleConvert"
|
||||
},
|
||||
{
|
||||
"Key": "com.docker.compose.network",
|
||||
"Value": "default"
|
||||
}
|
||||
],
|
||||
"VpcId": "vpcID"
|
||||
},
|
||||
"Type": "AWS::EC2::SecurityGroup"
|
||||
},
|
||||
"DefaultNetworkIngress": {
|
||||
"Properties": {
|
||||
"Description": "Allow communication within network default",
|
||||
"GroupId": {
|
||||
"Ref": "DefaultNetwork"
|
||||
},
|
||||
"IpProtocol": "-1",
|
||||
"SourceSecurityGroupId": {
|
||||
"Ref": "DefaultNetwork"
|
||||
}
|
||||
},
|
||||
"Type": "AWS::EC2::SecurityGroupIngress"
|
||||
},
|
||||
"LoadBalancer": {
|
||||
"Properties": {
|
||||
"Scheme": "internet-facing",
|
||||
"SecurityGroups": [
|
||||
{
|
||||
"Ref": "DefaultNetwork"
|
||||
}
|
||||
],
|
||||
"Subnets": [
|
||||
"subnet1",
|
||||
"subnet2"
|
||||
],
|
||||
"Tags": [
|
||||
{
|
||||
"Key": "com.docker.compose.project",
|
||||
"Value": "TestSimpleConvert"
|
||||
}
|
||||
],
|
||||
"Type": "application"
|
||||
},
|
||||
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer"
|
||||
},
|
||||
"LogGroup": {
|
||||
"Properties": {
|
||||
"LogGroupName": "/docker-compose/TestSimpleConvert"
|
||||
|
@ -77,15 +99,7 @@
|
|||
],
|
||||
"Properties": {
|
||||
"Cluster": {
|
||||
"Fn::If": [
|
||||
"CreateCluster",
|
||||
{
|
||||
"Ref": "Cluster"
|
||||
},
|
||||
{
|
||||
"Ref": "ParameterClusterName"
|
||||
}
|
||||
]
|
||||
"Ref": "Cluster"
|
||||
},
|
||||
"DeploymentConfiguration": {
|
||||
"MaximumPercent": 200,
|
||||
|
@ -110,16 +124,12 @@
|
|||
"AssignPublicIp": "ENABLED",
|
||||
"SecurityGroups": [
|
||||
{
|
||||
"Ref": "TestSimpleConvertDefaultNetwork"
|
||||
"Ref": "DefaultNetwork"
|
||||
}
|
||||
],
|
||||
"Subnets": [
|
||||
{
|
||||
"Ref": "ParameterSubnet1Id"
|
||||
},
|
||||
{
|
||||
"Ref": "ParameterSubnet2Id"
|
||||
}
|
||||
"subnet1",
|
||||
"subnet2"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -191,15 +201,7 @@
|
|||
}
|
||||
],
|
||||
"LoadBalancerArn": {
|
||||
"Fn::If": [
|
||||
"CreateLoadBalancer",
|
||||
{
|
||||
"Ref": "TestSimpleConvertLoadBalancer"
|
||||
},
|
||||
{
|
||||
"Ref": "ParameterLoadBalancerARN"
|
||||
}
|
||||
]
|
||||
"Ref": "LoadBalancer"
|
||||
},
|
||||
"Port": 80,
|
||||
"Protocol": "HTTP"
|
||||
|
@ -217,9 +219,7 @@
|
|||
}
|
||||
],
|
||||
"TargetType": "ip",
|
||||
"VpcId": {
|
||||
"Ref": "ParameterVPCId"
|
||||
}
|
||||
"VpcId": "vpcID"
|
||||
},
|
||||
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup"
|
||||
},
|
||||
|
@ -304,76 +304,6 @@
|
|||
]
|
||||
},
|
||||
"Type": "AWS::IAM::Role"
|
||||
},
|
||||
"TestSimpleConvertDefaultNetwork": {
|
||||
"Properties": {
|
||||
"GroupDescription": "TestSimpleConvert default Security Group",
|
||||
"GroupName": "TestSimpleConvertDefaultNetwork",
|
||||
"SecurityGroupIngress": [
|
||||
{
|
||||
"CidrIp": "0.0.0.0/0",
|
||||
"Description": "simple:80/tcp",
|
||||
"FromPort": 80,
|
||||
"IpProtocol": "TCP",
|
||||
"ToPort": 80
|
||||
}
|
||||
],
|
||||
"Tags": [
|
||||
{
|
||||
"Key": "com.docker.compose.project",
|
||||
"Value": "TestSimpleConvert"
|
||||
},
|
||||
{
|
||||
"Key": "com.docker.compose.network",
|
||||
"Value": "default"
|
||||
}
|
||||
],
|
||||
"VpcId": {
|
||||
"Ref": "ParameterVPCId"
|
||||
}
|
||||
},
|
||||
"Type": "AWS::EC2::SecurityGroup"
|
||||
},
|
||||
"TestSimpleConvertDefaultNetworkIngress": {
|
||||
"Properties": {
|
||||
"Description": "Allow communication within network default",
|
||||
"GroupId": {
|
||||
"Ref": "TestSimpleConvertDefaultNetwork"
|
||||
},
|
||||
"IpProtocol": "-1",
|
||||
"SourceSecurityGroupId": {
|
||||
"Ref": "TestSimpleConvertDefaultNetwork"
|
||||
}
|
||||
},
|
||||
"Type": "AWS::EC2::SecurityGroupIngress"
|
||||
},
|
||||
"TestSimpleConvertLoadBalancer": {
|
||||
"Condition": "CreateLoadBalancer",
|
||||
"Properties": {
|
||||
"Name": "TestSimpleConvertLoadBalancer",
|
||||
"Scheme": "internet-facing",
|
||||
"SecurityGroups": [
|
||||
{
|
||||
"Ref": "TestSimpleConvertDefaultNetwork"
|
||||
}
|
||||
],
|
||||
"Subnets": [
|
||||
{
|
||||
"Ref": "ParameterSubnet1Id"
|
||||
},
|
||||
{
|
||||
"Ref": "ParameterSubnet2Id"
|
||||
}
|
||||
],
|
||||
"Tags": [
|
||||
{
|
||||
"Key": "com.docker.compose.project",
|
||||
"Value": "TestSimpleConvert"
|
||||
}
|
||||
],
|
||||
"Type": "application"
|
||||
},
|
||||
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
87
ecs/up.go
87
ecs/up.go
|
@ -32,42 +32,11 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
|
|||
return err
|
||||
}
|
||||
|
||||
cluster, err := b.GetCluster(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template, err := b.Convert(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vpc, err := b.GetVPC(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subNets, err := b.SDK.GetSubNets(ctx, vpc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(subNets) < 2 {
|
||||
return fmt.Errorf("VPC %s should have at least 2 associated subnets in different availability zones", vpc)
|
||||
}
|
||||
|
||||
lb, err := b.GetLoadBalancer(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parameters := map[string]string{
|
||||
parameterClusterName: cluster,
|
||||
parameterVPCId: vpc,
|
||||
parameterSubnet1Id: subNets[0],
|
||||
parameterSubnet2Id: subNets[1],
|
||||
parameterLoadBalancerARN: lb,
|
||||
}
|
||||
|
||||
update, err := b.SDK.StackExists(ctx, project.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -75,7 +44,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
|
|||
operation := stackCreate
|
||||
if update {
|
||||
operation = stackUpdate
|
||||
changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template, parameters)
|
||||
changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -84,7 +53,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
err = b.SDK.CreateStack(ctx, project.Name, template, parameters)
|
||||
err = b.SDK.CreateStack(ctx, project.Name, template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -101,55 +70,3 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
|
|||
err = b.WaitStackCompletion(ctx, project.Name, operation)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b ecsAPIService) GetVPC(ctx context.Context, project *types.Project) (string, error) {
|
||||
var vpcID string
|
||||
//check compose file for custom VPC selected
|
||||
if vpc, ok := project.Extensions[extensionVPC]; ok {
|
||||
vpcID = vpc.(string)
|
||||
} else {
|
||||
defaultVPC, err := b.SDK.GetDefaultVPC(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
vpcID = defaultVPC
|
||||
}
|
||||
|
||||
err := b.SDK.CheckVPC(ctx, vpcID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return vpcID, nil
|
||||
}
|
||||
|
||||
func (b ecsAPIService) GetLoadBalancer(ctx context.Context, project *types.Project) (string, error) {
|
||||
//check compose file for custom VPC selected
|
||||
if ext, ok := project.Extensions[extensionLB]; ok {
|
||||
lb := ext.(string)
|
||||
ok, err := b.SDK.LoadBalancerExists(ctx, lb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !ok {
|
||||
return "", fmt.Errorf("load balancer does not exist: %s", lb)
|
||||
}
|
||||
return lb, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b ecsAPIService) GetCluster(ctx context.Context, project *types.Project) (string, error) {
|
||||
//check compose file for custom VPC selected
|
||||
if ext, ok := project.Extensions[extensionCluster]; ok {
|
||||
cluster := ext.(string)
|
||||
ok, err := b.SDK.ClusterExists(ctx, cluster)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !ok {
|
||||
return "", fmt.Errorf("cluster does not exist: %s", cluster)
|
||||
}
|
||||
return cluster, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/awslabs/goformation/v4/cloudformation"
|
||||
"github.com/awslabs/goformation/v4/cloudformation/ec2"
|
||||
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) createNFSmountIngress(securityGroups []string, project *types.Project, n string, template *cloudformation.Template) error {
|
||||
target := securityGroups[0]
|
||||
for _, s := range project.Services {
|
||||
for _, v := range s.Volumes {
|
||||
if v.Source != n {
|
||||
continue
|
||||
}
|
||||
var source string
|
||||
for net := range s.Networks {
|
||||
network := project.Networks[net]
|
||||
if ext, ok := network.Extensions[extensionSecurityGroup]; ok {
|
||||
source = ext.(string)
|
||||
} else {
|
||||
source = networkResourceName(net)
|
||||
}
|
||||
break
|
||||
}
|
||||
name := fmt.Sprintf("%sNFSMount%s", s.Name, n)
|
||||
template.Resources[name] = &ec2.SecurityGroupIngress{
|
||||
Description: fmt.Sprintf("Allow NFS mount for %s on %s", s.Name, n),
|
||||
GroupId: target,
|
||||
SourceSecurityGroupId: cloudformation.Ref(source),
|
||||
IpProtocol: "tcp",
|
||||
FromPort: 2049,
|
||||
ToPort: 2049,
|
||||
}
|
||||
service := template.Resources[serviceResourceName(s.Name)].(*ecs.Service)
|
||||
service.AWSCloudFormationDependsOn = append(service.AWSCloudFormationDependsOn, name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
2
ecs/x.go
2
ecs/x.go
|
@ -20,7 +20,7 @@ const (
|
|||
extensionSecurityGroup = "x-aws-securitygroup"
|
||||
extensionVPC = "x-aws-vpc"
|
||||
extensionPullCredentials = "x-aws-pull_credentials"
|
||||
extensionLB = "x-aws-loadbalancer"
|
||||
extensionLoadBalancer = "x-aws-loadbalancer"
|
||||
extensionProtocol = "x-aws-protocol"
|
||||
extensionCluster = "x-aws-cluster"
|
||||
extensionKeys = "x-aws-keys"
|
||||
|
|
Loading…
Reference in New Issue