Repository: docker/compose-cli
Pull request #701 (2020-09-30 12:37:51)

Introduce x-aws-autoscale to support CPU based autoscaling
**What I did**
Introduced `x-aws-autoscaling` and configure autoscaling policy with a CPU percent target


**Related issue**
https://github.com/docker/compose-cli/issues/668

/test-ecs

**(not mandatory) A picture of a cute animal, if possible in relation with what you did**
![image](https://user-images.githubusercontent.com/132757/94685842-48cf7f80-032a-11eb-964d-9122ae6fef4a.png)



Patch:
diff --git a/docs/aci-interation.md b/docs/aci-interation.md
deleted file mode 100644
index 5a03a9c3..00000000
--- a/docs/aci-interation.md
+++ /dev/null
@@ -1,130 +0,0 @@
-# Features
-
-## Login
-
-You can log into Azure with the `docker login azure` command. This will open a Web browser to complete the login process. If the Docker CLI cannot open a browser, it will fall back to the [Azure device code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code) and let you connect yourself.
-Note that the [Azure command line](https://docs.microsoft.com/en-us/cli/azure/) login is separated from the Docker CLI Azure login.
-
-## Docker contexts
-
-The Docker ACI integration is performed through the use of Docker contexts. You can create a context of type ACI, and then using this context you will be able to run classic Docker commands (like `docker run`) against ACI.
-Creating an ACI context requires an Azure subscription, a resource group and a region. 
-
-Before creating a context, you will need to `docker login azure` first so that the Docker CLI can find your Azure subscription.
-
-To create an ACI context, `docker context create aci` will prompt you for the relevant information, with the option to either create a new resource group or use an existing one.
-With a context created, you can either issue single commands against ACI, specifying the context name in the command line, like `docker --context acicontext run nginx`, or switch context so that all subsequent commands will be issued to ACI: `docker context use acicontext`.
-
-**Note:** You can have multiple ACI contexts associated each with different resource groups. This can be useful as it will act as namespaces for your containers. Actions on your containers will be restricted to your current context and resource group.
-
-## Single container
-
-You can start a single container on ACI with the `docker run` command. 
-The container is started in a container group, in the region and resource group associated with the ACI context.
-
-You can then list existing containers with `docker ps`. This will show the status of the container, if it exposes ports, and the container's public IP address or domain name.
-You can view a container's logs with the `docker logs` command.
-
-Running containers can be stopped with `docker stop` (`docker kill` will have the same effect). Stopped containers can be restarted with `docker start`.
- 
-**Note:** The semantics of restarting a container on ACI are different to those when using a moby type context for local development. On ACI, the container will be reset to its initial state and started on a new node. This includes the container's filesystem so all state that is not stored in a volume will be lost on restart.
-
-Containers can be removed using `docker rm`. Removing a running container will require using the `--force` flag, or stopping the container with `docker stop` before removing.   
-
-## Compose applications
-
-You can start Compose applications with the `docker compose up` command.
-All containers in the same Compose application are started in the same container group. Service discovery between the containers works using the service name specified in the Compose file.
-Name resolution is achieved by writing service names in the `/etc/hosts` file that is shared automatically by all containers in the container group.
-
-Containers started as part of Compose applications will be displayed along with single containers when using `docker ps`. 
-Their container ID will be of the format: `<COMPOSE-PROJECT>_<SERVICE>`. 
-These containers cannot be stopped, started or removed independently since they are all part of the same ACI container group. You can view each container's logs with `docker logs`.
-You can list deployed Compose applications with `docker compose ls`. This will list only compose applications, not single containers started with `docker run`.
-You can remove a Compose application with `docker compose down`.
-
-## Exposing ports
-
-Single containers and Compose applications can optionally expose ports. For single containers this is done using the `--publish` flag of the `docker run` command and for Compose applications this is done in the Compose file.
- 
-**Note:** ACI does not allow port mapping (i.e.: changing port number while exposing port), so the source and target ports must be the same when deploying to ACI.
-
-**Note:** All containers in the same Compose application are deployed in the same ACI container group. Containers in the same Compose application cannot expose the same port when deployed to ACI.
-
-By default, when exposing ports for your application, a random public IP address is associated with the container group supporting the deployed application (single container or Compose application).
-This IP address can be obtained when listing containers with `docker ps` or using `docker inspect`.    
-
-### DNS label name
-
-In addition to exposing ports on a random IP address, you can specify a DNS label name to expose your application on an FQDN of the form: `<NAME>.region.azurecontainer.io`.
-This name can be set with the `--domain` flag when performing a `docker run` or using the `domain` field in the Compose file when performing a `docker compose up`.
-**Note:** The domain of a Compose application can only be set once, if you specify `domain` for several services, the value must be identical.
-
-## Volumes
-
-Single containers and Compose applications can use volumes. In ACI, volumes are implemented as Azure file shares in Azure storage accounts.
-To use a volume when running a single container, specify a storage account and file share name like this: 
-
-```console
-docker run -v <STORAGE-ACCOUNT>/<FILE-SHARE>:/target/path[:ro]
-```
-  
-To specify more than one volume, just use the `-v` option as many times as required.
-Compose application volumes are specified in the _volumes_ section of the Compose file and attached to a service as follows:
-
-```yaml
-services:
-    myservice:
-        image: nginx
-        volumes:
-        - mydata:/mount/testvolumes
-
-volumes:
-  mydata:
-    driver: azure_file
-    driver_opts:
-      share_name: myfileshare
-      storage_account_name: mystorageaccount
-```
-
-**Note:** The volume short syntax in Compose files cannot be used as it is aimed at volume definition for local bind mounts. Using the volume driver and driver option syntax in Compose files makes the volume definition a lot more clear.
-
-In both cases, when deploying the containers, the Docker CLI will use your Azure login to fetch the key to the storage account, and provide this key with the container deployment information, so that the container can access the volume.
-Volumes can be used from any file share in any storage account you have access to with your Azure login. You can specify `rw` (read/write) or `ro` (read only) when mounting the volume (`rw` is the default).
-
-ACI volumes can be managed with the `volume` subcommand. This includes `docker volume ls`, `docker volume create`, and `docker volume rm`. 
-Creating a volume requires a storage account name and a volume name. The storage account is created using default options if it does not exist, using the region associated with your ACI context.  
-If the storage account name already exists (and you have access to it), a file share is created with default values, using this existing storage account. Volume creation fails if the file share already exists.
-
-Listing volumes will list all file shares you have access using your Azure login, in any storage account. You can use existing file shares as volumes, even if they have not been created through the Docker CLI.
-
-When using `docker volume rm`, the command will remove the specified file share from the specified storage account. If this storage account has no more file shares, and if the storage account was created by the Docker CLI, then the storage account will also be deleted.
-
-## Resource usage definition
-
-You can specify CPU and memory limits for your containers.
-For single containers, use `docker run --cpu 2 --memory 2G`. 
-In Compose files, you can use the service resource limits:
-
-```yaml
-services:
-  redis:
-    image: redis:alpine
-    deploy:
-      resources:
-        limits:
-          cpus: '0.50'
-          memory: 50M
-```
-
-In this general example, the _redis_ service is constrained to use no more than 50M of memory (50 MB) and 0.50 (50% of a single core) of available processing time (CPU).
-
-## Environment variables
-
-When using `docker run`, environment variables can be passed to ACI containers using the `--env` flag.
-Form compose applications, environment variables can be specified in the compose file with the `environment` or `env-file` service field, or with the `--environment` command line flag.
-
-## Private Docker Hub images and using the Azure Container Registry
-
-You can deploy private images to ACI that are hosted by any container registry. You need to `docker login` to the relevant registry before running `docker run` or `docker compose up`. The Docker CLI will fetch your registry login for the deployed images and send the credentials along with the image deployment information to ACI.
-In the case of the Azure Container Registry, the command line will try to automatically log you into ACR from your Azure login. You don't need to manually login to the ACR registry first, if your Azure login has access to the ACR.
diff --git a/ecs/architecture.md b/ecs/architecture.md
index f8561671..113c4350 100644
--- a/ecs/architecture.md
+++ b/ecs/architecture.md
@@ -8,38 +8,49 @@ This document describes the mapping between compose application model and AWS co
 This diagram shows compose model and on same line AWS components that get created as equivalent resources
 
 ```
-+----------+                                +-------------+                          +-------------------+
-| Project  |                                | Cluster     |                          | LoadBalancer      |
-+-+--------+                                +-------------+                          +-------------------+
++----------+                                +-------------+                              +-------------------+
+| Project  |  . . . . . . . . . . . . . .   | Cluster     |    . . . . . . .             | LoadBalancer      |
++-+--------+                                +-------------+                              +-------------------+
   |
-  |    +----------+                         +-------------+ +----------------+       +-------------------+
-  +----+ Service  |                         | Service     | | TaskDefinition |       | TargetGroup       |
-  |    +--+-------+                         +-------------+ +----------------+       +-------------------+
-  |       |                                                 +----------------+
-  |       |  x-aws-role, x-aws-policies                     | TaskRole       |
-  |       |                                                 +----------------+
-  |       |  +---------+                    +-------------+                          +-------------------+
-  |       +--+ Ports   |                    | IngressRule |                          | Listener          |
-  |       |  +---------+                    +-------------+                          +-------------------+
+  |    +----------+                         +-------------++-------------------+         +-------------------+  
+  +----+ Service  |   . . . . . . . . . .   | Service     || TaskDefinition    |         | TargetGroup       |  
+  |    +--+-------+                         +-------------++-------------------+-+       +-------------------+  
+  |       |                                                  | TaskRole          |                          
+  |       |                                                  +-------------------+-+                         
+  |       |  x-aws-role, x-aws-policies     . . . . . . . .    | TaskExecutionRole |                        
+  |       |                                                    +-------------------+                        
+  |       |  +---------+
+  |       +--+ Deploy  |
+  |       |  +---------+                    +-------------------+
+  |       |  x-aws-autoscale  . . . . . .   | ScalableTarget    |
+  |       |                                 +-------------------+---+
+  |       |                                     | ScalingPolicy     |
+  |       |                                     +-------------------+-+
+  |       |                                       | AutoScalingRole   |
+  |       |                                       +-------------------+
   |       |
+  |       |  +---------+                    +-------------+                              +-------------------+
+  |       +--+ Ports   |   . . . . . . .    | IngressRule +-----+                        | Listener          |
+  |       |  +---------+                    +-------------+     |                        +-------------------+
+  |       |                                                     |
   |       |  +---------+                    +---------------+ +------------------+
-  |       +--+ Secrets |                    | InitContainer | |TaskExecutionRole |
+  |       +--+ Secrets |   . . . . . . .    | InitContainer | |TaskExecutionRole |
   |       |  +---------+                    +---------------+ +------------+-----+
-  |       |                                                                |
-  |       |  +---------+                                                   |
-  |       +--+ Volumes |                                                   |
-  |       |  +---------+                                                   |
-  |       |                                                                |
-  |       |  +---------------+                                             |         +------------------------------------------+
-  |       +--+ DeviceRequest |                                             |         | CapacityProvider  || AutoscalingGroup    |
-  |          +---------------+                                             |         +------------------------------------------+
-  |                                                                        |                              | LaunchConfiguration |
-  |   +------------+                        +---------------+              |                              +---------------------+
-  +---+ Networks   |                        | SecurityGroup |              |
-  |   +------------+                        +---------------+              |
+  |       |                                                     |          |
+  |       |  +---------+                                        |          |
+  |       +--+ Volumes |                                        |          |
+  |       |  +---------+                                        |          |
+  |       |                                                     |          |
+  |       |  +---------------+                                  |          |         +-------------------+
+  |       +--+ DeviceRequest |  . . . . . . . . . . . . .  . .  | . . . .  | . . .   | CapacityProvider  |
+  |          +---------------+                                  |          |         +-------------------+--------+
+  |                                                             |          |                | AutoscalingGroup    |
+  |   +------------+                        +---------------+   |          |                +---------------------+
+  +---+ Networks   |   . . . . . . . . .    | SecurityGroup +---+          |                | LaunchConfiguration |
+  |   +------------+                        +---------------+              |                +---------------------+
   |                                                                        |
   |   +------------+                        +---------------+              |
-  +---+ Secret     |                        | Secret        +--------------+
+  +---+ Secret     |   . . . . . . . . .    | Secret        +--------------+
       +------------+                        +---------------+
 ```
 
@@ -63,6 +74,6 @@ A `TaskExecutionRole` is also created per service, and is updated to grant acces
 Services using a GPU (`DeviceRequest`) get the `Cluster` extended with an EC2 `CapacityProvider`, using an `AutoscalingGroup` to manage
 EC2 resources allocation based on a `LaunchConfiguration`. The latter uses ECS recommended AMI and machine type for GPU.
 
-
+Service to declare `deploy.x-aws-autoscaling` get a `ScalingPolicy` created targeting specified the configured CPU usage metric
 
 
diff --git a/ecs/autoscaling.go b/ecs/autoscaling.go
new file mode 100644
index 00000000..7a40938d
--- /dev/null
+++ b/ecs/autoscaling.go
@@ -0,0 +1,93 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package ecs
+
+import (
+	"fmt"
+
+	applicationautoscaling2 "github.com/aws/aws-sdk-go/service/applicationautoscaling"
+	"github.com/awslabs/goformation/v4/cloudformation"
+	"github.com/awslabs/goformation/v4/cloudformation/applicationautoscaling"
+	"github.com/awslabs/goformation/v4/cloudformation/iam"
+	"github.com/compose-spec/compose-go/types"
+)
+
+func (b *ecsAPIService) createAutoscalingPolicy(project *types.Project, resources awsResources, template *cloudformation.Template, service types.ServiceConfig) {
+	if service.Deploy == nil {
+		return
+	}
+	v, ok := service.Deploy.Extensions[extensionAutoScaling]
+	if !ok {
+		return
+	}
+
+	role := fmt.Sprintf("%sAutoScalingRole", normalizeResourceName(service.Name))
+	template.Resources[role] = &iam.Role{
+		AssumeRolePolicyDocument: ausocalingAssumeRolePolicyDocument,
+		Path:                     "/",
+		Policies: []iam.Role_Policy{
+			{
+				PolicyDocument: &PolicyDocument{
+					Statement: []PolicyStatement{
+						{
+							Effect: "Allow",
+							Action: []string{
+								actionAutoScaling,
+								actionDescribeService,
+								actionUpdateService,
+								actionGetMetrics,
+							},
+							Resource: []string{cloudformation.Ref(serviceResourceName(service.Name))},
+						},
+					},
+				},
+				PolicyName: "service-autoscaling",
+			},
+		},
+		Tags: serviceTags(project, service),
+	}
+
+	// Why isn't this just the service ARN ?????
+	resourceID := cloudformation.Join("/", []string{"service", resources.cluster, cloudformation.GetAtt(serviceResourceName(service.Name), "Name")})
+
+	target := fmt.Sprintf("%sScalableTarget", normalizeResourceName(service.Name))
+	template.Resources[target] = &applicationautoscaling.ScalableTarget{
+		MaxCapacity:                10,
+		MinCapacity:                0,
+		ResourceId:                 resourceID,
+		RoleARN:                    cloudformation.GetAtt(role, "Arn"),
+		ScalableDimension:          applicationautoscaling2.ScalableDimensionEcsServiceDesiredCount,
+		ServiceNamespace:           applicationautoscaling2.ServiceNamespaceEcs,
+		AWSCloudFormationDependsOn: []string{serviceResourceName(service.Name)},
+	}
+
+	policy := fmt.Sprintf("%sScalingPolicy", normalizeResourceName(service.Name))
+	template.Resources[policy] = &applicationautoscaling.ScalingPolicy{
+		PolicyType:                     "TargetTrackingScaling",
+		PolicyName:                     policy,
+		ScalingTargetId:                cloudformation.Ref(target),
+		StepScalingPolicyConfiguration: nil,
+		TargetTrackingScalingPolicyConfiguration: &applicationautoscaling.ScalingPolicy_TargetTrackingScalingPolicyConfiguration{
+			PredefinedMetricSpecification: &applicationautoscaling.ScalingPolicy_PredefinedMetricSpecification{
+				PredefinedMetricType: applicationautoscaling2.MetricTypeEcsserviceAverageCpuutilization,
+			},
+			ScaleOutCooldown: 60,
+			ScaleInCooldown:  60,
+			TargetValue:      float64(v.(int)),
+		},
+	}
+}
diff --git a/ecs/awsResources.go b/ecs/awsResources.go
index 2ba44e5e..829fc6bb 100644
--- a/ecs/awsResources.go
+++ b/ecs/awsResources.go
@@ -189,7 +189,6 @@ func (b *ecsAPIService) ensureNetworks(r *awsResources, project *types.Project,
 		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),
 		}
diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go
index 0b9cb93e..b9f7c61c 100644
--- a/ecs/cloudformation.go
+++ b/ecs/cloudformation.go
@@ -91,7 +91,7 @@ func (b *ecsAPIService) convert(project *types.Project, resources awsResources)
 
 	for _, service := range project.Services {
 		taskExecutionRole := b.createTaskExecutionRole(project, service, template)
-		taskRole := b.createTaskRole(service, template)
+		taskRole := b.createTaskRole(project, service, template)
 
 		definition, err := b.createTaskExecution(project, service)
 		if err != nil {
@@ -183,6 +183,8 @@ func (b *ecsAPIService) convert(project *types.Project, resources awsResources)
 			Tags:               serviceTags(project, service),
 			TaskDefinition:     cloudformation.Ref(normalizeResourceName(taskDefinition)),
 		}
+
+		b.createAutoscalingPolicy(project, resources, template, service)
 	}
 	return template, nil
 }
