mirror of
https://github.com/docker/compose.git
synced 2025-07-25 14:44:29 +02:00
introduce awsResources to replace CloudFormation parameters
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
b30cd649b9
commit
7034254911
278
ecs/awsResources.go
Normal file
278
ecs/awsResources.go
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
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 {
|
||||||
|
sdk sdk
|
||||||
|
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 (r *awsResources) parse(ctx context.Context, project *types.Project) error {
|
||||||
|
return findProjectFnError(ctx, project,
|
||||||
|
r.parseClusterExtension,
|
||||||
|
r.parseVPCExtension,
|
||||||
|
r.parseLoadBalancerExtension,
|
||||||
|
r.parseSecurityGroupExtension,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *awsResources) parseClusterExtension(ctx context.Context, project *types.Project) error {
|
||||||
|
if x, ok := project.Extensions[extensionCluster]; ok {
|
||||||
|
cluster := x.(string)
|
||||||
|
ok, err := r.sdk.ClusterExists(ctx, cluster)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("cluster does not exist: %s", cluster)
|
||||||
|
}
|
||||||
|
r.cluster = cluster
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *awsResources) parseVPCExtension(ctx context.Context, project *types.Project) error {
|
||||||
|
if x, ok := project.Extensions[extensionVPC]; ok {
|
||||||
|
vpc := x.(string)
|
||||||
|
err := r.sdk.CheckVPC(ctx, vpc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.vpc = vpc
|
||||||
|
} else {
|
||||||
|
defaultVPC, err := r.sdk.GetDefaultVPC(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.vpc = defaultVPC
|
||||||
|
}
|
||||||
|
|
||||||
|
subNets, err := r.sdk.GetSubNets(ctx, r.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", r.vpc)
|
||||||
|
}
|
||||||
|
r.subnets = subNets
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *awsResources) parseLoadBalancerExtension(ctx context.Context, project *types.Project) error {
|
||||||
|
if x, ok := project.Extensions[extensionLoadBalancer]; ok {
|
||||||
|
loadBalancer := x.(string)
|
||||||
|
loadBalancerType, err := r.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.loadBalancer = loadBalancer
|
||||||
|
r.loadBalancerType = loadBalancerType
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *awsResources) parseSecurityGroupExtension(ctx context.Context, project *types.Project) error {
|
||||||
|
if r.securityGroups == nil {
|
||||||
|
r.securityGroups = make(map[string]string, len(project.Networks))
|
||||||
|
}
|
||||||
|
for name, net := range project.Networks {
|
||||||
|
if net.External.External {
|
||||||
|
r.securityGroups[name] = 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)
|
||||||
|
r.securityGroups[name] = x.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure all required resources pre-exists or are defined as cloudformation resources
|
||||||
|
func (r *awsResources) ensure(project *types.Project, template *cloudformation.Template) {
|
||||||
|
r.ensureCluster(project, template)
|
||||||
|
r.ensureNetworks(project, template)
|
||||||
|
r.ensureLoadBalancer(project, template)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *awsResources) ensureCluster(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 (r *awsResources) ensureNetworks(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: "-1", // all protocols
|
||||||
|
GroupId: cloudformation.Ref(securityGroup),
|
||||||
|
SourceSecurityGroupId: cloudformation.Ref(securityGroup),
|
||||||
|
}
|
||||||
|
|
||||||
|
r.securityGroups[name] = cloudformation.Ref(securityGroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *awsResources) ensureLoadBalancer(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
|
||||||
|
}
|
||||||
|
|
||||||
|
type projectFn func(ctx context.Context, project *types.Project) error
|
||||||
|
|
||||||
|
func findProjectFnError(ctx context.Context, project *types.Project, funcs ...projectFn) error {
|
||||||
|
for _, fn := range funcs {
|
||||||
|
err := fn(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,17 +73,20 @@ func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sdk := newSDK(sess)
|
||||||
return &ecsAPIService{
|
return &ecsAPIService{
|
||||||
ctx: ecsCtx,
|
ctx: ecsCtx,
|
||||||
Region: ecsCtx.Region,
|
Region: ecsCtx.Region,
|
||||||
SDK: newSDK(sess),
|
SDK: sdk,
|
||||||
|
resources: awsResources{sdk: sdk},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ecsAPIService struct {
|
type ecsAPIService struct {
|
||||||
ctx store.EcsContext
|
ctx store.EcsContext
|
||||||
Region string
|
Region string
|
||||||
SDK sdk
|
SDK sdk
|
||||||
|
resources awsResources
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ecsAPIService) ContainerService() containers.Service {
|
func (a *ecsAPIService) ContainerService() containers.Service {
|
||||||
|
@ -35,15 +35,6 @@ import (
|
|||||||
"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
|
"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
|
||||||
cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
|
cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"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) {
|
func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
|
||||||
@ -52,7 +43,12 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
template, networks, err := b.convert(project)
|
err = b.resources.parse(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := b.convert(project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -68,7 +64,7 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.createCapacityProvider(ctx, project, networks, template)
|
err = b.createCapacityProvider(ctx, project, template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -77,86 +73,31 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert a compose project into a CloudFormation template
|
// Convert a compose project into a CloudFormation template
|
||||||
func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, map[string]string, error) { //nolint:gocyclo
|
func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, error) { //nolint:gocyclo
|
||||||
template := cloudformation.NewTemplate()
|
template := cloudformation.NewTemplate()
|
||||||
template.Description = "CloudFormation template created by Docker for deploying applications on Amazon ECS"
|
b.resources.ensure(project, template)
|
||||||
template.Parameters[parameterClusterName] = cloudformation.Parameter{
|
|
||||||
Type: "String",
|
|
||||||
Description: "Name of the ECS cluster to deploy to (optional)",
|
|
||||||
}
|
|
||||||
|
|
||||||
template.Parameters[parameterVPCId] = cloudformation.Parameter{
|
for name, secret := range project.Secrets {
|
||||||
Type: "AWS::EC2::VPC::Id",
|
err := b.createSecret(project, name, secret, template)
|
||||||
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)
|
|
||||||
if err != nil {
|
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
|
// Private DNS namespace will allow DNS name for the services to be <service>.<project>.local
|
||||||
createCloudMap(project, template)
|
b.createCloudMap(project, template)
|
||||||
|
|
||||||
loadBalancerARN := createLoadBalancer(project, template)
|
|
||||||
|
|
||||||
for _, service := range project.Services {
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
taskExecutionRole := createTaskExecutionRole(project, service, template)
|
|
||||||
definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
|
definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
|
||||||
|
|
||||||
taskRole := createTaskRole(service, template)
|
|
||||||
if taskRole != "" {
|
if taskRole != "" {
|
||||||
definition.TaskRoleArn = cloudformation.Ref(taskRole)
|
definition.TaskRoleArn = cloudformation.Ref(taskRole)
|
||||||
}
|
}
|
||||||
@ -165,34 +106,30 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
template.Resources[taskDefinition] = definition
|
template.Resources[taskDefinition] = definition
|
||||||
|
|
||||||
var healthCheck *cloudmap.Service_HealthCheckConfig
|
var healthCheck *cloudmap.Service_HealthCheckConfig
|
||||||
|
serviceRegistry := b.createServiceRegistry(service, template, healthCheck)
|
||||||
|
|
||||||
serviceRegistry := createServiceRegistry(service, template, healthCheck)
|
var (
|
||||||
|
dependsOn []string
|
||||||
serviceSecurityGroups := []string{}
|
serviceLB []ecs.Service_LoadBalancer
|
||||||
for net := range service.Networks {
|
)
|
||||||
serviceSecurityGroups = append(serviceSecurityGroups, networks[net])
|
for _, port := range service.Ports {
|
||||||
}
|
for net := range service.Networks {
|
||||||
|
b.createIngress(service, net, port, template)
|
||||||
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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protocol := strings.ToUpper(port.Protocol)
|
||||||
|
if b.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)
|
||||||
|
listenerName := b.createListener(service, port, template, targetGroupName, b.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
|
desiredCount := 1
|
||||||
@ -206,7 +143,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
|
|
||||||
minPercent, maxPercent, err := computeRollingUpdateLimits(service)
|
minPercent, maxPercent, err := computeRollingUpdateLimits(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
assignPublicIP := ecsapi.AssignPublicIpEnabled
|
assignPublicIP := ecsapi.AssignPublicIpEnabled
|
||||||
@ -220,7 +157,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
|
|
||||||
template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
|
template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
|
||||||
AWSCloudFormationDependsOn: dependsOn,
|
AWSCloudFormationDependsOn: dependsOn,
|
||||||
Cluster: cluster,
|
Cluster: b.resources.cluster,
|
||||||
DesiredCount: desiredCount,
|
DesiredCount: desiredCount,
|
||||||
DeploymentController: &ecs.Service_DeploymentController{
|
DeploymentController: &ecs.Service_DeploymentController{
|
||||||
Type: ecsapi.DeploymentControllerTypeEcs,
|
Type: ecsapi.DeploymentControllerTypeEcs,
|
||||||
@ -235,11 +172,8 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
NetworkConfiguration: &ecs.Service_NetworkConfiguration{
|
NetworkConfiguration: &ecs.Service_NetworkConfiguration{
|
||||||
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
|
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
|
||||||
AssignPublicIp: assignPublicIP,
|
AssignPublicIp: assignPublicIP,
|
||||||
SecurityGroups: serviceSecurityGroups,
|
SecurityGroups: b.resources.serviceSecurityGroups(service),
|
||||||
Subnets: []string{
|
Subnets: b.resources.subnets,
|
||||||
cloudformation.Ref(parameterSubnet1Id),
|
|
||||||
cloudformation.Ref(parameterSubnet2Id),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PlatformVersion: platformVersion,
|
PlatformVersion: platformVersion,
|
||||||
@ -250,10 +184,46 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
|
|||||||
TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
|
TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return template, networks, nil
|
return template, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createLogGroup(project *types.Project, template *cloudformation.Template) {
|
func (b *ecsAPIService) createIngress(service types.ServiceConfig, net string, port types.ServicePortConfig, template *cloudformation.Template) {
|
||||||
|
protocol := strings.ToUpper(port.Protocol)
|
||||||
|
if protocol == "" {
|
||||||
|
protocol = "-1"
|
||||||
|
}
|
||||||
|
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: b.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
|
retention := 0
|
||||||
if v, ok := project.Extensions[extensionRetention]; ok {
|
if v, ok := project.Extensions[extensionRetention]; ok {
|
||||||
retention = v.(int)
|
retention = v.(int)
|
||||||
@ -305,74 +275,9 @@ func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) {
|
|||||||
return minPercent, maxPercent, nil
|
return minPercent, maxPercent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLoadBalancerType(project *types.Project) string {
|
func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.ServicePortConfig,
|
||||||
for _, service := range project.Services {
|
template *cloudformation.Template,
|
||||||
for _, port := range service.Ports {
|
targetGroupName string, loadBalancerARN string, protocol string) string {
|
||||||
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 {
|
|
||||||
listenerName := fmt.Sprintf(
|
listenerName := fmt.Sprintf(
|
||||||
"%s%s%dListener",
|
"%s%s%dListener",
|
||||||
normalizeResourceName(service.Name),
|
normalizeResourceName(service.Name),
|
||||||
@ -401,7 +306,7 @@ func createListener(service types.ServiceConfig, port types.ServicePortConfig, t
|
|||||||
return listenerName
|
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) string {
|
||||||
targetGroupName := fmt.Sprintf(
|
targetGroupName := fmt.Sprintf(
|
||||||
"%s%s%dTargetGroup",
|
"%s%s%dTargetGroup",
|
||||||
normalizeResourceName(service.Name),
|
normalizeResourceName(service.Name),
|
||||||
@ -414,12 +319,12 @@ func createTargetGroup(project *types.Project, service types.ServiceConfig, port
|
|||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
Tags: projectTags(project),
|
Tags: projectTags(project),
|
||||||
TargetType: elbv2.TargetTypeEnumIp,
|
TargetType: elbv2.TargetTypeEnumIp,
|
||||||
VpcId: cloudformation.Ref(parameterVPCId),
|
VpcId: b.resources.vpc,
|
||||||
}
|
}
|
||||||
return targetGroupName
|
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))
|
serviceRegistration := fmt.Sprintf("%sServiceDiscoveryEntry", normalizeResourceName(service.Name))
|
||||||
serviceRegistry := ecs.Service_ServiceRegistry{
|
serviceRegistry := ecs.Service_ServiceRegistry{
|
||||||
RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"),
|
RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"),
|
||||||
@ -446,9 +351,9 @@ func createServiceRegistry(service types.ServiceConfig, template *cloudformation
|
|||||||
return serviceRegistry
|
return serviceRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTaskExecutionRole(project *types.Project, service types.ServiceConfig, 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))
|
taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
|
||||||
policies := createPolicies(project, service)
|
policies := b.createPolicies(project, service)
|
||||||
template.Resources[taskExecutionRole] = &iam.Role{
|
template.Resources[taskExecutionRole] = &iam.Role{
|
||||||
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
|
||||||
Policies: policies,
|
Policies: policies,
|
||||||
@ -460,7 +365,7 @@ func createTaskExecutionRole(project *types.Project, service types.ServiceConfig
|
|||||||
return taskExecutionRole
|
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))
|
taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
|
||||||
rolePolicies := []iam.Role_Policy{}
|
rolePolicies := []iam.Role_Policy{}
|
||||||
if roles, ok := service.Extensions[extensionRole]; ok {
|
if roles, ok := service.Extensions[extensionRole]; ok {
|
||||||
@ -485,88 +390,15 @@ func createTaskRole(service types.ServiceConfig, template *cloudformation.Templa
|
|||||||
return taskRole
|
return taskRole
|
||||||
}
|
}
|
||||||
|
|
||||||
func createCluster(project *types.Project, template *cloudformation.Template) string {
|
func (b *ecsAPIService) createCloudMap(project *types.Project, template *cloudformation.Template) {
|
||||||
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) {
|
|
||||||
template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
|
template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
|
||||||
Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
|
Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
|
||||||
Name: fmt.Sprintf("%s.local", project.Name),
|
Name: fmt.Sprintf("%s.local", project.Name),
|
||||||
Vpc: cloudformation.Ref(parameterVPCId),
|
Vpc: b.resources.vpc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertNetwork(project *types.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string {
|
func (b *ecsAPIService) createPolicies(project *types.Project, service types.ServiceConfig) []iam.Role_Policy {
|
||||||
if net.External.External {
|
|
||||||
return net.Name
|
|
||||||
}
|
|
||||||
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(project *types.Project, service types.ServiceConfig) []iam.Role_Policy {
|
|
||||||
var arns []string
|
var arns []string
|
||||||
if value, ok := service.Extensions[extensionPullCredentials]; ok {
|
if value, ok := service.Extensions[extensionPullCredentials]; ok {
|
||||||
arns = append(arns, value.(string))
|
arns = append(arns, value.(string))
|
||||||
@ -593,14 +425,14 @@ func createPolicies(project *types.Project, service types.ServiceConfig) []iam.R
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func uniqueStrings(items []string) []string {
|
func networkResourceName(network string) string {
|
||||||
keys := make(map[string]bool)
|
return fmt.Sprintf("%sNetwork", normalizeResourceName(network))
|
||||||
unique := []string{}
|
}
|
||||||
for _, item := range items {
|
|
||||||
if _, val := keys[item]; !val {
|
func serviceResourceName(service string) string {
|
||||||
keys[item] = true
|
return fmt.Sprintf("%sService", normalizeResourceName(service))
|
||||||
unique = append(unique, item)
|
}
|
||||||
}
|
|
||||||
}
|
func normalizeResourceName(s string) string {
|
||||||
return unique
|
return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
|
||||||
}
|
}
|
||||||
|
@ -170,13 +170,13 @@ networks:
|
|||||||
back-tier:
|
back-tier:
|
||||||
internal: true
|
internal: true
|
||||||
`)
|
`)
|
||||||
assert.Check(t, template.Resources["TestPublicNetwork"] != nil)
|
assert.Check(t, template.Resources["FronttierNetwork"] != nil)
|
||||||
assert.Check(t, template.Resources["TestBacktierNetwork"] != nil)
|
assert.Check(t, template.Resources["BacktierNetwork"] != nil)
|
||||||
assert.Check(t, template.Resources["TestBacktierNetworkIngress"] != nil)
|
assert.Check(t, template.Resources["BacktierNetworkIngress"] != nil)
|
||||||
i := template.Resources["TestPublicNetworkIngress"]
|
i := template.Resources["FronttierNetworkIngress"]
|
||||||
assert.Check(t, i != nil)
|
assert.Check(t, i != nil)
|
||||||
ingress := *i.(*ec2.SecurityGroupIngress)
|
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
|
image: nginx
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
`,
|
|
||||||
`services:
|
|
||||||
test:
|
|
||||||
image: nginx
|
|
||||||
ports:
|
|
||||||
- target: 8080
|
|
||||||
protocol: http
|
|
||||||
`,
|
`,
|
||||||
`services:
|
`services:
|
||||||
test:
|
test:
|
||||||
@ -205,7 +198,7 @@ func TestLoadBalancerTypeApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, y := range cases {
|
for _, y := range cases {
|
||||||
template := convertYaml(t, y)
|
template := convertYaml(t, y)
|
||||||
lb := template.Resources["TestLoadBalancer"]
|
lb := template.Resources["LoadBalancer"]
|
||||||
assert.Check(t, lb != nil)
|
assert.Check(t, lb != nil)
|
||||||
loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
|
loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
|
||||||
assert.Check(t, len(loadBalancer.Name) <= 32)
|
assert.Check(t, len(loadBalancer.Name) <= 32)
|
||||||
@ -328,7 +321,7 @@ services:
|
|||||||
memory: 2043248M
|
memory: 2043248M
|
||||||
`)
|
`)
|
||||||
backend := &ecsAPIService{}
|
backend := &ecsAPIService{}
|
||||||
_, _, err := backend.convert(model)
|
_, err := backend.convert(model)
|
||||||
assert.ErrorContains(t, err, "the resources requested are not supported by ECS/Fargate")
|
assert.ErrorContains(t, err, "the resources requested are not supported by ECS/Fargate")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +334,7 @@ services:
|
|||||||
- 80:80
|
- 80:80
|
||||||
- 88:88
|
- 88:88
|
||||||
`)
|
`)
|
||||||
lb := template.Resources["TestLoadBalancer"]
|
lb := template.Resources["LoadBalancer"]
|
||||||
assert.Check(t, lb != nil)
|
assert.Check(t, lb != nil)
|
||||||
loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
|
loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
|
||||||
assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumNetwork)
|
assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumNetwork)
|
||||||
@ -411,8 +404,13 @@ services:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func convertResultAsString(t *testing.T, project *types.Project) string {
|
func convertResultAsString(t *testing.T, project *types.Project) string {
|
||||||
backend := &ecsAPIService{}
|
backend := &ecsAPIService{
|
||||||
template, _, err := backend.convert(project)
|
resources: awsResources{
|
||||||
|
vpc: "vpcID",
|
||||||
|
subnets: []string{"subnet1", "subnet2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
template, err := backend.convert(project)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
resultAsJSON, err := marshall(template)
|
resultAsJSON, err := marshall(template)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
@ -432,7 +430,7 @@ func load(t *testing.T, paths ...string) *types.Project {
|
|||||||
func convertYaml(t *testing.T, yaml string) *cloudformation.Template {
|
func convertYaml(t *testing.T, yaml string) *cloudformation.Template {
|
||||||
project := loadConfig(t, yaml)
|
project := loadConfig(t, yaml)
|
||||||
backend := &ecsAPIService{}
|
backend := &ecsAPIService{}
|
||||||
template, _, err := backend.convert(project)
|
template, err := backend.convert(project)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
|
||||||
ecsapi "github.com/aws/aws-sdk-go/service/ecs"
|
ecsapi "github.com/aws/aws-sdk-go/service/ecs"
|
||||||
"github.com/awslabs/goformation/v4/cloudformation"
|
"github.com/awslabs/goformation/v4/cloudformation"
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
||||||
@ -39,7 +40,7 @@ import (
|
|||||||
|
|
||||||
const secretsInitContainerImage = "docker/ecs-secrets-sidecar"
|
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)
|
cpu, mem, err := toLimits(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
33
ecs/ec2.go
33
ecs/ec2.go
@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/compose-spec/compose-go/types"
|
"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) error {
|
||||||
var ec2 bool
|
var ec2 bool
|
||||||
for _, s := range project.Services {
|
for _, s := range project.Services {
|
||||||
if requireEC2(s) {
|
if requireEC2(s) {
|
||||||
@ -51,11 +51,6 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var securityGroups []string
|
|
||||||
for _, r := range networks {
|
|
||||||
securityGroups = append(securityGroups, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
template.Resources["CapacityProvider"] = &ecs.CapacityProvider{
|
template.Resources["CapacityProvider"] = &ecs.CapacityProvider{
|
||||||
AutoScalingGroupProvider: &ecs.CapacityProvider_AutoScalingGroupProvider{
|
AutoScalingGroupProvider: &ecs.CapacityProvider_AutoScalingGroupProvider{
|
||||||
AutoScalingGroupArn: cloudformation.Ref("AutoscalingGroup"),
|
AutoScalingGroupArn: cloudformation.Ref("AutoscalingGroup"),
|
||||||
@ -63,36 +58,29 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
|
|||||||
TargetCapacity: 100,
|
TargetCapacity: 100,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Tags: projectTags(project),
|
Tags: projectTags(project),
|
||||||
AWSCloudFormationCondition: "CreateCluster",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template.Resources["AutoscalingGroup"] = &autoscaling.AutoScalingGroup{
|
template.Resources["AutoscalingGroup"] = &autoscaling.AutoScalingGroup{
|
||||||
LaunchConfigurationName: cloudformation.Ref("LaunchConfiguration"),
|
LaunchConfigurationName: cloudformation.Ref("LaunchConfiguration"),
|
||||||
MaxSize: "10", //TODO
|
MaxSize: "10", //TODO
|
||||||
MinSize: "1",
|
MinSize: "1",
|
||||||
VPCZoneIdentifier: []string{
|
VPCZoneIdentifier: b.resources.subnets,
|
||||||
cloudformation.Ref(parameterSubnet1Id),
|
|
||||||
cloudformation.Ref(parameterSubnet2Id),
|
|
||||||
},
|
|
||||||
AWSCloudFormationCondition: "CreateCluster",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userData := base64.StdEncoding.EncodeToString([]byte(
|
userData := base64.StdEncoding.EncodeToString([]byte(
|
||||||
fmt.Sprintf("#!/bin/bash\necho ECS_CLUSTER=%s >> /etc/ecs/ecs.config", project.Name)))
|
fmt.Sprintf("#!/bin/bash\necho ECS_CLUSTER=%s >> /etc/ecs/ecs.config", project.Name)))
|
||||||
|
|
||||||
template.Resources["LaunchConfiguration"] = &autoscaling.LaunchConfiguration{
|
template.Resources["LaunchConfiguration"] = &autoscaling.LaunchConfiguration{
|
||||||
ImageId: ami,
|
ImageId: ami,
|
||||||
InstanceType: machineType,
|
InstanceType: machineType,
|
||||||
SecurityGroups: securityGroups,
|
SecurityGroups: b.resources.allSecurityGroups(),
|
||||||
IamInstanceProfile: cloudformation.Ref("EC2InstanceProfile"),
|
IamInstanceProfile: cloudformation.Ref("EC2InstanceProfile"),
|
||||||
UserData: userData,
|
UserData: userData,
|
||||||
AWSCloudFormationCondition: "CreateCluster",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template.Resources["EC2InstanceProfile"] = &iam.InstanceProfile{
|
template.Resources["EC2InstanceProfile"] = &iam.InstanceProfile{
|
||||||
Roles: []string{cloudformation.Ref("EC2InstanceRole")},
|
Roles: []string{cloudformation.Ref("EC2InstanceRole")},
|
||||||
AWSCloudFormationCondition: "CreateCluster",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template.Resources["EC2InstanceRole"] = &iam.Role{
|
template.Resources["EC2InstanceRole"] = &iam.Role{
|
||||||
@ -100,8 +88,7 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
|
|||||||
ManagedPolicyArns: []string{
|
ManagedPolicyArns: []string{
|
||||||
ecsEC2InstanceRole,
|
ecsEC2InstanceRole,
|
||||||
},
|
},
|
||||||
Tags: projectTags(project),
|
Tags: projectTags(project),
|
||||||
AWSCloudFormationCondition: "CreateCluster",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cluster := template.Resources["Cluster"].(*ecs.Cluster)
|
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) {
|
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)
|
resources, err := b.SDK.ListStackResources(ctx, project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesARN := []string{}
|
var (
|
||||||
|
cluster = project
|
||||||
|
servicesARN []string
|
||||||
|
)
|
||||||
for _, r := range resources {
|
for _, r := range resources {
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
case "AWS::ECS::Service":
|
case "AWS::ECS::Service":
|
||||||
@ -45,6 +42,7 @@ func (b *ecsAPIService) Ps(ctx context.Context, project string) ([]compose.Servi
|
|||||||
cluster = r.ARN
|
cluster = r.ARN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(servicesARN) == 0 {
|
if len(servicesARN) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
33
ecs/sdk.go
33
ecs/sdk.go
@ -196,22 +196,13 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
|
|||||||
return len(stacks.Stacks) > 0, nil
|
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")
|
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{
|
_, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
|
||||||
OnFailure: aws.String("DELETE"),
|
OnFailure: aws.String("DELETE"),
|
||||||
StackName: aws.String(name),
|
StackName: aws.String(name),
|
||||||
TemplateBody: aws.String(string(template)),
|
TemplateBody: aws.String(string(template)),
|
||||||
Parameters: param,
|
|
||||||
TimeoutInMinutes: nil,
|
TimeoutInMinutes: nil,
|
||||||
Capabilities: []*string{
|
Capabilities: []*string{
|
||||||
aws.String(cloudformation.CapabilityCapabilityIam),
|
aws.String(cloudformation.CapabilityCapabilityIam),
|
||||||
@ -226,24 +217,15 @@ func (s sdk) CreateStack(ctx context.Context, name string, template []byte, para
|
|||||||
return err
|
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")
|
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"))
|
update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05"))
|
||||||
changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{
|
changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{
|
||||||
ChangeSetName: aws.String(update),
|
ChangeSetName: aws.String(update),
|
||||||
ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
|
ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
|
||||||
StackName: aws.String(name),
|
StackName: aws.String(name),
|
||||||
TemplateBody: aws.String(string(template)),
|
TemplateBody: aws.String(string(template)),
|
||||||
Parameters: param,
|
|
||||||
Capabilities: []*string{
|
Capabilities: []*string{
|
||||||
aws.String(cloudformation.CapabilityCapabilityIam),
|
aws.String(cloudformation.CapabilityCapabilityIam),
|
||||||
},
|
},
|
||||||
@ -647,15 +629,18 @@ func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string
|
|||||||
return publicIPs, nil
|
return publicIPs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s sdk) LoadBalancerExists(ctx context.Context, arn string) (bool, error) {
|
func (s sdk) LoadBalancerType(ctx context.Context, arn string) (string, error) {
|
||||||
logrus.Debug("CheckRequirements if PortPublisher exists: ", arn)
|
logrus.Debug("Check if LoadBalancer exists: ", arn)
|
||||||
lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
|
lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
|
||||||
LoadBalancerArns: []*string{aws.String(arn)},
|
LoadBalancerArns: []*string{aws.String(arn)},
|
||||||
})
|
})
|
||||||
if err != nil {
|
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) {
|
func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) {
|
||||||
|
@ -1,59 +1,15 @@
|
|||||||
{
|
{
|
||||||
"AWSTemplateFormatVersion": "2010-09-09",
|
"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": {
|
"Resources": {
|
||||||
"CloudMap": {
|
"CloudMap": {
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"Description": "Service Map for Docker Compose project TestSimpleConvert",
|
"Description": "Service Map for Docker Compose project TestSimpleConvert",
|
||||||
"Name": "TestSimpleConvert.local",
|
"Name": "TestSimpleConvert.local",
|
||||||
"Vpc": {
|
"Vpc": "vpcID"
|
||||||
"Ref": "ParameterVPCId"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Type": "AWS::ServiceDiscovery::PrivateDnsNamespace"
|
"Type": "AWS::ServiceDiscovery::PrivateDnsNamespace"
|
||||||
},
|
},
|
||||||
"Cluster": {
|
"Cluster": {
|
||||||
"Condition": "CreateCluster",
|
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"ClusterName": "TestSimpleConvert",
|
"ClusterName": "TestSimpleConvert",
|
||||||
"Tags": [
|
"Tags": [
|
||||||
@ -65,6 +21,72 @@
|
|||||||
},
|
},
|
||||||
"Type": "AWS::ECS::Cluster"
|
"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": {
|
"LogGroup": {
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"LogGroupName": "/docker-compose/TestSimpleConvert"
|
"LogGroupName": "/docker-compose/TestSimpleConvert"
|
||||||
@ -77,15 +99,7 @@
|
|||||||
],
|
],
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"Cluster": {
|
"Cluster": {
|
||||||
"Fn::If": [
|
"Ref": "Cluster"
|
||||||
"CreateCluster",
|
|
||||||
{
|
|
||||||
"Ref": "Cluster"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Ref": "ParameterClusterName"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"DeploymentConfiguration": {
|
"DeploymentConfiguration": {
|
||||||
"MaximumPercent": 200,
|
"MaximumPercent": 200,
|
||||||
@ -110,16 +124,12 @@
|
|||||||
"AssignPublicIp": "ENABLED",
|
"AssignPublicIp": "ENABLED",
|
||||||
"SecurityGroups": [
|
"SecurityGroups": [
|
||||||
{
|
{
|
||||||
"Ref": "TestSimpleConvertDefaultNetwork"
|
"Ref": "DefaultNetwork"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Subnets": [
|
"Subnets": [
|
||||||
{
|
"subnet1",
|
||||||
"Ref": "ParameterSubnet1Id"
|
"subnet2"
|
||||||
},
|
|
||||||
{
|
|
||||||
"Ref": "ParameterSubnet2Id"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -191,15 +201,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"LoadBalancerArn": {
|
"LoadBalancerArn": {
|
||||||
"Fn::If": [
|
"Ref": "LoadBalancer"
|
||||||
"CreateLoadBalancer",
|
|
||||||
{
|
|
||||||
"Ref": "TestSimpleConvertLoadBalancer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Ref": "ParameterLoadBalancerARN"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"Port": 80,
|
"Port": 80,
|
||||||
"Protocol": "HTTP"
|
"Protocol": "HTTP"
|
||||||
@ -217,9 +219,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"TargetType": "ip",
|
"TargetType": "ip",
|
||||||
"VpcId": {
|
"VpcId": "vpcID"
|
||||||
"Ref": "ParameterVPCId"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup"
|
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup"
|
||||||
},
|
},
|
||||||
@ -304,76 +304,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Type": "AWS::IAM::Role"
|
"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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cluster, err := b.GetCluster(ctx, project)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := b.Convert(ctx, project)
|
template, err := b.Convert(ctx, project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
update, err := b.SDK.StackExists(ctx, project.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -75,7 +44,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
|
|||||||
operation := stackCreate
|
operation := stackCreate
|
||||||
if update {
|
if update {
|
||||||
operation = stackUpdate
|
operation = stackUpdate
|
||||||
changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template, parameters)
|
changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -84,7 +53,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = b.SDK.CreateStack(ctx, project.Name, template, parameters)
|
err = b.SDK.CreateStack(ctx, project.Name, template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -101,55 +70,3 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
|
|||||||
err = b.WaitStackCompletion(ctx, project.Name, operation)
|
err = b.WaitStackCompletion(ctx, project.Name, operation)
|
||||||
return err
|
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
|
|
||||||
}
|
|
||||||
|
@ -38,7 +38,7 @@ func (b *ecsAPIService) createNFSmountIngress(securityGroups []string, project *
|
|||||||
if ext, ok := network.Extensions[extensionSecurityGroup]; ok {
|
if ext, ok := network.Extensions[extensionSecurityGroup]; ok {
|
||||||
source = ext.(string)
|
source = ext.(string)
|
||||||
} else {
|
} else {
|
||||||
source = networkResourceName(project, net)
|
source = networkResourceName(net)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
2
ecs/x.go
2
ecs/x.go
@ -20,7 +20,7 @@ const (
|
|||||||
extensionSecurityGroup = "x-aws-securitygroup"
|
extensionSecurityGroup = "x-aws-securitygroup"
|
||||||
extensionVPC = "x-aws-vpc"
|
extensionVPC = "x-aws-vpc"
|
||||||
extensionPullCredentials = "x-aws-pull_credentials"
|
extensionPullCredentials = "x-aws-pull_credentials"
|
||||||
extensionLB = "x-aws-loadbalancer"
|
extensionLoadBalancer = "x-aws-loadbalancer"
|
||||||
extensionProtocol = "x-aws-protocol"
|
extensionProtocol = "x-aws-protocol"
|
||||||
extensionCluster = "x-aws-cluster"
|
extensionCluster = "x-aws-cluster"
|
||||||
extensionKeys = "x-aws-keys"
|
extensionKeys = "x-aws-keys"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user