소스 검색

Adopt CloudFormation to create ECS app from compose.yaml

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 년 전
부모
커밋
b70f01d2f4

+ 29 - 2
ecs/cmd/main/main.go

@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+
 	"github.com/docker/cli/cli-plugins/manager"
 	"github.com/docker/cli/cli-plugins/plugin"
 	"github.com/docker/cli/cli/command"
@@ -70,6 +71,7 @@ func ComposeCommand(clusteropts *clusterOptions) *cobra.Command {
 	opts.AddFlags(cmd.Flags())
 
 	cmd.AddCommand(
+		ConvertCommand(clusteropts, opts),
 		UpCommand(clusteropts, opts),
 		DownCommand(clusteropts, opts),
 	)
@@ -87,6 +89,32 @@ func (o upOptions) LoadBalancerArn() *string {
 	return &o.loadBalancerArn
 }
 
+func ConvertCommand(clusteropts *clusterOptions, projectOpts *compose.ProjectOptions) *cobra.Command {
+	opts := upOptions{}
+	cmd := &cobra.Command{
+		Use: "convert",
+		RunE: compose.WithProject(projectOpts, func(project *compose.Project, args []string) error {
+			client, err := amazon.NewClient(clusteropts.profile, clusteropts.cluster, clusteropts.region)
+			if err != nil {
+				return err
+			}
+			template, err := client.Convert(project, opts.LoadBalancerArn())
+			if err != nil {
+				return err
+			}
+
+			j, err := template.JSON()
+			if err != nil {
+				fmt.Printf("Failed to generate JSON: %s\n", err)
+			} else {
+				fmt.Printf("%s\n", string(j))
+			}
+			return nil
+		}),
+	}
+	return cmd
+}
+
 func UpCommand(clusteropts *clusterOptions, projectOpts *compose.ProjectOptions) *cobra.Command {
 	opts := upOptions{}
 	cmd := &cobra.Command{
@@ -107,7 +135,6 @@ type downOptions struct {
 	KeepLoadBalancer bool
 }
 
-
 func DownCommand(clusteropts *clusterOptions, projectOpts *compose.ProjectOptions) *cobra.Command {
 	opts := downOptions{}
 	cmd := &cobra.Command{
@@ -122,4 +149,4 @@ func DownCommand(clusteropts *clusterOptions, projectOpts *compose.ProjectOption
 	}
 	cmd.Flags().BoolVar(&opts.KeepLoadBalancer, "keep-load-balancer", false, "Keep Load Balancer for further use")
 	return cmd
-}
+}

+ 1 - 0
ecs/go.mod

@@ -6,6 +6,7 @@ require (
 	github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
 	github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
 	github.com/aws/aws-sdk-go v1.28.9
+	github.com/awslabs/goformation/v4 v4.8.0
 	github.com/bitly/go-hostpool v0.1.0 // indirect
 	github.com/bitly/go-simplejson v0.5.0 // indirect
 	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect

+ 19 - 7
ecs/go.sum

@@ -21,6 +21,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/aws/aws-sdk-go v1.28.9 h1:grIuBQc+p3dTRXerh5+2OxSuWFi0iXuxbFdTSg0jaW0=
 github.com/aws/aws-sdk-go v1.28.9/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/awslabs/goformation/v4 v4.8.0 h1:UiUhyokRy3suEqBXTnipvY8klqY3Eyl4GCH17brraEc=
+github.com/awslabs/goformation/v4 v4.8.0/go.mod h1:GcJULxCJfloT+3pbqCluXftdEK2AD/UqpS3hkaaBntg=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -46,8 +48,6 @@ github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiK
 github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo=
 github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
 github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
-github.com/compose-spec/compose-go v0.0.0-20200131085702-0b38cc2d8e6b h1:VK0c2Hfrg9FHjvJpWfGwiHPP2UeU0QZ6/5/dN0ehbSQ=
-github.com/compose-spec/compose-go v0.0.0-20200131085702-0b38cc2d8e6b/go.mod h1:KoJjdV81vERSyYVuQD63nryyt8ZTlqTWe8JuJIMhRo4=
 github.com/compose-spec/compose-go v0.0.0-20200409090215-53c0040c9127 h1:mAsQN3s19glh3KBOQjiRYBhqaX1SdzNqhB3/cuqgSbE=
 github.com/compose-spec/compose-go v0.0.0-20200409090215-53c0040c9127/go.mod h1:1PUpzRF1O/65VOqXZuwpCuYY7pJxbIq1jbAvAf62FGM=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
@@ -139,7 +139,9 @@ github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@@ -180,7 +182,6 @@ github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
 github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
 github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -205,8 +206,12 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P
 github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
+github.com/onsi/ginkgo v1.5.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.2.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -244,12 +249,14 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
 github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
 github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA=
+github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY=
+github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522 h1:fOCp11H0yuyAt2wqlbJtbyPzSgaxHTv8uN1pMpkG1t8=
+github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522/go.mod h1:tQTYKOQgxoH3v6dEmdHiz4JG+nbxWwM5fgPQUpSZqVQ=
 github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
 github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
 github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
@@ -285,9 +292,12 @@ github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg
 github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
+github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
 github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
@@ -327,6 +337,8 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYR
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -385,10 +397,12 @@ gopkg.in/dancannon/gorethink.v3 v3.0.5 h1:/g7PWP7zUS6vSNmHSDbjCHQh1Rqn8Jy6zSMQxA
 gopkg.in/dancannon/gorethink.v3 v3.0.5/go.mod h1:GXsi1e3N2OcKhcP6nsYABTiUejbWMFO4GY5a4pEaeEc=
 gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
 gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
 gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU=
 gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -396,8 +410,6 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
-gotest.tools/v3 v3.0.0 h1:d+tVGRu6X0ZBQ+kyAR8JKi6AXhTP2gmQaoIYaGFz634=
-gotest.tools/v3 v3.0.0/go.mod h1:TUP+/YtXl/dp++T+SZ5v2zUmLVBHmptSb/ajDLCJ+3c=
 gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 16 - 17
ecs/pkg/amazon/client.go

@@ -18,7 +18,6 @@ import (
 	"github.com/docker/ecs-plugin/pkg/compose"
 )
 
-
 const (
 	ProjectTag = "com.docker.compose.project"
 )
@@ -35,27 +34,27 @@ func NewClient(profile string, cluster string, region string) (compose.API, erro
 	}
 	return &client{
 		Cluster: cluster,
-		Region: region,
-		sess: sess,
-		ECS: ecs.New(sess),
-		EC2:  ec2.New(sess),
-		ELB: elbv2.New(sess),
-		CW: cloudwatchlogs.New(sess),
-		IAM: iam.New(sess),
-		CF: cloudformation.New(sess),
+		Region:  region,
+		sess:    sess,
+		ECS:     ecs.New(sess),
+		EC2:     ec2.New(sess),
+		ELB:     elbv2.New(sess),
+		CW:      cloudwatchlogs.New(sess),
+		IAM:     iam.New(sess),
+		CF:      cloudformation.New(sess),
 	}, nil
 }
 
 type client struct {
 	Cluster string
-	Region string
-	sess *session.Session
-	ECS ecsiface.ECSAPI
-	EC2 ec2iface.EC2API
-	ELB elbv2iface.ELBV2API
-	CW cloudwatchlogsiface.CloudWatchLogsAPI
-	IAM iamiface.IAMAPI
-	CF cloudformationiface.CloudFormationAPI
+	Region  string
+	sess    *session.Session
+	ECS     ecsiface.ECSAPI
+	EC2     ec2iface.EC2API
+	ELB     elbv2iface.ELBV2API
+	CW      cloudwatchlogsiface.CloudWatchLogsAPI
+	IAM     iamiface.IAMAPI
+	CF      cloudformationiface.CloudFormationAPI
 }
 
 var _ compose.API = &client{}

+ 80 - 0
ecs/pkg/amazon/cloudformation.go

@@ -1 +1,81 @@
 package amazon
+
+import (
+	"fmt"
+	"strings"
+
+	ecsapi "github.com/aws/aws-sdk-go/service/ecs"
+	"github.com/awslabs/goformation/v4/cloudformation"
+	"github.com/awslabs/goformation/v4/cloudformation/ec2"
+	"github.com/awslabs/goformation/v4/cloudformation/ecs"
+	"github.com/docker/ecs-plugin/pkg/compose"
+	"github.com/docker/ecs-plugin/pkg/convert"
+)
+
+func (c client) Convert(project *compose.Project, loadBalancerArn *string) (*cloudformation.Template, error) {
+	template := cloudformation.NewTemplate()
+
+	vpc, err := c.GetDefaultVPC()
+	if err != nil {
+		return nil, err
+	}
+
+	subnets, err := c.GetSubNets(vpc)
+	if err != nil {
+		return nil, err
+	}
+
+	var ingresses = []ec2.SecurityGroup_Ingress{}
+	for _, service := range project.Services {
+		for _, port := range service.Ports {
+			ingresses = append(ingresses, ec2.SecurityGroup_Ingress{
+				CidrIp:      "0.0.0.0/0",
+				Description: fmt.Sprintf("%d/%s", port.Target, port.Protocol),
+				FromPort:    int(port.Target),
+				IpProtocol:  strings.ToUpper(port.Protocol),
+				ToPort:      int(port.Target),
+			})
+		}
+	}
+
+	securityGroup := fmt.Sprintf("%s Security Group", project.Name)
+	template.Resources["SecurityGroup"] = &ec2.SecurityGroup{
+		GroupDescription:     securityGroup,
+		GroupName:            securityGroup,
+		SecurityGroupIngress: ingresses,
+		VpcId:                *vpc,
+	}
+
+	for _, service := range project.Services {
+		definition, err := convert.Convert(project, service)
+		if err != nil {
+			return nil, err
+		}
+
+		role, err := c.GetEcsTaskExecutionRole(service)
+		if err != nil {
+			return nil, err
+		}
+		definition.TaskRoleArn = *role
+
+		taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name)
+		template.Resources[taskDefinition] = definition
+
+		template.Resources[service.Name] = &ecs.Service{
+			Cluster:      c.Cluster,
+			DesiredCount: 1,
+			LaunchType:   ecsapi.LaunchTypeFargate,
+			NetworkConfiguration: &ecs.Service_NetworkConfiguration{
+				AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
+					AssignPublicIp: ecsapi.AssignPublicIpEnabled,
+					SecurityGroups: []string{cloudformation.Ref("SecurityGroup")},
+					Subnets:        subnets,
+				},
+			},
+			SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
+			ServiceName:        service.Name,
+			TaskDefinition:     cloudformation.Ref(taskDefinition),
+		}
+	}
+	return template, nil
+}

+ 4 - 50
ecs/pkg/amazon/down.go

@@ -1,64 +1,18 @@
 package amazon
 
 import (
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/service/ec2"
-	"github.com/aws/aws-sdk-go/service/ecs"
+	"github.com/aws/aws-sdk-go/service/cloudformation"
 	"github.com/docker/ecs-plugin/pkg/compose"
-	"github.com/sirupsen/logrus"
 )
 
 func (c *client) ComposeDown(project *compose.Project, keepLoadBalancer bool) error {
-	err := c.DeleteLoadBalancer(project, keepLoadBalancer)
-	if err != nil {
-		return err
-	}
-
-	services := []*string{}
-	// FIXME we should be able to retrieve services by tags, so we don't need the initial compose file to run "down"
-	for _, service := range project.Services {
-		logrus.Debugf("Deleting service %q\n", service.Name)
-		out, err := c.ECS.DeleteService(&ecs.DeleteServiceInput{
-			// Force to true so that we don't have to scale down to 0
-			// before deleting
-			Force:   aws.Bool(true),
-			Cluster: aws.String(c.Cluster),
-			Service: aws.String(service.Name),
-		})
-		if err != nil {
-			return err
-		}
-		services = append(services, out.Service.ServiceArn)
-	}
-
-	logrus.Info("Stopping services...")
-	err = c.ECS.WaitUntilServicesInactive(&ecs.DescribeServicesInput{
-		Services: services,
+	_, err := c.CF.DeleteStack(&cloudformation.DeleteStackInput{
+		StackName: &project.Name,
 	})
 	if err != nil {
 		return err
 	}
-	logrus.Info("All services stopped")
 
-	logrus.Debug("Deleting security groups")
-	groups, err := c.EC2.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{
-		Filters: []*ec2.Filter{
-			{
-				Name:   aws.String("tag:" + ProjectTag),
-				Values: aws.StringSlice([]string{project.Name}),
-			},
-		},
-	})
-	if err != nil {
-		return err
-	}
-	for _, g := range groups.SecurityGroups {
-		_, err = c.EC2.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{
-			GroupId: g.GroupId,
-		})
-		if err != nil {
-			return err
-		}
-	}
+	// TODO monitor progress
 	return nil
 }

+ 0 - 1
ecs/pkg/amazon/ecs.go

@@ -5,7 +5,6 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
-
 func (c client) RegisterTaskDefinition(task *ecs.RegisterTaskDefinitionInput) (*string, error) {
 	logrus.Debug("Register Task Definition")
 	def, err := c.ECS.RegisterTaskDefinition(task)

+ 3 - 2
ecs/pkg/amazon/loadBalancer.go

@@ -2,9 +2,10 @@ package amazon
 
 import (
 	"fmt"
+	"strings"
+
 	"github.com/docker/ecs-plugin/pkg/compose"
 	"github.com/sirupsen/logrus"
-	"strings"
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/elbv2"
@@ -132,4 +133,4 @@ func (c client) DeleteListeners(loadBalancer *string) error {
 		}
 	}
 	return nil
-}
+}

