فهرست منبع

Merge pull request #882 from docker/yaml

Marshall cloudformation template as yaml
Guillaume Tardif 5 سال پیش
والد
کامیت
5c04e84e49

+ 1 - 1
aci/compose.go

@@ -129,6 +129,6 @@ func (cs *aciComposeService) Logs(ctx context.Context, project string, w io.Writ
 	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
 }

+ 1 - 1
api/client/compose.go

@@ -55,6 +55,6 @@ func (c *composeService) List(context.Context, string) ([]compose.Stack, error)
 }
 
 // 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
 }

+ 1 - 1
api/compose/api.go

@@ -36,7 +36,7 @@ type Service interface {
 	// List executes the equivalent to a `docker stack ls`
 	List(ctx context.Context, projectName string) ([]Stack, error)
 	// 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

+ 2 - 1
cli/cmd/compose/convert.go

@@ -39,6 +39,7 @@ func convertCommand() *cobra.Command {
 	convertCmd.Flags().StringVar(&opts.WorkingDir, "workdir", "", "Work dir")
 	convertCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
 	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
 }
@@ -60,7 +61,7 @@ func runConvert(ctx context.Context, opts composeOptions) error {
 		return err
 	}
 
-	json, err = c.ComposeService().Convert(ctx, project)
+	json, err = c.ComposeService().Convert(ctx, project, opts.Format)
 	if err != nil {
 		return err
 	}

+ 2 - 2
ecs/cloudformation.go

@@ -37,13 +37,13 @@ import (
 	"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)
 	if err != nil {
 		return nil, err
 	}
 
-	return marshall(template)
+	return marshall(template, format)
 }
 
 func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*cloudformation.Template, error) {

+ 1 - 1
ecs/cloudformation_test.go

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

+ 2 - 2
ecs/local/compose.go

@@ -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)
 	}
 
-	converted, err := e.Convert(ctx, project)
+	converted, err := e.Convert(ctx, project, "yaml")
 	if err != nil {
 		return err
 	}