@@ -363,11 +365,12 @@ func (b *ecsAPIService) createTaskExecutionRole(project *types.Project, service
 			ecsTaskExecutionPolicy,
 			ecrReadOnlyPolicy,
 		},
+		Tags: serviceTags(project, service),
 	}
 	return taskExecutionRole
 }
 
-func (b *ecsAPIService) createTaskRole(service types.ServiceConfig, template *cloudformation.Template) string {
+func (b *ecsAPIService) createTaskRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template) string {
 	taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
 	rolePolicies := []iam.Role_Policy{}
 	if roles, ok := service.Extensions[extensionRole]; ok {
@@ -388,6 +391,7 @@ func (b *ecsAPIService) createTaskRole(service types.ServiceConfig, template *cl
 		AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
 		Policies:                 rolePolicies,
 		ManagedPolicyArns:        managedPolicies,
+		Tags:                     serviceTags(project, service),
 	}
 	return taskRole
 }
diff --git a/ecs/iam.go b/ecs/iam.go
index 3a517482..5b061172 100644
--- a/ecs/iam.go
+++ b/ecs/iam.go
@@ -21,35 +21,35 @@ const (
 	ecrReadOnlyPolicy      = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
 	ecsEC2InstanceRole     = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
 
-	actionGetSecretValue = "secretsmanager:GetSecretValue"
-	actionGetParameters  = "ssm:GetParameters"
-	actionDecrypt        = "kms:Decrypt"
+	actionGetSecretValue  = "secretsmanager:GetSecretValue"
+	actionGetParameters   = "ssm:GetParameters"
+	actionDecrypt         = "kms:Decrypt"
+	actionAutoScaling     = "application-autoscaling:*"
+	actionGetMetrics      = "cloudwatch:GetMetricStatistics"
+	actionDescribeService = "ecs:DescribeServices"
+	actionUpdateService   = "ecs:UpdateService"
 )
 
-var ecsTaskAssumeRolePolicyDocument = PolicyDocument{
-	Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
-	Statement: []PolicyStatement{
-		{
-			Effect: "Allow",
-			Principal: PolicyPrincipal{
-				Service: "ecs-tasks.amazonaws.com",
-			},
-			Action: []string{"sts:AssumeRole"},
-		},
-	},
-}
+var (
+	ecsTaskAssumeRolePolicyDocument     = policyDocument("ecs-tasks.amazonaws.com")
+	ec2InstanceAssumeRolePolicyDocument = policyDocument("ec2.amazonaws.com")
+	ausocalingAssumeRolePolicyDocument  = policyDocument("application-autoscaling.amazonaws.com")
+)
 
-var ec2InstanceAssumeRolePolicyDocument = PolicyDocument{
-	Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
-	Statement: []PolicyStatement{
-		{
-			Effect: "Allow",
-			Principal: PolicyPrincipal{
-				Service: "ec2.amazonaws.com",
+func policyDocument(service string) PolicyDocument {
+	return PolicyDocument{
+		Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
+		Statement: []PolicyStatement{
+			{
+				Effect: "Allow",
+				Principal: PolicyPrincipal{
+					Service: service,
+				},
+				Action: []string{"sts:AssumeRole"},
 			},
-			Action: []string{"sts:AssumeRole"},
 		},
-	},
+	}
+
 }
 
 // PolicyDocument describes an IAM policy document
diff --git a/ecs/x.go b/ecs/x.go
index 9b93436c..6968ba99 100644
--- a/ecs/x.go
+++ b/ecs/x.go
@@ -29,4 +29,5 @@ const (
 	extensionRetention       = "x-aws-logs_retention"
 	extensionRole            = "x-aws-role"
 	extensionManagedPolicies = "x-aws-policies"
+	extensionAutoScaling     = "x-aws-autoscaling"
 )


Test patch:
diff --git a/ecs/autoscaling_test.go b/ecs/autoscaling_test.go
new file mode 100644
index 00000000..1a411614
--- /dev/null
+++ b/ecs/autoscaling_test.go
@@ -0,0 +1,41 @@
+/*
+   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 (
+	"testing"
+
+	autoscaling "github.com/awslabs/goformation/v4/cloudformation/applicationautoscaling"
+	"gotest.tools/v3/assert"
+)
+
+func TestAutoScaling(t *testing.T) {
+	template := convertYaml(t, `
+services:
+  foo:
+    image: hello_world
+    deploy:
+      x-aws-autoscaling: 75
+`)
+	target := template.Resources["FooScalableTarget"].(*autoscaling.ScalableTarget)
+	assert.Check(t, target != nil)
+	policy := template.Resources["FooScalingPolicy"].(*autoscaling.ScalingPolicy)
+	if policy == nil || policy.TargetTrackingScalingPolicyConfiguration == nil {
+		t.Fail()
+	}
+	assert.Check(t, policy.TargetTrackingScalingPolicyConfiguration.TargetValue == float64(75))
+}
diff --git a/ecs/testdata/simple/simple-cloudformation-conversion.golden b/ecs/testdata/simple/simple-cloudformation-conversion.golden
index 56ec12f0..07200659 100644
--- a/ecs/testdata/simple/simple-cloudformation-conversion.golden
+++ b/ecs/testdata/simple/simple-cloudformation-conversion.golden
@@ -37,7 +37,6 @@
     "DefaultNetwork": {
       "Properties": {
         "GroupDescription": "TestSimpleConvert Security Group for default network",
-        "GroupName": "DefaultNetwork",
         "Tags": [
           {
             "Key": "com.docker.compose.project",
@@ -301,6 +300,16 @@
         "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"