+ 1 - 0
ecs/pkg/amazon/logs.go

@@ -2,6 +2,7 @@ package amazon
 
 import (
 	"fmt"
+
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
 	"github.com/docker/ecs-plugin/pkg/compose"

+ 6 - 7
ecs/pkg/amazon/network.go

@@ -2,12 +2,13 @@ package amazon
 
 import (
 	"fmt"
+	"strings"
+
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/ec2"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/ecs-plugin/pkg/compose"
 	"github.com/sirupsen/logrus"
-	"strings"
 )
 
 // GetDefaultVPC retrieve the default VPC for AWS account
@@ -30,9 +31,8 @@ func (c client) GetDefaultVPC() (*string, error) {
 	return vpcs.Vpcs[0].VpcId, nil
 }
 
-
 // GetSubNets retrieve default subnets for a VPC
-func (c client) GetSubNets(vpc *string) ([]*string, error) {
+func (c client) GetSubNets(vpc *string) ([]string, error) {
 	logrus.Debug("Retrieve SubNets")
 	subnets, err := c.EC2.DescribeSubnets(&ec2.DescribeSubnetsInput{
 		DryRun: nil,
@@ -51,9 +51,9 @@ func (c client) GetSubNets(vpc *string) ([]*string, error) {
 		return nil, err
 	}
 
-	ids := []*string{}
+	ids := []string{}
 	for _, subnet := range subnets.Subnets {
-		ids = append(ids, subnet.SubnetId)
+		ids = append(ids, *subnet.SubnetId)
 	}
 	return ids, nil
 }
@@ -91,7 +91,6 @@ func (c client) CreateSecurityGroup(project *compose.Project, vpc *string) (*str
 	return securityGroup.GroupId, nil
 }
 
-
 func (c *client) ExposePort(securityGroup *string, port types.ServicePortConfig) error {
 	logrus.Debugf("Authorize ingress port %d/%s\n", port.Published, port.Protocol)
 	_, err := c.EC2.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
@@ -110,4 +109,4 @@ func (c *client) ExposePort(securityGroup *string, port types.ServicePortConfig)
 		},
 	})
 	return err
-}
+}