@@ -69,7 +69,7 @@ func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project, deta
 	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{
 		Driver: "bridge",
 		Ipam: types.IPAMConfig{

+ 31 - 14
ecs/marshall.go

@@ -22,29 +22,50 @@ import (
 	"strings"
 
 	"github.com/awslabs/goformation/v4/cloudformation"
+	"github.com/sanathkr/go-yaml"
 )
 
-func marshall(template *cloudformation.Template) ([]byte, error) {
-	raw, err := template.JSON()
+func marshall(template *cloudformation.Template, format string) ([]byte, error) {
+	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 {
 		return nil, err
 	}
 
 	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)
 	}
 
-	if input, ok := unmarshalled.(map[string]interface{}); ok {
+	if input, ok := unmarshalled.(map[interface{}]interface{}); ok {
 		if resources, ok := input["Resources"]; ok {
-			for _, uresource := range resources.(map[string]interface{}) {
-				if resource, ok := uresource.(map[string]interface{}); ok {
+			for _, uresource := range resources.(map[interface{}]interface{}) {
+				if resource, ok := uresource.(map[interface{}]interface{}); ok {
 					if resource["Type"] == "AWS::ECS::TaskDefinition" {
-						properties := resource["Properties"].(map[string]interface{})
+						properties := resource["Properties"].(map[interface{}]interface{})
 						for _, def := range properties["ContainerDefinitions"].([]interface{}) {
-							containerDefinition := def.(map[string]interface{})
+							containerDefinition := def.(map[interface{}]interface{})
 							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, "", "  ")
-	if err != nil {
-		return nil, fmt.Errorf("invalid JSON: %s", err)
-	}
-	return raw, err
+	return marshal(unmarshalled)
 }

+ 208 - 332
ecs/testdata/simple/simple-cloudformation-conversion.golden

@@ -1,332 +1,208 @@
-{
-  "AWSTemplateFormatVersion": "2010-09-09",
-  "Resources": {
-    "CloudMap": {
-      "Properties": {
-        "Description": "Service Map for Docker Compose project TestSimpleConvert",
-        "Name": "TestSimpleConvert.local",
-        "Vpc": "vpc-123"
-      },
-      "Type": "AWS::ServiceDiscovery::PrivateDnsNamespace"
-    },
-    "Cluster": {
-      "Properties": {
-        "ClusterName": "TestSimpleConvert",
-        "Tags": [
-          {
-            "Key": "com.docker.compose.project",
-            "Value": "TestSimpleConvert"
-          }
-        ]
-      },
-      "Type": "AWS::ECS::Cluster"
-    },
-    "Default80Ingress": {
-      "Properties": {
-        "CidrIp": "0.0.0.0/0",
-        "Description": "simple:80/tcp on default network",
-        "FromPort": 80,
-        "GroupId": {
-          "Ref": "DefaultNetwork"
-        },
-        "IpProtocol": "TCP",
-        "ToPort": 80
-      },
-      "Type": "AWS::EC2::SecurityGroupIngress"
-    },
-    "DefaultNetwork": {
-      "Properties": {
-        "GroupDescription": "TestSimpleConvert Security Group for default network",
-        "Tags": [
-          {
-            "Key": "com.docker.compose.project",
-            "Value": "TestSimpleConvert"
-          },
-          {
-            "Key": "com.docker.compose.network",
-            "Value": "default"
-          }
-        ],
-        "VpcId": "vpc-123"
-      },
-      "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"
-      },
-      "Type": "AWS::Logs::LogGroup"
-    },
-    "SimpleService": {
-      "DependsOn": [
-        "SimpleTCP80Listener"
-      ],
-      "Properties": {
-        "Cluster": {
-          "Fn::GetAtt": [
-            "Cluster",
-            "Arn"
-          ]
-        },
-        "DeploymentConfiguration": {
-          "MaximumPercent": 200,
-          "MinimumHealthyPercent": 100
-        },
-        "DeploymentController": {
-          "Type": "ECS"
-        },
-        "DesiredCount": 1,
-        "LaunchType": "FARGATE",
-        "LoadBalancers": [
-          {
-            "ContainerName": "simple",
-            "ContainerPort": 80,
-            "TargetGroupArn": {
-              "Ref": "SimpleTCP80TargetGroup"
-            }
-          }
-        ],
-        "NetworkConfiguration": {
-          "AwsvpcConfiguration": {
-            "AssignPublicIp": "ENABLED",
-            "SecurityGroups": [
-              {
-                "Ref": "DefaultNetwork"
-              }
-            ],
-            "Subnets": [
-              "subnet1",
-              "subnet2"
-            ]
-          }
-        },
-        "PlatformVersion": "1.4.0",
-        "PropagateTags": "SERVICE",
-        "SchedulingStrategy": "REPLICA",
-        "ServiceRegistries": [
-          {
-            "RegistryArn": {
-              "Fn::GetAtt": [
-                "SimpleServiceDiscoveryEntry",
-                "Arn"
-              ]
-            }
-          }
-        ],
-        "Tags": [
-          {
-            "Key": "com.docker.compose.project",
-            "Value": "TestSimpleConvert"
-          },
-          {
-            "Key": "com.docker.compose.service",
-            "Value": "simple"
-          }
-        ],
-        "TaskDefinition": {
-          "Ref": "SimpleTaskDefinition"
-        }
-      },
-      "Type": "AWS::ECS::Service"
-    },
-    "SimpleServiceDiscoveryEntry": {
-      "Properties": {
-        "Description": "\"simple\" service discovery entry in Cloud Map",
-        "DnsConfig": {
-          "DnsRecords": [
-            {
-              "TTL": 60,
-              "Type": "A"
-            }
-          ],
-          "RoutingPolicy": "MULTIVALUE"
-        },
-        "HealthCheckCustomConfig": {
-          "FailureThreshold": 1
-        },
-        "Name": "simple",
-        "NamespaceId": {
-          "Ref": "CloudMap"
-        }
-      },
-      "Type": "AWS::ServiceDiscovery::Service"
-    },
-    "SimpleTCP80Listener": {
-      "Properties": {
-        "DefaultActions": [
-          {
-            "ForwardConfig": {
-              "TargetGroups": [
-                {
-                  "TargetGroupArn": {
-                    "Ref": "SimpleTCP80TargetGroup"
-                  }
-                }
-              ]
-            },
-            "Type": "forward"
-          }
-        ],
-        "LoadBalancerArn": {
-          "Ref": "LoadBalancer"
-        },
-        "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"
-    }
-  }
-}
+AWSTemplateFormatVersion: 2010-09-09
+Resources:
+  CloudMap:
+    Properties:
+      Description: Service Map for Docker Compose project TestSimpleConvert
+      Name: TestSimpleConvert.local
+      Vpc: vpc-123
+    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
+  Cluster:
+    Properties:
+      ClusterName: TestSimpleConvert
+      Tags:
+      - Key: com.docker.compose.project
+        Value: TestSimpleConvert
+    Type: AWS::ECS::Cluster
+  Default80Ingress:
+    Properties:
+      CidrIp: 0.0.0.0/0
+      Description: simple:80/tcp on default network
+      FromPort: 80
+      GroupId:
+        Ref: DefaultNetwork
+      IpProtocol: TCP
+      ToPort: 80
+    Type: AWS::EC2::SecurityGroupIngress
+  DefaultNetwork:
+    Properties:
+      GroupDescription: TestSimpleConvert Security Group for default network
+      Tags:
+      - Key: com.docker.compose.project
+        Value: TestSimpleConvert
+      - Key: com.docker.compose.network
+        Value: default
+      VpcId: vpc-123
+    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
+    Type: AWS::Logs::LogGroup
+  SimpleService:
+    DependsOn:
+    - SimpleTCP80Listener
+    Properties:
+      Cluster:
+        Fn::GetAtt:
+        - Cluster
+        - Arn
+      DeploymentConfiguration:
+        MaximumPercent: 200
+        MinimumHealthyPercent: 100
+      DeploymentController:
+        Type: ECS
+      DesiredCount: 1
+      LaunchType: FARGATE
+      LoadBalancers:
+      - ContainerName: simple
+        ContainerPort: 80
+        TargetGroupArn:
+          Ref: SimpleTCP80TargetGroup
+      NetworkConfiguration:
+        AwsvpcConfiguration:
+          AssignPublicIp: ENABLED
+          SecurityGroups:
+          - Ref: DefaultNetwork
+          Subnets:
+          - subnet1
+          - subnet2
+      PlatformVersion: 1.4.0
+      PropagateTags: SERVICE
+      SchedulingStrategy: REPLICA
+      ServiceRegistries:
+      - RegistryArn:
+          Fn::GetAtt:
+          - SimpleServiceDiscoveryEntry
+          - Arn
+      Tags:
+      - Key: com.docker.compose.project
+        Value: TestSimpleConvert
+      - Key: com.docker.compose.service
+        Value: simple
+      TaskDefinition:
+        Ref: SimpleTaskDefinition
+    Type: AWS::ECS::Service
+  SimpleServiceDiscoveryEntry:
+    Properties:
+      Description: '"simple" service discovery entry in Cloud Map'
+      DnsConfig:
+        DnsRecords:
+        - TTL: 60
+          Type: A
+        RoutingPolicy: MULTIVALUE
+      HealthCheckCustomConfig:
+        FailureThreshold: 1
+      Name: simple
+      NamespaceId:
+        Ref: CloudMap
+    Type: AWS::ServiceDiscovery::Service
+  SimpleTCP80Listener:
+    Properties:
+      DefaultActions:
+      - ForwardConfig:
+          TargetGroups:
+          - TargetGroupArn:
+              Ref: SimpleTCP80TargetGroup
+        Type: forward
+      LoadBalancerArn:
+        Ref: LoadBalancer
+      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
+

+ 1 - 1
ecs/up.go

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

+ 1 - 1
example/backend.go

@@ -158,6 +158,6 @@ func (cs *composeService) Logs(ctx context.Context, project string, w io.Writer)
 	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
 }