Marshall cloudformation template as yaml

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-11-05 09:27:14 +01:00
parent 4b88896547
commit cb02622318
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
11 changed files with 251 additions and 357 deletions

View File

@ -129,6 +129,6 @@ func (cs *aciComposeService) Logs(ctx context.Context, project string, w io.Writ
return errdefs.ErrNotImplemented return errdefs.ErrNotImplemented
} }
func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project) ([]byte, error) { func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }

View File

@ -55,6 +55,6 @@ func (c *composeService) List(context.Context, string) ([]compose.Stack, error)
} }
// Convert translate compose model into backend's native format // Convert translate compose model into backend's native format
func (c *composeService) Convert(context.Context, *types.Project) ([]byte, error) { func (c *composeService) Convert(context.Context, *types.Project, string) ([]byte, error) {
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }

View File

@ -36,7 +36,7 @@ type Service interface {
// List executes the equivalent to a `docker stack ls` // List executes the equivalent to a `docker stack ls`
List(ctx context.Context, projectName string) ([]Stack, error) List(ctx context.Context, projectName string) ([]Stack, error)
// Convert translate compose model into backend's native format // Convert translate compose model into backend's native format
Convert(ctx context.Context, project *types.Project) ([]byte, error) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error)
} }
// PortPublisher hold status about published port // PortPublisher hold status about published port

View File

@ -39,6 +39,7 @@ func convertCommand() *cobra.Command {
convertCmd.Flags().StringVar(&opts.WorkingDir, "workdir", "", "Work dir") convertCmd.Flags().StringVar(&opts.WorkingDir, "workdir", "", "Work dir")
convertCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files") convertCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
convertCmd.Flags().StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables") convertCmd.Flags().StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
convertCmd.Flags().StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
return convertCmd return convertCmd
} }
@ -60,7 +61,7 @@ func runConvert(ctx context.Context, opts composeOptions) error {
return err return err
} }
json, err = c.ComposeService().Convert(ctx, project) json, err = c.ComposeService().Convert(ctx, project, opts.Format)
if err != nil { if err != nil {
return err return err
} }

View File