+ 2 - 1
ecs/pkg/amazon/roles.go

@@ -2,6 +2,7 @@ package amazon
 
 import (
 	"fmt"
+
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/iam"
 	"github.com/compose-spec/compose-go/types"
@@ -13,7 +14,7 @@ const ECSTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTa
 var defaultTaskExecutionRole *string
 
 // GetEcsTaskExecutionRole retrieve the role ARN to apply for task execution
-func (c client) GetEcsTaskExecutionRole(spec *types.ServiceConfig) (*string, error) {
+func (c client) GetEcsTaskExecutionRole(spec types.ServiceConfig) (*string, error) {
 	if arn, ok := spec.Extras["x-ecs-TaskExecutionRole"]; ok {
 		s := arn.(string)
 		return &s, nil

+ 25 - 99
ecs/pkg/amazon/up.go

@@ -2,128 +2,54 @@ package amazon
 
 import (
 	"fmt"
+	"os"
+
 	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/service/ecs"
-	"github.com/compose-spec/compose-go/types"
+	"github.com/aws/aws-sdk-go/service/cloudformation"
 	"github.com/docker/ecs-plugin/pkg/compose"
-	"github.com/docker/ecs-plugin/pkg/convert"
-	"github.com/sirupsen/logrus"
 )
 
 func (c *client) ComposeUp(project *compose.Project, loadBalancerArn *string) error {
-	type mapping struct {
-		service *types.ServiceConfig
-		task *ecs.RegisterTaskDefinitionInput
-	}
-	mappings := []mapping{}
-	for _, service := range project.Services {
-		task, err := convert.Convert(project, service)
-		if err != nil {
-			return err
-		}
-		mappings = append(mappings, mapping{
-			service: &service,
-			task:    task,
-		})
-	}
-
-	vpc, err := c.GetDefaultVPC()
-	if err != nil {
-		return err
-	}
-	subnets, err := c.GetSubNets(vpc)
+	template, err := c.Convert(project, loadBalancerArn)
 	if err != nil {
 		return err
 	}
 
-	securityGroup, err := c.CreateSecurityGroup(project, vpc)
+	json, err := template.JSON()
 	if err != nil {
 		return err
 	}
 
-	if loadBalancerArn == nil {
-		loadBalancerArn, err = c.CreateLoadBalancer(project, subnets)
-		if err != nil {
-			return err
-		}
-	}
-
-	logGroup, err := c.GetOrCreateLogGroup(project)
+	_, err = c.CF.ValidateTemplate(&cloudformation.ValidateTemplateInput{
+		TemplateBody: aws.String(string(json)),
+	})
 	if err != nil {
 		return err
 	}
 
-	for _, mapping := range mappings {
-		ingress := []*ecs.LoadBalancer{}
-		for _, port := range mapping.service.Ports {
-			name := fmt.Sprintf("%s-%s-%d-%s", project.Name, mapping.service.Name, port.Target, port.Protocol)
-			targetgroup, err := c.CreateTargetGroup(name, vpc, port)
-			if err != nil {
-				return err
-			}
-			ingress = append(ingress, &ecs.LoadBalancer{
-				ContainerName:  aws.String(mapping.service.Name),
-				ContainerPort:  aws.Int64(int64(port.Target)),
-				TargetGroupArn: targetgroup,
-			})
-
-			err = c.CreateListener(port, loadBalancerArn, targetgroup)
-			if err != nil {
-				return err
-			}
-		}
-
-		_, err = c.CreateService(project, mapping.service, mapping.task, securityGroup, subnets, logGroup, ingress)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (c *client) CreateService(project *compose.Project, service *types.ServiceConfig, task *ecs.RegisterTaskDefinitionInput, securityGroup *string, subnets []*string, logGroup *string, ingress []*ecs.LoadBalancer) (*string, error) {
-	role, err := c.GetEcsTaskExecutionRole(service)
+	_, err = c.CF.CreateStack(&cloudformation.CreateStackInput{
+		OnFailure:        aws.String("DELETE"),
+		StackName:        aws.String(project.Name),
+		TemplateBody:     aws.String(string(json)),
+		TimeoutInMinutes: aws.Int64(10),
+	})
 	if err != nil {
-		return nil, err
-	}
-
-	task.ExecutionRoleArn = role
-
-	for _, def := range task.ContainerDefinitions {
-		def.LogConfiguration.Options["awslogs-group"] = logGroup
-		def.LogConfiguration.Options["awslogs-stream-prefix"] = aws.String(service.Name)
-		def.LogConfiguration.Options["awslogs-region"] = aws.String(c.Region)
+		return err
 	}
 
-	arn, err := c.RegisterTaskDefinition(task)
+	events, err := c.CF.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{
+		StackName: aws.String(project.Name),
+	})
 	if err != nil {
-		return nil, err
+		return err
 	}
-
-	logrus.Debug("Create Service")
-	created, err := c.ECS.CreateService(&ecs.CreateServiceInput{
-		Cluster:      aws.String(c.Cluster),
-		DesiredCount: aws.Int64(1), // FIXME get from deploy options
-		LaunchType:   aws.String(ecs.LaunchTypeFargate), //FIXME use service.Isolation tro select EC2 vs Fargate
-		NetworkConfiguration: &ecs.NetworkConfiguration{
-			AwsvpcConfiguration: &ecs.AwsVpcConfiguration{
-				AssignPublicIp: aws.String(ecs.AssignPublicIpEnabled),
-				SecurityGroups: []*string{securityGroup},
-				Subnets:        subnets,
-			},
-		},
-		ServiceName:        aws.String(service.Name),
-		SchedulingStrategy: aws.String(ecs.SchedulingStrategyReplica),
-		TaskDefinition:     arn,
-		LoadBalancers: ingress,
-	})
-
-	for _, port := range service.Ports {
-		err = c.ExposePort(securityGroup, port)
-		if err != nil {
-			return nil, err
+	for _, event := range events.StackEvents {
+		fmt.Printf("%s %s\n", *event.LogicalResourceId, *event.ResourceStatus)
+		if *event.ResourceStatus == "CREATE_FAILED" {
+			fmt.Fprintln(os.Stderr, event.ResourceStatusReason)
 		}
 	}
 
-	return created.Service.ServiceArn, err
+	// TODO monitor progress
+	return nil
 }

+ 4 - 1
ecs/pkg/compose/api.go

@@ -1,6 +1,9 @@
 package compose
 
+import "github.com/awslabs/goformation/v4/cloudformation"
+
 type API interface {
+	Convert(project *Project, loadBalancerArn *string) (*cloudformation.Template, error)
 	ComposeUp(project *Project, loadBalancerArn *string) error
 	ComposeDown(project *Project, keepLoadBalancer bool) error
-}
+}

+ 0 - 1
ecs/pkg/compose/opts.go

@@ -15,7 +15,6 @@ func (o *ProjectOptions) AddFlags(flags *pflag.FlagSet) {
 	flags.StringVarP(&o.name, "project-name", "n", "", "Specify an alternate project name (default: directory name)")
 }
 
-
 type ProjectFunc func(project *Project, args []string) error
 
 // WithProject wrap a ProjectFunc into a cobra command

+ 4 - 4
ecs/pkg/compose/project.go

@@ -2,14 +2,15 @@ package compose
 
 import (
 	"fmt"
-	"github.com/compose-spec/compose-go/loader"
-	"github.com/compose-spec/compose-go/types"
-	"github.com/sirupsen/logrus"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"regexp"
 	"strings"
+
+	"github.com/compose-spec/compose-go/loader"
+	"github.com/compose-spec/compose-go/types"
+	"github.com/sirupsen/logrus"
 )
 
 type Project struct {
@@ -32,7 +33,6 @@ func NewProject(config types.ConfigDetails, name string) (*Project, error) {
 	return &p, nil
 }
 
-
 // projectFromOptions load a compose project based on command line options
 func projectFromOptions(options *ProjectOptions) (*Project, error) {
 	configPath, err := getConfigPathFromOptions(options)

+ 2 - 1
ecs/pkg/compose/project_test.go

@@ -1,9 +1,10 @@
 package compose
 
 import (
-	"gotest.tools/v3/assert"
 	"os"
 	"testing"
+
+	"gotest.tools/v3/assert"
 )
 
 func Test_project_name(t *testing.T) {

+ 114 - 164
ecs/pkg/convert/convert.go

@@ -1,118 +1,87 @@
 package convert
 
 import (
-	"github.com/docker/ecs-plugin/pkg/compose"
+	"strings"
 	"time"
 
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/service/ecs"
+	ecsapi "github.com/aws/aws-sdk-go/service/ecs"
+	"github.com/awslabs/goformation/v4/cloudformation/ecs"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/cli/opts"
+	"github.com/docker/ecs-plugin/pkg/compose"
 )
 
-func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.RegisterTaskDefinitionInput, error) {
+func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) {
 	_, err := toCPULimits(service)
 	if err != nil {
 		return nil, err
 	}
 
-	foo := int64(256)
-	logDriver := "awslogs" // FIXME could be set by service.Logging, especially to enable use of firelens
-	return &ecs.RegisterTaskDefinitionInput{
-		ContainerDefinitions: []*ecs.ContainerDefinition{
+	return &ecs.TaskDefinition{
+		ContainerDefinitions: []ecs.TaskDefinition_ContainerDefinition{
 			// Here we can declare sidecars and init-containers using https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definition_dependson
 			{
-				Command:               toStringPtrSlice(service.Command),
-				Cpu:                   &foo,
-				DependsOn:             nil,
-				DisableNetworking:     toBoolPtr(service.NetworkMode == "none"),
-				DnsSearchDomains:      toStringPtrSlice(service.DNSSearch),
-				DnsServers:            toStringPtrSlice(service.DNS),
-				DockerLabels:          nil,
-				DockerSecurityOptions: toStringPtrSlice(service.SecurityOpt),
-				EntryPoint:            toStringPtrSlice(service.Entrypoint),
-				Environment:           toKeyValuePairPtr(service.Environment),
-				Essential:             toBoolPtr(true),
-				ExtraHosts:            toHostEntryPtr(service.ExtraHosts),
-				FirelensConfiguration: nil,
-				HealthCheck:           toHealthCheck(service.HealthCheck),
-				Hostname:              toStringPtr(service.Hostname),
-				Image:                 toStringPtr(service.Image),
-				Interactive:           nil,
-				Links:                 nil,
-				LinuxParameters:       toLinuxParameters(service),
-				LogConfiguration: &ecs.LogConfiguration{
-					LogDriver:     &logDriver,
-					Options:       map[string]*string{},
-					SecretOptions: nil,
-				},
+				Command:                service.Command,
+				Cpu:                    256,
+				DisableNetworking:      service.NetworkMode == "none",
+				DnsSearchDomains:       service.DNSSearch,
+				DnsServers:             service.DNS,
+				DockerLabels:           nil,
+				DockerSecurityOptions:  service.SecurityOpt,
+				EntryPoint:             service.Entrypoint,
+				Environment:            toKeyValuePair(service.Environment),
+				Essential:              true,
+				ExtraHosts:             toHostEntryPtr(service.ExtraHosts),
+				FirelensConfiguration:  nil,
+				HealthCheck:            toHealthCheck(service.HealthCheck),
+				Hostname:               service.Hostname,
+				Image:                  service.Image,
+				Interactive:            false,
+				Links:                  nil,
+				LinuxParameters:        toLinuxParameters(service),
 				Memory:                 toMemoryLimits(service.Deploy),
 				MemoryReservation:      toMemoryReservation(service.Deploy),
 				MountPoints:            nil,
-				Name:                   toStringPtr(service.Name),
+				Name:                   service.Name,
 				PortMappings:           toPortMappings(service.Ports),
-				Privileged:             toBoolPtr(service.Privileged),
-				PseudoTerminal:         toBoolPtr(service.Tty),
-				ReadonlyRootFilesystem: toBoolPtr(service.ReadOnly),
+				Privileged:             service.Privileged,
+				PseudoTerminal:         service.Tty,
+				ReadonlyRootFilesystem: service.ReadOnly,
 				RepositoryCredentials:  nil,
 				ResourceRequirements:   nil,
 				Secrets:                nil,
-				StartTimeout:           nil,
-				StopTimeout:            durationToInt64Ptr(service.StopGracePeriod),
+				StartTimeout:           0,
+				StopTimeout:            durationToInt(service.StopGracePeriod),
 				SystemControls:         nil,
 				Ulimits:                toUlimits(service.Ulimits),
-				User:                   toStringPtr(service.User),
+				User:                   service.User,
 				VolumesFrom:            nil,
-				WorkingDirectory:       toStringPtr(service.WorkingDir),
+				WorkingDirectory:       service.WorkingDir,
 			},
 		},
 		Cpu:                     toCPU(service),
-		ExecutionRoleArn:        nil,
-		Family:                  toStringPtr(project.Name),
-		IpcMode:                 toStringPtr(service.Ipc),
+		Family:                  project.Name,
+		IpcMode:                 service.Ipc,
 		Memory:                  toMemory(service),
-		NetworkMode:             toStringPtr("awsvpc"), // FIXME could be set by service.NetworkMode, Fargate only supports network mode ‘awsvpc’.
-		PidMode:                 toStringPtr(service.Pid),
+		NetworkMode:             ecsapi.NetworkModeAwsvpc, // FIXME could be set by service.NetworkMode, Fargate only supports network mode ‘awsvpc’.
+		PidMode:                 service.Pid,
 		PlacementConstraints:    toPlacementConstraints(service.Deploy),
 		ProxyConfiguration:      nil,
-		RequiresCompatibilities: toRequiresCompatibilities(ecs.LaunchTypeFargate),
+		RequiresCompatibilities: []string{ecsapi.LaunchTypeFargate},
 		Tags:                    nil,
-		Volumes: []*ecs.Volume{
-			{
-				/* ONLY supported when using EC2 launch type
-				DockerVolumeConfiguration: {
-					Autoprovision: nil,
-					Driver:        nil,
-					DriverOpts:    nil,
-					Labels:        nil,
-					Scope:         nil,
-				}, */
-				/* Beta and ONLY supported when using EC2 launch type
-				EfsVolumeConfiguration: {
-					FileSystemId:  nil,
-					RootDirectory: nil,
-				}, */
-				/* Bind mount host volume
-				Host:                      {
-						SourcePath:
-				}, */
-				Name: aws.String("MyVolume"),
-			},
-		},
+		Volumes:                 []ecs.TaskDefinition_Volume{},
 	}, nil
 
 }
 
-func toCPU(service types.ServiceConfig) *string {
+func toCPU(service types.ServiceConfig) string {
 	// FIXME based on service's memory/cpu requirements, select the adequate Fargate CPU
-	v := "256"
-	return &v
+	return "256"
 }
 
-func toMemory(service types.ServiceConfig) *string {
+func toMemory(service types.ServiceConfig) string {
 	// FIXME based on service's memory/cpu requirements, select the adequate Fargate CPU
-	v := "512"
-	return &v
+	return "512"
 }
 
 func toCPULimits(service types.ServiceConfig) (*int64, error) {
@@ -153,45 +122,45 @@ func hasMemoryOrMemoryReservation(service types.ServiceConfig) bool {
 	return false
 }
 
-func toPlacementConstraints(deploy *types.DeployConfig) []*ecs.TaskDefinitionPlacementConstraint {
+func toPlacementConstraints(deploy *types.DeployConfig) []ecs.TaskDefinition_TaskDefinitionPlacementConstraint {
 	if deploy == nil || deploy.Placement.Constraints == nil || len(deploy.Placement.Constraints) == 0 {
 		return nil
 	}
-	pl := []*ecs.TaskDefinitionPlacementConstraint{}
+	pl := []ecs.TaskDefinition_TaskDefinitionPlacementConstraint{}
 	for _, c := range deploy.Placement.Constraints {
-		pl = append(pl, &ecs.TaskDefinitionPlacementConstraint{
-			Expression: toStringPtr(c),
-			Type:       nil,
+		pl = append(pl, ecs.TaskDefinition_TaskDefinitionPlacementConstraint{
+			Expression: c,
+			Type:       "",
 		})
 	}
 	return pl
 }
 
-func toPortMappings(ports []types.ServicePortConfig) []*ecs.PortMapping {
+func toPortMappings(ports []types.ServicePortConfig) []ecs.TaskDefinition_PortMapping {
 	if len(ports) == 0 {
 		return nil
 	}
-	m := []*ecs.PortMapping{}
+	m := []ecs.TaskDefinition_PortMapping{}
 	for _, p := range ports {
-		m = append(m, &ecs.PortMapping{
-			ContainerPort: uint32Toint64Ptr(p.Target),
-			HostPort:      uint32Toint64Ptr(p.Published),
-			Protocol:      toStringPtr(p.Protocol),
+		m = append(m, ecs.TaskDefinition_PortMapping{
+			ContainerPort: int(p.Target),
+			HostPort:      int(p.Published),
+			Protocol:      p.Protocol,
 		})
 	}
 	return m
 }
 
-func toUlimits(ulimits map[string]*types.UlimitsConfig) []*ecs.Ulimit {
+func toUlimits(ulimits map[string]*types.UlimitsConfig) []ecs.TaskDefinition_Ulimit {
 	if len(ulimits) == 0 {
 		return nil
 	}
-	u := []*ecs.Ulimit{}
+	u := []ecs.TaskDefinition_Ulimit{}
 	for k, v := range ulimits {
-		u = append(u, &ecs.Ulimit{
-			Name:      toStringPtr(k),
-			SoftLimit: intToInt64Ptr(v.Soft),
-			HardLimit: intToInt64Ptr(v.Hard),
+		u = append(u, ecs.TaskDefinition_Ulimit{
+			Name:      k,
+			SoftLimit: v.Soft,
+			HardLimit: v.Hard,
 		})
 	}
 	return u
@@ -209,79 +178,82 @@ func intToInt64Ptr(i int) *int64 {
 
 const Mb = 1024 * 1024
 
-func toMemoryLimits(deploy *types.DeployConfig) *int64 {
+func toMemoryLimits(deploy *types.DeployConfig) int {
 	if deploy == nil {
-		return nil
+		return 0
 	}
 	res := deploy.Resources.Limits
 	if res == nil {
-		return nil
+		return 0
 	}
-	v := int64(res.MemoryBytes) / Mb
-	return &v
+	v := int(res.MemoryBytes) / Mb
+	return v
 }
 
-func toMemoryReservation(deploy *types.DeployConfig) *int64 {
+func toMemoryReservation(deploy *types.DeployConfig) int {
 	if deploy == nil {
-		return nil
+		return 0
 	}
 	res := deploy.Resources.Reservations
 	if res == nil {
-		return nil
+		return 0
 	}
-	v := int64(res.MemoryBytes) / Mb
-	return &v
+	v := int(res.MemoryBytes) / Mb
+	return v
 }
 
-func toLinuxParameters(service types.ServiceConfig) *ecs.LinuxParameters {
-	return &ecs.LinuxParameters{
+func toLinuxParameters(service types.ServiceConfig) *ecs.TaskDefinition_LinuxParameters {
+	return &ecs.TaskDefinition_LinuxParameters{
 		Capabilities:       toKernelCapabilities(service.CapAdd, service.CapDrop),
 		Devices:            nil,
-		InitProcessEnabled: service.Init,
-		MaxSwap:            nil,
+		InitProcessEnabled: service.Init != nil && *service.Init,
+		MaxSwap:            0,
 		// FIXME SharedMemorySize:   service.ShmSize,
-		Swappiness: nil,
+		Swappiness: 0,
 		Tmpfs:      toTmpfs(service.Tmpfs),
 	}
 }
 
-func toTmpfs(tmpfs types.StringList) []*ecs.Tmpfs {
+func toTmpfs(tmpfs types.StringList) []ecs.TaskDefinition_Tmpfs {
 	if tmpfs == nil || len(tmpfs) == 0 {
 		return nil
 	}
-	o := []*ecs.Tmpfs{}
-	for _, t := range tmpfs {
-		path := t
-		o = append(o, &ecs.Tmpfs{
-			ContainerPath: &path,
+	o := []ecs.TaskDefinition_Tmpfs{}
+	for _, path := range tmpfs {
+		o = append(o, ecs.TaskDefinition_Tmpfs{
+			ContainerPath: path,
 			MountOptions:  nil,
-			Size:          nil,
+			Size:          0,
 		})
 	}
 	return o
 }
 
-func toKernelCapabilities(add []string, drop []string) *ecs.KernelCapabilities {
+func toKernelCapabilities(add []string, drop []string) *ecs.TaskDefinition_KernelCapabilities {
 	if len(add) == 0 && len(drop) == 0 {
 		return nil
 	}
-	return &ecs.KernelCapabilities{
-		Add:  toStringPtrSlice(add),
-		Drop: toStringPtrSlice(drop),
+	return &ecs.TaskDefinition_KernelCapabilities{
+		Add:  add,
+		Drop: drop,
 	}
 
 }
 
-func toHealthCheck(check *types.HealthCheckConfig) *ecs.HealthCheck {
+func toHealthCheck(check *types.HealthCheckConfig) *ecs.TaskDefinition_HealthCheck {
 	if check == nil {
 		return nil
 	}
-	return &ecs.HealthCheck{
-		Command:     toStringPtrSlice(check.Test),
-		Interval:    durationToInt64Ptr(check.Interval),
-		Retries:     uint64ToInt64Ptr(check.Retries),
-		StartPeriod: durationToInt64Ptr(check.StartPeriod),
-		Timeout:     durationToInt64Ptr(check.Timeout),
+	retries := 0
+	if check.Retries != nil {
+		retries = int(*check.Retries)
+	}
+	return &ecs.TaskDefinition_HealthCheck{
+		Command:     check.Test,
+		Interval:    durationToInt(check.Interval),
+		Retries:     retries,
+		StartPeriod: durationToInt(check.StartPeriod),
+		Timeout:     durationToInt(check.Timeout),
 	}
 }
 
@@ -293,66 +265,44 @@ func uint64ToInt64Ptr(i *uint64) *int64 {
 	return &v
 }
 
-func durationToInt64Ptr(interval *types.Duration) *int64 {
+func durationToInt(interval *types.Duration) int {
 	if interval == nil {
-		return nil
+		return 0
 	}
-	v := int64(time.Duration(*interval).Seconds())
-	return &v
+	v := int(time.Duration(*interval).Seconds())
+	return v
 }
 
-func toHostEntryPtr(hosts types.HostsList) []*ecs.HostEntry {
+func toHostEntryPtr(hosts types.HostsList) []ecs.TaskDefinition_HostEntry {
 	if hosts == nil || len(hosts) == 0 {
 		return nil
 	}
-	e := []*ecs.HostEntry{}
+	e := []ecs.TaskDefinition_HostEntry{}
 	for _, h := range hosts {
-		host := h
-		e = append(e, &ecs.HostEntry{
-			Hostname: &host,
+		parts := strings.SplitN(h, ":", 2) // FIXME this should be handled by compose-go
+		e = append(e, ecs.TaskDefinition_HostEntry{
+			Hostname:  parts[0],
+			IpAddress: parts[1],
 		})
 	}
 	return e
 }
 
-func toKeyValuePairPtr(environment types.MappingWithEquals) []*ecs.KeyValuePair {
+func toKeyValuePair(environment types.MappingWithEquals) []ecs.TaskDefinition_KeyValuePair {
 	if environment == nil || len(environment) == 0 {
 		return nil
 	}
-	pairs := []*ecs.KeyValuePair{}
+	pairs := []ecs.TaskDefinition_KeyValuePair{}
 	for k, v := range environment {
 		name := k
-		value := v
-		pairs = append(pairs, &ecs.KeyValuePair{
-			Name:  &name,
+		var value string
+		if v != nil {
+			value = *v
+		}
+		pairs = append(pairs, ecs.TaskDefinition_KeyValuePair{
+			Name:  name,
 			Value: value,
 		})
 	}
 	return pairs
 }
-
-func toStringPtr(s string) *string {
-	if s == "" {
-		return nil
-	}
-	return &s
-}
-
-func toStringPtrSlice(s []string) []*string {
-	if len(s) == 0 {
-		return nil
-	}
-	v := []*string{}
-	for _, x := range s {
-		value := x
-		v = append(v, &value)
-	}
-	return v
-}
-
-func toBoolPtr(b bool) *bool {
-	if !b {
-		return nil
-	}
-	return &b
-}