Skip to content

Commit

Permalink
Custom::ECSTaskDefinition resource.
Browse files Browse the repository at this point in the history
  • Loading branch information
ejholmes committed Jul 15, 2016
1 parent 5c687f9 commit 7e64b39
Show file tree
Hide file tree
Showing 8 changed files with 690 additions and 17 deletions.
15 changes: 15 additions & 0 deletions migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,21 @@ ALTER TABLE apps ADD COLUMN exposure TEXT NOT NULL default 'private'`,
`DROP TABLE scheduler_migration`,
}),
},

// This migration adds a table that stores environment variables for the
// Custom::ECSEnvironment resource.
{
ID: 18,
Up: migrate.Queries([]string{
`CREATE TABLE ecs_environment (
id uuid NOT NULL DEFAULT uuid_generate_v4() primary key,
environment json NOT NULL
)`,
}),
Down: migrate.Queries([]string{
`DROP TABLE ecs_environment`,
}),
},
}

// latestSchema returns the schema version that this version of Empire should be
Expand Down
2 changes: 1 addition & 1 deletion migrations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ func TestMigrations(t *testing.T) {
}

func TestLatestSchema(t *testing.T) {
assert.Equal(t, 17, latestSchema())
assert.Equal(t, 18, latestSchema())
}
69 changes: 62 additions & 7 deletions scheduler/cloudformation/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const (
defaultCNAMETTL = 60

runTaskFunction = "RunTaskFunction"

appEnvironment = "AppEnvironment"
)

// This implements the Template interface to create a suitable CloudFormation
Expand Down Expand Up @@ -248,6 +250,28 @@ func (t *EmpireTemplate) addService(tmpl *troposphere.Template, app *scheduler.A
// resources intead, which does not wait for the service to stabilize
// after updating.
ecsServiceType := "Custom::ECSService"
taskDefinitionType := "AWS::ECS::TaskDefinition"

if app.Env["ECS_TASK_DEFINITION"] == "custom" {
taskDefinitionType = "Custom::ECSTaskDefinition"
}

if taskDefinitionType == "Custom::ECSTaskDefinition" {
var env []interface{}
for k, v := range app.Env {
env = append(env, map[string]interface{}{
"Name": k,
"Value": v,
})
}
tmpl.Resources[appEnvironment] = troposphere.Resource{
Type: "Custom::ECSEnvironment",
Properties: map[string]interface{}{
"ServiceToken": t.CustomResourcesTopic,
"Environment": env,
},
}
}

cd := t.ContainerDefinition(app, p)

Expand Down Expand Up @@ -364,14 +388,45 @@ func (t *EmpireTemplate) addService(tmpl *troposphere.Template, app *scheduler.A
containerDefinition["DockerLabels"] = labels
containerDefinition["PortMappings"] = portMappings

tmpl.Resources[taskDefinition] = troposphere.Resource{
Type: "AWS::ECS::TaskDefinition",
Properties: map[string]interface{}{
"ContainerDefinitions": []interface{}{
containerDefinition,
taskDefinitionProperties := map[string]interface{}{
"Volumes": []interface{}{},
}
if taskDefinitionType == "Custom::ECSTaskDefinition" {
taskDefinition = fmt.Sprintf("%sTD", key)

processEnvironment := fmt.Sprintf("%sEnvironment", key)
var env []interface{}
for k, v := range p.Env {
env = append(env, map[string]interface{}{
"Name": k,
"Value": v,
})
}
tmpl.Resources[processEnvironment] = troposphere.Resource{
Type: "Custom::ECSEnvironment",
Properties: map[string]interface{}{
"ServiceToken": t.CustomResourcesTopic,
"Environment": env,
},
"Volumes": []interface{}{},
},
}

containerDefinition["Environment"] = []interface{}{
Ref(appEnvironment),
Ref(processEnvironment),
}
taskDefinitionProperties["ServiceToken"] = t.CustomResourcesTopic
taskDefinitionProperties["Family"] = fmt.Sprintf("%s-%s", app.Name, p.Type)
} else {
containerDefinition["Environment"] = cd.Environment
}

taskDefinitionProperties["ContainerDefinitions"] = []interface{}{
containerDefinition,
}

tmpl.Resources[taskDefinition] = troposphere.Resource{
Type: taskDefinitionType,
Properties: taskDefinitionProperties,
}

service := fmt.Sprintf("%sService", key)
Expand Down
33 changes: 33 additions & 0 deletions scheduler/cloudformation/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,38 @@ func TestEmpireTemplate(t *testing.T) {
},
},

{
"custom.json",
&scheduler.App{
ID: "1234",
Release: "v1",
Name: "acme-inc",
Env: map[string]string{
"ECS_TASK_DEFINITION": "custom",
},
Processes: []*scheduler.Process{
{
Type: "web",
Image: image.Image{Repository: "remind101/acme-inc", Tag: "latest"},
Command: []string{"./bin/web"},
Env: map[string]string{
"FOO": "bar",
},
Exposure: &scheduler.Exposure{
Type: &scheduler.HTTPExposure{},
},
Labels: map[string]string{
"empire.app.process": "web",
},
MemoryLimit: 128 * bytesize.MB,
CPUShares: 256,
Instances: 1,
Nproc: 256,
},
},
},
},

{
"cron.json",
&scheduler.App{
Expand Down Expand Up @@ -174,6 +206,7 @@ func TestEmpireTemplate_Large(t *testing.T) {
buf := new(bytes.Buffer)

err := tmpl.Execute(buf, app)
t.Logf("Template size: %d bytes", buf.Len())
assert.NoError(t, err)
assert.Condition(t, func() bool {
return buf.Len() < MaxTemplateSize
Expand Down
217 changes: 217 additions & 0 deletions scheduler/cloudformation/templates/custom.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
{
"Conditions": {
"DNSCondition": {
"Fn::Equals": [
{
"Ref": "DNS"
},
"true"
]
}
},
"Outputs": {
"EmpireVersion": {
"Value": "x.x.x"
},
"Release": {
"Value": "v1"
},
"Services": {
"Value": {
"Fn::Join": [
",",
[
{
"Fn::Join": [
"=",
[
"web",
{
"Ref": "webService"
}
]
]
}
]
]
}
}
},
"Parameters": {
"DNS": {
"Type": "String",
"Description": "When set to `true`, CNAME's will be altered",
"Default": "true"
},
"RestartKey": {
"Type": "String"
},
"webScale": {
"Type": "String"
}
},
"Resources": {
"AppEnvironment": {
"Properties": {
"Environment": [
{
"Name": "ECS_TASK_DEFINITION",
"Value": "custom"
}
],
"ServiceToken": "sns topic arn"
},
"Type": "Custom::ECSEnvironment"
},
"CNAME": {
"Condition": "DNSCondition",
"Properties": {
"HostedZoneId": "Z3DG6IL3SJCGPX",
"Name": "acme-inc.empire",
"ResourceRecords": [
{
"Fn::GetAtt": [
"webLoadBalancer",
"DNSName"
]
}
],
"TTL": 60,
"Type": "CNAME"
},
"Type": "AWS::Route53::RecordSet"
},
"web8080InstancePort": {
"Properties": {
"ServiceToken": "sns topic arn"
},
"Type": "Custom::InstancePort",
"Version": "1.0"
},
"webEnvironment": {
"Properties": {
"Environment": [
{
"Name": "FOO",
"Value": "bar"
}
],
"ServiceToken": "sns topic arn"
},
"Type": "Custom::ECSEnvironment"
},
"webLoadBalancer": {
"Properties": {
"ConnectionDrainingPolicy": {
"Enabled": true,
"Timeout": 30
},
"CrossZone": true,
"Listeners": [
{
"InstancePort": {
"Fn::GetAtt": [
"web8080InstancePort",
"InstancePort"
]
},
"InstanceProtocol": "http",
"LoadBalancerPort": 80,
"Protocol": "http"
}
],
"Scheme": "internal",
"SecurityGroups": [
"sg-e7387381"
],
"Subnets": [
"subnet-bb01c4cd",
"subnet-c85f4091"
],
"Tags": [
{
"Key": "empire.app.process",
"Value": "web"
}
]
},
"Type": "AWS::ElasticLoadBalancing::LoadBalancer"
},
"webService": {
"Properties": {
"Cluster": "cluster",
"DesiredCount": {
"Ref": "webScale"
},
"LoadBalancers": [
{
"ContainerName": "web",
"ContainerPort": 8080,
"LoadBalancerName": {
"Ref": "webLoadBalancer"
}
}
],
"Role": "ecsServiceRole",
"ServiceName": "acme-inc-web",
"ServiceToken": "sns topic arn",
"TaskDefinition": {
"Ref": "webTD"
}
},
"Type": "Custom::ECSService"
},
"webTD": {
"Properties": {
"ContainerDefinitions": [
{
"Command": [
"./bin/web"
],
"Cpu": 256,
"DockerLabels": {
"cloudformation.restart-key": {
"Ref": "RestartKey"
},
"empire.app.process": "web"
},
"Environment": [
{
"Ref": "AppEnvironment"
},
{
"Ref": "webEnvironment"
}
],
"Essential": true,
"Image": "remind101/acme-inc:latest",
"Memory": 128,
"Name": "web",
"PortMappings": [
{
"ContainerPort": 8080,
"HostPort": {
"Fn::GetAtt": [
"web8080InstancePort",
"InstancePort"
]
}
}
],
"Ulimits": [
{
"HardLimit": 256,
"Name": "nproc",
"SoftLimit": 256
}
]
}
],
"Family": "acme-inc-web",
"ServiceToken": "sns topic arn",
"Volumes": []
},
"Type": "Custom::ECSTaskDefinition"
}
}
}
Loading

0 comments on commit 7e64b39

Please sign in to comment.