@ -37,13 +37,13 @@ import (
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
) )
func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]byte, error) { func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {
template, err := b.convert(ctx, project) template, err := b.convert(ctx, project)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return marshall(template) return marshall(template, format)
} }
func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*cloudformation.Template, error) { func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*cloudformation.Template, error) {

View File

@ -44,7 +44,7 @@ func TestSimpleConvert(t *testing.T) {
bytes, err := ioutil.ReadFile("testdata/input/simple-single-service.yaml") bytes, err := ioutil.ReadFile("testdata/input/simple-single-service.yaml")
assert.NilError(t, err) assert.NilError(t, err)
template := convertYaml(t, string(bytes), useDefaultVPC) template := convertYaml(t, string(bytes), useDefaultVPC)
resultAsJSON, err := marshall(template) resultAsJSON, err := marshall(template, "yaml")
assert.NilError(t, err) assert.NilError(t, err)
result := fmt.Sprintf("%s\n", string(resultAsJSON)) result := fmt.Sprintf("%s\n", string(resultAsJSON))
expected := "simple/simple-cloudformation-conversion.golden" expected := "simple/simple-cloudformation-conversion.golden"

View File

@ -57,7 +57,7 @@ func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project, deta
return fmt.Errorf("ECS simulation mode require Docker-compose 1.27, found %s", version) return fmt.Errorf("ECS simulation mode require Docker-compose 1.27, found %s", version)
} }
converted, err := e.Convert(ctx, project) converted, err := e.Convert(ctx, project, "yaml")
if err != nil { if err != nil {
return err return err
} }
@ -69,7 +69,7 @@ func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project, deta
return cmd.Run() return cmd.Run()
} }
func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project) ([]byte, error) { func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {
project.Networks["credentials_network"] = types.NetworkConfig{ project.Networks["credentials_network"] = types.NetworkConfig{
Driver: "bridge", Driver: "bridge",
Ipam: types.IPAMConfig{ Ipam: types.IPAMConfig{

View File

@ -22,29 +22,50 @@ import (
"strings" "strings"
"github.com/awslabs/goformation/v4/cloudformation" "github.com/awslabs/goformation/v4/cloudformation"
"github.com/sanathkr/go-yaml"
) )
func marshall(template *cloudformation.Template) ([]byte, error) { func marshall(template *cloudformation.Template, format string) ([]byte, error) {
raw, err := template.JSON() var (
source func() ([]byte, error)
marshal func(in interface{}) ([]byte, error)
unmarshal func(in []byte, out interface{}) error
)
switch format {
case "yaml":
source = template.YAML
marshal = yaml.Marshal
unmarshal = yaml.Unmarshal
case "json":
source = template.JSON
marshal = func(in interface{}) ([]byte, error) {
return json.MarshalIndent(in, "", " ")
}
unmarshal = json.Unmarshal
default:
return nil, fmt.Errorf("unsupported format %q", format)
}
raw, err := source()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var unmarshalled interface{} var unmarshalled interface{}
if err := json.Unmarshal(raw, &unmarshalled); err != nil { if err := unmarshal(raw, &unmarshalled); err != nil {
return nil, fmt.Errorf("invalid JSON: %s", err) return nil, fmt.Errorf("invalid JSON: %s", err)
} }
if input, ok := unmarshalled.(map[string]interface{}); ok { if input, ok := unmarshalled.(map[interface{}]interface{}); ok {
if resources, ok := input["Resources"]; ok { if resources, ok := input["Resources"]; ok {
for _, uresource := range resources.(map[string]interface{}) { for _, uresource := range resources.(map[interface{}]interface{}) {
if resource, ok := uresource.(map[string]interface{}); ok { if resource, ok := uresource.(map[interface{}]interface{}); ok {
if resource["Type"] == "AWS::ECS::TaskDefinition" { if resource["Type"] == "AWS::ECS::TaskDefinition" {
properties := resource["Properties"].(map[string]interface{}) properties := resource["Properties"].(map[interface{}]interface{})
for _, def := range properties["ContainerDefinitions"].([]interface{}) { for _, def := range properties["ContainerDefinitions"].([]interface{}) {
containerDefinition := def.(map[string]interface{}) containerDefinition := def.(map[interface{}]interface{})
if strings.HasSuffix(containerDefinition["Name"].(string), "_InitContainer") { if strings.HasSuffix(containerDefinition["Name"].(string), "_InitContainer") {
containerDefinition["Essential"] = "false" containerDefinition["Essential"] = false
} }
} }
} }
@ -53,9 +74,5 @@ func marshall(template *cloudformation.Template) ([]byte, error) {
} }
} }
raw, err = json.MarshalIndent(unmarshalled, "", " ") return marshal(unmarshalled)
if err != nil {
return nil, fmt.Errorf("invalid JSON: %s", err)
}
return raw, err
} }

View File

@ -1,332 +1,208 @@
{ AWSTemplateFormatVersion: 2010-09-09
"AWSTemplateFormatVersion": "2010-09-09", 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-123
"Vpc": "vpc-123" Type: AWS::ServiceDiscovery::PrivateDnsNamespace
}, Cluster:
"Type": "AWS::ServiceDiscovery::PrivateDnsNamespace" Properties:
}, ClusterName: TestSimpleConvert
"Cluster": { Tags:
"Properties": { - Key: com.docker.compose.project
"ClusterName": "TestSimpleConvert", Value: TestSimpleConvert
"Tags": [ Type: AWS::ECS::Cluster
{ Default80Ingress:
"Key": "com.docker.compose.project", Properties:
"Value": "TestSimpleConvert" CidrIp: 0.0.0.0/0
} Description: simple:80/tcp on default network
] FromPort: 80
}, GroupId:
"Type": "AWS::ECS::Cluster" Ref: DefaultNetwork
}, IpProtocol: TCP
"Default80Ingress": { ToPort: 80
"Properties": { Type: AWS::EC2::SecurityGroupIngress
"CidrIp": "0.0.0.0/0", DefaultNetwork:
"Description": "simple:80/tcp on default network", Properties:
"FromPort": 80, GroupDescription: TestSimpleConvert Security Group for default network
"GroupId": { Tags:
"Ref": "DefaultNetwork" - Key: com.docker.compose.project
}, Value: TestSimpleConvert
"IpProtocol": "TCP", - Key: com.docker.compose.network
"ToPort": 80 Value: default
}, VpcId: vpc-123
"Type": "AWS::EC2::SecurityGroupIngress" Type: AWS::EC2::SecurityGroup
}, DefaultNetworkIngress:
"DefaultNetwork": { Properties:
"Properties": { Description: Allow communication within network default
"GroupDescription": "TestSimpleConvert Security Group for default network", GroupId:
"Tags": [ Ref: DefaultNetwork
{ IpProtocol: "-1"
"Key": "com.docker.compose.project", SourceSecurityGroupId:
"Value": "TestSimpleConvert" Ref: DefaultNetwork
}, Type: AWS::EC2::SecurityGroupIngress
{ LoadBalancer:
"Key": "com.docker.compose.network", Properties:
"Value": "default" Scheme: internet-facing
} SecurityGroups:
], - Ref: DefaultNetwork
"VpcId": "vpc-123" Subnets:
}, - subnet1
"Type": "AWS::EC2::SecurityGroup" - subnet2
}, Tags:
"DefaultNetworkIngress": { - Key: com.docker.compose.project
"Properties": { Value: TestSimpleConvert
"Description": "Allow communication within network default", Type: application
"GroupId": { Type: AWS::ElasticLoadBalancingV2::LoadBalancer
"Ref": "DefaultNetwork" LogGroup:
}, Properties:
"IpProtocol": "-1", LogGroupName: /docker-compose/TestSimpleConvert
"SourceSecurityGroupId": { Type: AWS::Logs::LogGroup
"Ref": "DefaultNetwork" SimpleService:
} DependsOn:
}, - SimpleTCP80Listener
"Type": "AWS::EC2::SecurityGroupIngress" Properties:
}, Cluster:
"LoadBalancer": { Fn::GetAtt:
"Properties": { - Cluster
"Scheme": "internet-facing", - Arn
"SecurityGroups": [ DeploymentConfiguration:
{ MaximumPercent: 200
"Ref": "DefaultNetwork" MinimumHealthyPercent: 100
} DeploymentController:
], Type: ECS
"Subnets": [ DesiredCount: 1
"subnet1", LaunchType: FARGATE
"subnet2" LoadBalancers:
], - ContainerName: simple
"Tags": [ ContainerPort: 80
{ TargetGroupArn:
"Key": "com.docker.compose.project", Ref: SimpleTCP80TargetGroup
"Value": "TestSimpleConvert" NetworkConfiguration:
} AwsvpcConfiguration:
], AssignPublicIp: ENABLED
"Type": "application" SecurityGroups:
}, - Ref: DefaultNetwork
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer" Subnets:
}, - subnet1
"LogGroup": { - subnet2
"Properties": { PlatformVersion: 1.4.0
"LogGroupName": "/docker-compose/TestSimpleConvert" PropagateTags: SERVICE
}, SchedulingStrategy: REPLICA
"Type": "AWS::Logs::LogGroup" ServiceRegistries:
}, - RegistryArn:
"SimpleService": { Fn::GetAtt:
"DependsOn": [ - SimpleServiceDiscoveryEntry
"SimpleTCP80Listener" - Arn
], Tags:
"Properties": { - Key: com.docker.compose.project
"Cluster": { Value: TestSimpleConvert
"Fn::GetAtt": [ - Key: com.docker.compose.service
"Cluster", Value: simple
"Arn" TaskDefinition:
] Ref: SimpleTaskDefinition
}, Type: AWS::ECS::Service
"DeploymentConfiguration": { SimpleServiceDiscoveryEntry:
"MaximumPercent": 200, Properties:
"MinimumHealthyPercent": 100 Description: '"simple" service discovery entry in Cloud Map'
}, DnsConfig:
"DeploymentController": { DnsRecords:
"Type": "ECS" - TTL: 60
}, Type: A
"DesiredCount": 1, RoutingPolicy: MULTIVALUE
"LaunchType": "FARGATE", HealthCheckCustomConfig:
"LoadBalancers": [ FailureThreshold: 1
{ Name: simple
"ContainerName": "simple", NamespaceId:
"ContainerPort": 80, Ref: CloudMap
"TargetGroupArn": { Type: AWS::ServiceDiscovery::Service
"Ref": "SimpleTCP80TargetGroup" SimpleTCP80Listener:
} Properties:
} DefaultActions:
], - ForwardConfig:
"NetworkConfiguration": { TargetGroups:
"AwsvpcConfiguration": { - TargetGroupArn:
"AssignPublicIp": "ENABLED", Ref: SimpleTCP80TargetGroup
"SecurityGroups": [ Type: forward
{ LoadBalancerArn:
"Ref": "DefaultNetwork" Ref: LoadBalancer
} Port: 80
], Protocol: HTTP
"Subnets": [ Type: AWS::ElasticLoadBalancingV2::Listener
"subnet1", SimpleTCP80TargetGroup:
"subnet2" Properties:
] Port: 80
} Protocol: HTTP
}, Tags:
"PlatformVersion": "1.4.0", - Key: com.docker.compose.project
"PropagateTags": "SERVICE", Value: TestSimpleConvert
"SchedulingStrategy": "REPLICA", TargetType: ip
"ServiceRegistries": [ VpcId: vpc-123
{ Type: AWS::ElasticLoadBalancingV2::TargetGroup
"RegistryArn": { SimpleTaskDefinition:
"Fn::GetAtt": [ Properties:
"SimpleServiceDiscoveryEntry", ContainerDefinitions:
"Arn" - Command:
] - .compute.internal
} - TestSimpleConvert.local
} Essential: false
], Image: docker/ecs-searchdomain-sidecar:1.0
"Tags": [ LogConfiguration:
{ LogDriver: awslogs
"Key": "com.docker.compose.project", Options:
"Value": "TestSimpleConvert" awslogs-group:
}, Ref: LogGroup
{ awslogs-region:
"Key": "com.docker.compose.service", Ref: AWS::Region
"Value": "simple" awslogs-stream-prefix: TestSimpleConvert
} Name: Simple_ResolvConf_InitContainer
], - DependsOn:
"TaskDefinition": { - Condition: SUCCESS
"Ref": "SimpleTaskDefinition" ContainerName: Simple_ResolvConf_InitContainer
} Essential: true
}, Image: nginx
"Type": "AWS::ECS::Service" LinuxParameters: {}
}, LogConfiguration:
"SimpleServiceDiscoveryEntry": { LogDriver: awslogs
"Properties": { Options:
"Description": "\"simple\" service discovery entry in Cloud Map", awslogs-group:
"DnsConfig": { Ref: LogGroup
"DnsRecords": [ awslogs-region:
{ Ref: AWS::Region
"TTL": 60, awslogs-stream-prefix: TestSimpleConvert
"Type": "A" Name: simple
} PortMappings:
], - ContainerPort: 80
"RoutingPolicy": "MULTIVALUE" HostPort: 80
}, Protocol: tcp
"HealthCheckCustomConfig": { Cpu: "256"
"FailureThreshold": 1 ExecutionRoleArn:
}, Ref: SimpleTaskExecutionRole
"Name": "simple", Family: TestSimpleConvert-simple
"NamespaceId": { Memory: "512"
"Ref": "CloudMap" NetworkMode: awsvpc
} RequiresCompatibilities:
}, - FARGATE
"Type": "AWS::ServiceDiscovery::Service" Type: AWS::ECS::TaskDefinition
}, SimpleTaskExecutionRole:
"SimpleTCP80Listener": { Properties:
"Properties": { AssumeRolePolicyDocument:
"DefaultActions": [ Statement:
{ - Action:
"ForwardConfig": { - sts:AssumeRole
"TargetGroups": [ Condition: {}
{ Effect: Allow
"TargetGroupArn": { Principal:
"Ref": "SimpleTCP80TargetGroup" Service: ecs-tasks.amazonaws.com
} Version: 2012-10-17
} ManagedPolicyArns:
] - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
}, - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
"Type": "forward" Tags:
} - Key: com.docker.compose.project
], Value: TestSimpleConvert
"LoadBalancerArn": { - Key: com.docker.compose.service
"Ref": "LoadBalancer" Value: simple
}, Type: AWS::IAM::Role
"Port": 80,
"Protocol": "HTTP"
},
"Type": "AWS::ElasticLoadBalancingV2::Listener"
},
"SimpleTCP80TargetGroup": {
"Properties": {
"Port": 80,
"Protocol": "HTTP",
"Tags": [
{
"Key": "com.docker.compose.project",
"Value": "TestSimpleConvert"
}
],
"TargetType": "ip",
"VpcId": "vpc-123"
},
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup"
},
"SimpleTaskDefinition": {
"Properties": {
"ContainerDefinitions": [
{
"Command": [
".compute.internal",
"TestSimpleConvert.local"
],
"Essential": "false",
"Image": "docker/ecs-searchdomain-sidecar:1.0",
"LogConfiguration": {
"LogDriver": "awslogs",
"Options": {
"awslogs-group": {
"Ref": "LogGroup"
},
"awslogs-region": {
"Ref": "AWS::Region"
},
"awslogs-stream-prefix": "TestSimpleConvert"
}
},
"Name": "Simple_ResolvConf_InitContainer"
},
{
"DependsOn": [
{
"Condition": "SUCCESS",
"ContainerName": "Simple_ResolvConf_InitContainer"
}
],
"Essential": true,
"Image": "nginx",
"LinuxParameters": {},
"LogConfiguration": {
"LogDriver": "awslogs",
"Options": {
"awslogs-group": {
"Ref": "LogGroup"
},
"awslogs-region": {
"Ref": "AWS::Region"
},
"awslogs-stream-prefix": "TestSimpleConvert"
}
},
"Name": "simple",
"PortMappings": [
{
"ContainerPort": 80,
"HostPort": 80,
"Protocol": "tcp"
}
]
}
],
"Cpu": "256",
"ExecutionRoleArn": {
"Ref": "SimpleTaskExecutionRole"
},
"Family": "TestSimpleConvert-simple",
"Memory": "512",
"NetworkMode": "awsvpc",
"RequiresCompatibilities": [
"FARGATE"
]
},
"Type": "AWS::ECS::TaskDefinition"
},
"SimpleTaskExecutionRole": {
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": [
"sts:AssumeRole"
],
"Condition": {},
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
],
"Tags": [
{
"Key": "com.docker.compose.project",
"Value": "TestSimpleConvert"
},
{
"Key": "com.docker.compose.service",
"Value": "simple"
}
]
},
"Type": "AWS::IAM::Role"
}
}
}

View File

@ -32,7 +32,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, detach b
return err return err
} }
template, err := b.Convert(ctx, project) template, err := b.Convert(ctx, project, "yaml")
if err != nil { if err != nil {
return err return err
} }

View File

@ -158,6 +158,6 @@ func (cs *composeService) Logs(ctx context.Context, project string, w io.Writer)
return errdefs.ErrNotImplemented return errdefs.ErrNotImplemented
} }
func (cs *composeService) Convert(ctx context.Context, project *types.Project) ([]byte, error) { func (cs *composeService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {
return nil, errdefs.ErrNotImplemented return nil, errdefs.ErrNotImplemented
} }