示例#1
0
文件: clients.go 项目: mhahn/empire
// DescribeTaskDefinition will use the task definition from cache if provided
// with a task definition ARN.
func (c *cachingECSClient) DescribeTaskDefinition(input *ecs.DescribeTaskDefinitionInput) (*ecs.DescribeTaskDefinitionOutput, error) {
	if _, err := arn.Parse(*input.TaskDefinition); err != nil {
		return c.ecsClient.DescribeTaskDefinition(input)
	}

	if v, ok := c.taskDefinitions.Get(*input.TaskDefinition); ok {
		return &ecs.DescribeTaskDefinitionOutput{
			TaskDefinition: v.(*ecs.TaskDefinition),
		}, nil
	}

	resp, err := c.ecsClient.DescribeTaskDefinition(input)
	if err != nil {
		return resp, err
	}

	c.taskDefinitions.Set(*resp.TaskDefinition.TaskDefinitionArn, resp.TaskDefinition, 0)

	return resp, err
}
示例#2
0
文件: template.go 项目: mhahn/empire
// If the ServiceRole option is not an ARN, it will return a CloudFormation
// expression that expands the ServiceRole to an ARN.
func (t *EmpireTemplate) serviceRoleArn() interface{} {
	if _, err := arn.Parse(t.ServiceRole); err == nil {
		return t.ServiceRole
	}
	return Join("", "arn:aws:iam::", Ref("AWS::AccountId"), ":role/", t.ServiceRole)
}
示例#3
0
文件: template.go 项目: mhahn/empire
func (t *EmpireTemplate) addService(tmpl *troposphere.Template, app *scheduler.App, p *scheduler.Process) (serviceName string) {
	key := processResourceName(p.Type)

	// The standard AWS::ECS::Service resource's default behavior is to wait
	// for services to stabilize when you update them. While this is a
	// sensible default for CloudFormation, the overall behavior when
	// applied to Empire is not a great experience, because updates will
	// lock up the stack.
	//
	// Setting this option makes the stack use a Custom::ECSService
	// resources intead, which does not wait for the service to stabilize
	// after updating.
	ecsServiceType := "Custom::ECSService"

	var portMappings []*PortMappingProperties

	var serviceDependencies []string
	loadBalancers := []map[string]interface{}{}
	if p.Exposure != nil {
		scheme := schemeInternal
		sg := t.InternalSecurityGroupID
		subnets := t.InternalSubnetIDs

		if p.Exposure.External {
			scheme = schemeExternal
			sg = t.ExternalSecurityGroupID
			subnets = t.ExternalSubnetIDs
		}

		p.Env["PORT"] = fmt.Sprintf("%d", ContainerPort)

		loadBalancerType := classicLoadBalancer
		if v, ok := app.Env["LOAD_BALANCER_TYPE"]; ok {
			loadBalancerType = v
		}

		var loadBalancer string
		switch loadBalancerType {
		case applicationLoadBalancer:
			loadBalancer = fmt.Sprintf("%sApplicationLoadBalancer", key)
			tmpl.Resources[loadBalancer] = troposphere.Resource{
				Type: "AWS::ElasticLoadBalancingV2::LoadBalancer",
				Properties: map[string]interface{}{
					"Scheme":         scheme,
					"SecurityGroups": []string{sg},
					"Subnets":        subnets,
					"Tags": []map[string]string{
						map[string]string{
							"Key":   "empire.app.process",
							"Value": p.Type,
						},
					},
				},
			}

			targetGroup := fmt.Sprintf("%sTargetGroup", key)
			tmpl.Resources[targetGroup] = troposphere.Resource{
				Type: "AWS::ElasticLoadBalancingV2::TargetGroup",
				Properties: map[string]interface{}{
					"Port":     65535, // Not used. ECS sets a port override when registering targets.
					"Protocol": "HTTP",
					"VpcId":    t.VpcId,
				},
			}

			httpListener := fmt.Sprintf("%sPort%dListener", loadBalancer, 80)
			tmpl.Resources[httpListener] = troposphere.Resource{
				Type: "AWS::ElasticLoadBalancingV2::Listener",
				Properties: map[string]interface{}{
					"LoadBalancerArn": Ref(loadBalancer),
					"Port":            80,
					"Protocol":        "HTTP",
					"DefaultActions": []interface{}{
						map[string]interface{}{
							"TargetGroupArn": Ref(targetGroup),
							"Type":           "forward",
						},
					},
				},
			}
			serviceDependencies = append(serviceDependencies, httpListener)

			if e, ok := p.Exposure.Type.(*scheduler.HTTPSExposure); ok {
				var cert interface{}
				if _, err := arn.Parse(e.Cert); err == nil {
					cert = e.Cert
				} else {
					cert = Join("", "arn:aws:iam::", Ref("AWS::AccountId"), ":server-certificate/", e.Cert)
				}

				httpsListener := fmt.Sprintf("%sPort%dListener", loadBalancer, 443)
				tmpl.Resources[httpsListener] = troposphere.Resource{
					Type: "AWS::ElasticLoadBalancingV2::Listener",
					Properties: map[string]interface{}{
						"Certificates": []interface{}{
							map[string]interface{}{
								"CertificateArn": cert,
							},
						},
						"LoadBalancerArn": GetAtt(loadBalancer, "Arn"),
						"Port":            443,
						"Protocol":        "HTTPS",
						"DefaultActions": []interface{}{
							map[string]interface{}{
								"TargetGroupArn": Ref(targetGroup),
								"Type":           "forward",
							},
						},
					},
				}
				serviceDependencies = append(serviceDependencies, httpsListener)
			}

			loadBalancers = append(loadBalancers, map[string]interface{}{
				"ContainerName":  p.Type,
				"ContainerPort":  ContainerPort,
				"TargetGroupArn": Ref(targetGroup),
			})
			portMappings = append(portMappings, &PortMappingProperties{
				ContainerPort: ContainerPort,
				HostPort:      0,
			})
		default:
			loadBalancer = fmt.Sprintf("%sLoadBalancer", key)

			instancePort := fmt.Sprintf("%s%dInstancePort", key, ContainerPort)
			tmpl.Resources[instancePort] = troposphere.Resource{
				Type:    "Custom::InstancePort",
				Version: "1.0",
				Properties: map[string]interface{}{
					"ServiceToken": t.CustomResourcesTopic,
				},
			}

			listeners := []map[string]interface{}{
				map[string]interface{}{
					"LoadBalancerPort": 80,
					"Protocol":         "http",
					"InstancePort":     GetAtt(instancePort, "InstancePort"),
					"InstanceProtocol": "http",
				},
			}

			if e, ok := p.Exposure.Type.(*scheduler.HTTPSExposure); ok {
				var cert interface{}
				if _, err := arn.Parse(e.Cert); err == nil {
					cert = e.Cert
				} else {
					cert = Join("", "arn:aws:iam::", Ref("AWS::AccountId"), ":server-certificate/", e.Cert)
				}

				listeners = append(listeners, map[string]interface{}{
					"LoadBalancerPort": 443,
					"Protocol":         "https",
					"InstancePort":     GetAtt(instancePort, "InstancePort"),
					"SSLCertificateId": cert,
					"InstanceProtocol": "http",
				})
			}

			tmpl.Resources[loadBalancer] = troposphere.Resource{
				Type: "AWS::ElasticLoadBalancing::LoadBalancer",
				Properties: map[string]interface{}{
					"Scheme":         scheme,
					"SecurityGroups": []string{sg},
					"Subnets":        subnets,
					"Listeners":      listeners,
					"CrossZone":      true,
					"Tags": []map[string]string{
						map[string]string{
							"Key":   "empire.app.process",
							"Value": p.Type,
						},
					},
					"ConnectionDrainingPolicy": map[string]interface{}{
						"Enabled": true,
						"Timeout": defaultConnectionDrainingTimeout,
					},
				},
			}

			loadBalancers = append(loadBalancers, map[string]interface{}{
				"ContainerName":    p.Type,
				"ContainerPort":    ContainerPort,
				"LoadBalancerName": Ref(loadBalancer),
			})
			portMappings = append(portMappings, &PortMappingProperties{
				ContainerPort: ContainerPort,
				HostPort:      GetAtt(instancePort, "InstancePort"),
			})
		}

		if p.Type == "web" {
			tmpl.Resources["CNAME"] = troposphere.Resource{
				Type:      "AWS::Route53::RecordSet",
				Condition: "DNSCondition",
				Properties: map[string]interface{}{
					"HostedZoneId":    *t.HostedZone.Id,
					"Name":            fmt.Sprintf("%s.%s", app.Name, *t.HostedZone.Name),
					"Type":            "CNAME",
					"TTL":             defaultCNAMETTL,
					"ResourceRecords": []interface{}{GetAtt(loadBalancer, "DNSName")},
				},
			}
		}
	}

	taskDefinition, containerDefinition := t.addTaskDefinition(tmpl, app, p)

	containerDefinition.DockerLabels[restartLabel] = Ref(restartParameter)
	containerDefinition.PortMappings = portMappings

	serviceProperties := map[string]interface{}{
		"Cluster":        t.Cluster,
		"DesiredCount":   Ref(scaleParameter(p.Type)),
		"LoadBalancers":  loadBalancers,
		"TaskDefinition": Ref(taskDefinition),
		"ServiceName":    fmt.Sprintf("%s-%s", app.Name, p.Type),
		"ServiceToken":   t.CustomResourcesTopic,
	}
	if len(loadBalancers) > 0 {
		serviceProperties["Role"] = t.ServiceRole
	}
	service := troposphere.NamedResource{
		Name: fmt.Sprintf("%sService", key),
		Resource: troposphere.Resource{
			Type:       ecsServiceType,
			Properties: serviceProperties,
		},
	}
	if len(serviceDependencies) > 0 {
		service.Resource.DependsOn = serviceDependencies
	}
	tmpl.AddResource(service)
	return service.Name
}
示例#4
0
// Build builds a Go representation of a CloudFormation template for the app.
func (t *EmpireTemplate) Build(app *scheduler.App) (interface{}, error) {
	parameters := map[string]interface{}{
		"DNS": map[string]string{
			"Type":        "String",
			"Description": "When set to `true`, CNAME's will be altered",
			"Default":     "true",
		},
		restartParameter: map[string]string{
			"Type": "String",
		},
	}
	conditions := map[string]interface{}{
		"DNSCondition": map[string]interface{}{
			"Fn::Equals": []interface{}{
				map[string]string{
					"Ref": "DNS",
				},
				"true",
			},
		},
	}
	resources := map[string]interface{}{}
	outputs := map[string]interface{}{
		"Release": map[string]interface{}{
			"Value": app.Release,
		},
		"EmpireVersion": map[string]interface{}{
			"Value": empire.Version,
		},
	}

	serviceMappings := []map[string]interface{}{}

	// The standard AWS::ECS::Service resource's default behavior is to wait
	// for services to stabilize when you update them. While this is a
	// sensible default for CloudFormation, the overall behavior when
	// applied to Empire is not a great experience, because updates will
	// lock up the stack.
	//
	// Setting this option makes the stack use a Custom::ECSService
	// resources intead, which does not wait for the service to stabilize
	// after updating.
	ecsServiceType := "Custom::ECSService"
	if app.Env["ECS_SERVICE"] == "standard" {
		ecsServiceType = "AWS::ECS::Service"
	}

	for _, p := range app.Processes {
		cd := t.ContainerDefinition(app, p)

		key := processResourceName(p.Type)

		parameters[scaleParameter(p.Type)] = map[string]string{
			"Type": "String",
		}

		portMappings := []map[string]interface{}{}

		loadBalancers := []map[string]interface{}{}
		if p.Exposure != nil {
			scheme := schemeInternal
			sg := t.InternalSecurityGroupID
			subnets := t.InternalSubnetIDs

			if p.Exposure.External {
				scheme = schemeExternal
				sg = t.ExternalSecurityGroupID
				subnets = t.ExternalSubnetIDs
			}

			instancePort := fmt.Sprintf("%s%dInstancePort", key, ContainerPort)
			resources[instancePort] = map[string]interface{}{
				"Type":    "Custom::InstancePort",
				"Version": "1.0",
				"Properties": map[string]interface{}{
					"ServiceToken": t.CustomResourcesTopic,
				},
			}

			listeners := []map[string]interface{}{
				map[string]interface{}{
					"LoadBalancerPort": 80,
					"Protocol":         "http",
					"InstancePort": map[string][]string{
						"Fn::GetAtt": []string{
							instancePort,
							"InstancePort",
						},
					},
					"InstanceProtocol": "http",
				},
			}

			if e, ok := p.Exposure.Type.(*scheduler.HTTPSExposure); ok {
				var cert interface{}
				if _, err := arn.Parse(e.Cert); err == nil {
					cert = e.Cert
				} else {
					cert = map[string]interface{}{
						"Fn::Join": []interface{}{
							"",
							[]interface{}{"arn:aws:iam::", map[string]string{"Ref": "AWS::AccountId"}, ":server-certificate/", e.Cert},
						},
					}
				}

				listeners = append(listeners, map[string]interface{}{
					"LoadBalancerPort": 443,
					"Protocol":         "https",
					"InstancePort": map[string][]string{
						"Fn::GetAtt": []string{
							instancePort,
							"InstancePort",
						},
					},
					"SSLCertificateId": cert,
					"InstanceProtocol": "http",
				})
			}

			portMappings = append(portMappings, map[string]interface{}{
				"ContainerPort": ContainerPort,
				"HostPort": map[string][]string{
					"Fn::GetAtt": []string{
						instancePort,
						"InstancePort",
					},
				},
			})
			cd.Environment = append(cd.Environment, &ecs.KeyValuePair{
				Name:  aws.String("PORT"),
				Value: aws.String(fmt.Sprintf("%d", ContainerPort)),
			})

			loadBalancer := fmt.Sprintf("%sLoadBalancer", key)
			loadBalancers = append(loadBalancers, map[string]interface{}{
				"ContainerName": p.Type,
				"ContainerPort": ContainerPort,
				"LoadBalancerName": map[string]string{
					"Ref": loadBalancer,
				},
			})
			resources[loadBalancer] = map[string]interface{}{
				"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
				"Properties": map[string]interface{}{
					"Scheme":         scheme,
					"SecurityGroups": []string{sg},
					"Subnets":        subnets,
					"Listeners":      listeners,
					"CrossZone":      true,
					"Tags": []map[string]string{
						map[string]string{
							"Key":   "empire.app.process",
							"Value": p.Type,
						},
					},
					"ConnectionDrainingPolicy": map[string]interface{}{
						"Enabled": true,
						"Timeout": defaultConnectionDrainingTimeout,
					},
				},
			}

			if p.Type == "web" {
				resources["CNAME"] = map[string]interface{}{
					"Type":      "AWS::Route53::RecordSet",
					"Condition": "DNSCondition",
					"Properties": map[string]interface{}{
						"HostedZoneId": *t.HostedZone.Id,
						"Name":         fmt.Sprintf("%s.%s", app.Name, *t.HostedZone.Name),
						"Type":         "CNAME",
						"TTL":          defaultCNAMETTL,
						"ResourceRecords": []map[string][]string{
							map[string][]string{
								"Fn::GetAtt": []string{loadBalancer, "DNSName"},
							},
						},
					},
				}
			}
		}

		labels := map[string]interface{}{}
		for k, v := range cd.DockerLabels {
			labels[k] = v
		}
		labels["cloudformation.restart-key"] = map[string]string{"Ref": restartParameter}

		taskDefinition := fmt.Sprintf("%sTaskDefinition", key)
		containerDefinition := map[string]interface{}{
			"Name":         *cd.Name,
			"Command":      cd.Command,
			"Cpu":          *cd.Cpu,
			"Image":        *cd.Image,
			"Essential":    *cd.Essential,
			"Memory":       *cd.Memory,
			"Environment":  cd.Environment,
			"PortMappings": portMappings,
			"DockerLabels": labels,
			"Ulimits":      cd.Ulimits,
		}
		if cd.LogConfiguration != nil {
			containerDefinition["LogConfiguration"] = cd.LogConfiguration
		}
		resources[taskDefinition] = map[string]interface{}{
			"Type": "AWS::ECS::TaskDefinition",
			"Properties": map[string]interface{}{
				"ContainerDefinitions": []interface{}{
					containerDefinition,
				},
				"Volumes": []interface{}{},
			},
		}

		service := fmt.Sprintf("%s", key)
		serviceProperties := map[string]interface{}{
			"Cluster": t.Cluster,
			"DesiredCount": map[string]string{
				"Ref": scaleParameter(p.Type),
			},
			"LoadBalancers": loadBalancers,
			"TaskDefinition": map[string]string{
				"Ref": taskDefinition,
			},
		}
		if ecsServiceType == "Custom::ECSService" {
			// It's not possible to change the type of a resource,
			// so we have to change the name of the service resource
			// to something different (just append "Service").
			service = fmt.Sprintf("%sService", service)
			serviceProperties["ServiceName"] = fmt.Sprintf("%s-%s", app.Name, p.Type)
			serviceProperties["ServiceToken"] = t.CustomResourcesTopic
		}
		if len(loadBalancers) > 0 {
			serviceProperties["Role"] = t.ServiceRole
		}
		serviceMappings = append(serviceMappings, map[string]interface{}{
			"Fn::Join": []interface{}{
				"=",
				[]interface{}{p.Type, map[string]string{"Ref": service}},
			},
		})
		resources[service] = map[string]interface{}{
			"Type":       ecsServiceType,
			"Properties": serviceProperties,
		}

	}

	outputs[servicesOutput] = map[string]interface{}{
		"Value": map[string]interface{}{
			"Fn::Join": []interface{}{
				",",
				serviceMappings,
			},
		},
	}

	return map[string]interface{}{
		"Parameters": parameters,
		"Conditions": conditions,
		"Resources":  resources,
		"Outputs":    outputs,
	}, nil
}
示例#5
0
文件: template.go 项目: iserko/empire
func (t *EmpireTemplate) addService(tmpl *troposphere.Template, app *scheduler.App, p *scheduler.Process, stackTags []*cloudformation.Tag) (serviceName string, err error) {
	key := processResourceName(p.Type)

	// Process specific tags to apply to resources.
	tags := tagsFromLabels(p.Labels)

	// The standard AWS::ECS::Service resource's default behavior is to wait
	// for services to stabilize when you update them. While this is a
	// sensible default for CloudFormation, the overall behavior when
	// applied to Empire is not a great experience, because updates will
	// lock up the stack.
	//
	// Setting this option makes the stack use a Custom::ECSService
	// resources intead, which does not wait for the service to stabilize
	// after updating.
	ecsServiceType := "Custom::ECSService"

	var portMappings []*PortMappingProperties

	var serviceDependencies []string
	loadBalancers := []map[string]interface{}{}
	if p.Exposure != nil {
		scheme := schemeInternal
		sg := t.InternalSecurityGroupID
		subnets := t.InternalSubnetIDs

		if p.Exposure.External {
			scheme = schemeExternal
			sg = t.ExternalSecurityGroupID
			subnets = t.ExternalSubnetIDs
		}

		loadBalancerType := loadBalancerType(app, p)

		var (
			loadBalancer          troposphere.NamedResource
			canonicalHostedZoneId interface{}
		)

		switch loadBalancerType {
		case applicationLoadBalancer:
			loadBalancer = troposphere.NamedResource{
				Name: fmt.Sprintf("%sApplicationLoadBalancer", key),
				Resource: troposphere.Resource{
					Type: "AWS::ElasticLoadBalancingV2::LoadBalancer",
					Properties: map[string]interface{}{
						"Scheme":         scheme,
						"SecurityGroups": []string{sg},
						"Subnets":        subnets,
						"Tags":           append(stackTags, tags...),
					},
				},
			}
			canonicalHostedZoneId = GetAtt(loadBalancer, "CanonicalHostedZoneID")

			tmpl.AddResource(loadBalancer)

			targetGroup := fmt.Sprintf("%sTargetGroup", key)
			tmpl.Resources[targetGroup] = troposphere.Resource{
				Type: "AWS::ElasticLoadBalancingV2::TargetGroup",
				Properties: map[string]interface{}{
					"Port":     65535, // Not used. ECS sets a port override when registering targets.
					"Protocol": "HTTP",
					"VpcId":    t.VpcId,
					"Tags":     append(stackTags, tags...),
				},
			}

			// Add a port mapping for each unique container port.
			containerPorts := make(map[int]bool)
			for _, port := range p.Exposure.Ports {
				if ok := containerPorts[port.Container]; !ok {
					containerPorts[port.Container] = true
					portMappings = append(portMappings, &PortMappingProperties{
						ContainerPort: port.Container,
						HostPort:      0,
					})
				}
			}

			// Unlike ELB, ALB can only route to a single container
			// port, when dynamic ports are used. Thus, we have to
			// ensure that all of the defined ports map to the same
			// container port.
			//
			// ELB can route to multiple container ports, because a
			// listener can directly point to a container port,
			// through an instance port:
			//
			//	Listener Port => Instance Port => Container Port
			if len(containerPorts) > 1 {
				err = fmt.Errorf("AWS Application Load Balancers can only map listeners to a single container port. %d unique container ports were defined: [%s]", len(p.Exposure.Ports), fmtPorts(p.Exposure.Ports))
				return
			}

			// Add a listener for each port.
			for _, port := range p.Exposure.Ports {
				listener := troposphere.NamedResource{
					Name: fmt.Sprintf("%sPort%dListener", loadBalancer.Name, port.Host),
				}

				switch e := port.Protocol.(type) {
				case *scheduler.HTTP:
					listener.Resource = troposphere.Resource{
						Type: "AWS::ElasticLoadBalancingV2::Listener",
						Properties: map[string]interface{}{
							"LoadBalancerArn": Ref(loadBalancer),
							"Port":            port.Host,
							"Protocol":        "HTTP",
							"DefaultActions": []interface{}{
								map[string]interface{}{
									"TargetGroupArn": Ref(targetGroup),
									"Type":           "forward",
								},
							},
						},
					}
				case *scheduler.HTTPS:
					var cert interface{}
					if _, err := arn.Parse(e.Cert); err == nil {
						cert = e.Cert
					} else {
						cert = Join("", "arn:aws:iam::", Ref("AWS::AccountId"), ":server-certificate/", e.Cert)
					}

					listener.Resource = troposphere.Resource{
						Type: "AWS::ElasticLoadBalancingV2::Listener",
						Properties: map[string]interface{}{
							"Certificates": []interface{}{
								map[string]interface{}{
									"CertificateArn": cert,
								},
							},
							"LoadBalancerArn": Ref(loadBalancer),
							"Port":            port.Host,
							"Protocol":        "HTTPS",
							"DefaultActions": []interface{}{
								map[string]interface{}{
									"TargetGroupArn": Ref(targetGroup),
									"Type":           "forward",
								},
							},
						},
					}
				default:
					err = fmt.Errorf("%s listeners are not supported with AWS Application Load Balancing", e.Protocol())
					return
				}
				tmpl.AddResource(listener)
				serviceDependencies = append(serviceDependencies, listener.Name)
			}

			loadBalancers = append(loadBalancers, map[string]interface{}{
				"ContainerName":  p.Type,
				"ContainerPort":  p.Exposure.Ports[0].Container,
				"TargetGroupArn": Ref(targetGroup),
			})
		default:
			loadBalancer = troposphere.NamedResource{
				Name: fmt.Sprintf("%sLoadBalancer", key),
			}
			canonicalHostedZoneId = GetAtt(loadBalancer, "CanonicalHostedZoneNameID")

			listeners := []map[string]interface{}{}

			// Add a port mapping for each unique container port.
			instancePorts := make(map[int]troposphere.NamedResource)
			for _, port := range p.Exposure.Ports {
				if _, ok := instancePorts[port.Container]; !ok {
					instancePort := troposphere.NamedResource{
						Name: fmt.Sprintf("%s%dInstancePort", key, port.Container),
						Resource: troposphere.Resource{
							Type:    "Custom::InstancePort",
							Version: "1.0",
							Properties: map[string]interface{}{
								"ServiceToken": t.CustomResourcesTopic,
							},
						},
					}
					portMappings = append(portMappings, &PortMappingProperties{
						ContainerPort: port.Container,
						HostPort:      GetAtt(instancePort, "InstancePort"),
					})
					tmpl.AddResource(instancePort)
					instancePorts[port.Container] = instancePort
				}
			}

			for _, port := range p.Exposure.Ports {
				instancePort := instancePorts[port.Container]

				switch e := port.Protocol.(type) {
				case *scheduler.TCP:
					listeners = append(listeners, map[string]interface{}{
						"LoadBalancerPort": port.Host,
						"Protocol":         "tcp",
						"InstancePort":     GetAtt(instancePort, "InstancePort"),
						"InstanceProtocol": "tcp",
					})
				case *scheduler.SSL:
					var cert interface{}
					if _, err := arn.Parse(e.Cert); err == nil {
						cert = e.Cert
					} else {
						cert = Join("", "arn:aws:iam::", Ref("AWS::AccountId"), ":server-certificate/", e.Cert)
					}

					listeners = append(listeners, map[string]interface{}{
						"LoadBalancerPort": port.Host,
						"Protocol":         "ssl",
						"InstancePort":     GetAtt(instancePort, "InstancePort"),
						"SSLCertificateId": cert,
						"InstanceProtocol": "tcp",
					})
				case *scheduler.HTTP:
					listeners = append(listeners, map[string]interface{}{
						"LoadBalancerPort": port.Host,
						"Protocol":         "http",
						"InstancePort":     GetAtt(instancePort, "InstancePort"),
						"InstanceProtocol": "http",
					})
				case *scheduler.HTTPS:
					var cert interface{}
					if _, err := arn.Parse(e.Cert); err == nil {
						cert = e.Cert
					} else {
						cert = Join("", "arn:aws:iam::", Ref("AWS::AccountId"), ":server-certificate/", e.Cert)
					}

					listeners = append(listeners, map[string]interface{}{
						"LoadBalancerPort": port.Host,
						"Protocol":         "https",
						"InstancePort":     GetAtt(instancePort, "InstancePort"),
						"SSLCertificateId": cert,
						"InstanceProtocol": "http",
					})
				}
			}

			loadBalancer.Resource = troposphere.Resource{
				Type: "AWS::ElasticLoadBalancing::LoadBalancer",
				Properties: map[string]interface{}{
					"Scheme":         scheme,
					"SecurityGroups": []string{sg},
					"Subnets":        subnets,
					"Listeners":      listeners,
					"CrossZone":      true,
					"Tags":           tags,
					"ConnectionDrainingPolicy": map[string]interface{}{
						"Enabled": true,
						"Timeout": defaultConnectionDrainingTimeout,
					},
				},
			}
			tmpl.AddResource(loadBalancer)

			loadBalancers = append(loadBalancers, map[string]interface{}{
				"ContainerName":    p.Type,
				"ContainerPort":    p.Exposure.Ports[0].Container,
				"LoadBalancerName": Ref(loadBalancer),
			})
		}

		alias := troposphere.NamedResource{
			Name: fmt.Sprintf("%sAlias", key),
			Resource: troposphere.Resource{
				Type:      "AWS::Route53::RecordSet",
				Condition: "DNSCondition",
				Properties: map[string]interface{}{
					"HostedZoneId": *t.HostedZone.Id,
					"Name":         fmt.Sprintf("%s.%s.%s", p.Type, app.Name, *t.HostedZone.Name),
					"Type":         "A",
					"AliasTarget": map[string]interface{}{
						"DNSName":              GetAtt(loadBalancer, "DNSName"),
						"EvaluateTargetHealth": "true",
						"HostedZoneId":         canonicalHostedZoneId,
					},
				},
			},
		}
		tmpl.AddResource(alias)

		// DEPRECATED: This was used in the world where only the "web"
		// process could be exposed.
		if p.Type == "web" {
			tmpl.Resources["CNAME"] = troposphere.Resource{
				Type:      "AWS::Route53::RecordSet",
				Condition: "DNSCondition",
				Properties: map[string]interface{}{
					"HostedZoneId":    *t.HostedZone.Id,
					"Name":            fmt.Sprintf("%s.%s", app.Name, *t.HostedZone.Name),
					"Type":            "CNAME",
					"TTL":             defaultCNAMETTL,
					"ResourceRecords": []interface{}{GetAtt(loadBalancer, "DNSName")},
				},
			}
		}
	}

	taskDefinition, containerDefinition := t.addTaskDefinition(tmpl, app, p)

	containerDefinition.DockerLabels[restartLabel] = Ref(restartParameter)
	containerDefinition.PortMappings = portMappings

	serviceProperties := map[string]interface{}{
		"Cluster":        t.Cluster,
		"DesiredCount":   Ref(scaleParameter(p.Type)),
		"LoadBalancers":  loadBalancers,
		"TaskDefinition": Ref(taskDefinition),
		"ServiceName":    fmt.Sprintf("%s-%s", app.Name, p.Type),
		"ServiceToken":   t.CustomResourcesTopic,
	}
	if len(loadBalancers) > 0 {
		serviceProperties["Role"] = t.ServiceRole
	}
	service := troposphere.NamedResource{
		Name: fmt.Sprintf("%sService", key),
		Resource: troposphere.Resource{
			Type:       ecsServiceType,
			Properties: serviceProperties,
		},
	}
	if len(serviceDependencies) > 0 {
		service.Resource.DependsOn = serviceDependencies
	}
	tmpl.AddResource(service)
	return service.Name, nil
}