Skip to content

Commit 057430c

Browse files
author
Patrick Robinson
committed
Merge remote-tracking branch 'upstream/master' into always-output-events
2 parents c01359e + a25cd93 commit 057430c

File tree

5 files changed

+332
-37
lines changed

5 files changed

+332
-37
lines changed

README.md

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ECS Deploy Buildkite Plugin (Alpha) ![Build status](https://badge.buildkite.com/67da940833c8744761259918c52d4a005e2b5599a173d1e131.svg?branch=master)
1+
# ECS Deploy Buildkite Plugin ![Build status](https://badge.buildkite.com/67da940833c8744761259918c52d4a005e2b5599a173d1e131.svg?branch=master)
22

33
A [Buildkite plugin](https://buildkite.com/docs/agent/v3/plugins) for deploying to [Amazon ECS](https://aws.amazon.com/ecs/).
44

@@ -7,8 +7,6 @@ A [Buildkite plugin](https://buildkite.com/docs/agent/v3/plugins) for deploying
77
* Updates the ECS service to use the new task definition ([`update-service`](http://docs.aws.amazon.com/cli/latest/reference/ecs/update-service.html))
88
* Waits for the service to stabilize ([`wait services-stable`](http://docs.aws.amazon.com/cli/latest/reference/ecs/wait/services-stable.html))
99

10-
_The ECS service must have been created before using this plugin._
11-
1210
## Example
1311

1412
```yml
@@ -17,7 +15,7 @@ steps:
1715
concurrency_group: "my-service-deploy"
1816
concurrency: 1
1917
plugins:
20-
- ecs-deploy#v1.1.0:
18+
- ecs-deploy#v1.2.0:
2119
cluster: "my-ecs-cluster"
2220
service: "my-service"
2321
task-definition: "examples/hello-world.json"
@@ -53,29 +51,67 @@ Example: `"my-task"`
5351

5452
### `image`
5553

56-
The Docker image to deploy.
54+
The Docker image to deploy. This can be an array to substitute multiple images in a single container definition.
55+
56+
Examples:
57+
`"012345.dkr.ecr.us-east-1.amazonaws.com/my-service:123"`
58+
59+
```yaml
60+
image:
61+
- "012345.dkr.ecr.us-east-1.amazonaws.com/my-service:123"
62+
- "012345.dkr.ecr.us-east-1.amazonaws.com/nginx:123"
63+
```
64+
65+
### `task-role-arn` (optional)
5766

58-
Example: `"012345.dkr.ecr.us-east-1.amazonaws.com/my-service:123"`
67+
An IAM ECS Task Role to assign to tasks.
68+
Requires the `iam:PassRole` permission for the ARN specified.
5969

60-
### `target-group`
70+
### `target-group` (optional)
6171

6272
The Target Group ARN to map the service to.
6373

6474
Example: `"arn:aws:elasticloadbalancing:us-east-1:012345678910:targetgroup/alb/e987e1234cd12abc"`
6575

66-
### `target-container-name`
76+
### `target-container-name` (optional)
6777

6878
The Container Name to forward ALB requests to.
6979

70-
### `target-container-port`
80+
### `target-container-port` (optional)
7181

7282
The Container Port to forward requests to.
7383

84+
### `execution-role` (optional)
85+
86+
The Execution Role ARN used by ECS to pull container images and secrets.
87+
88+
Example: `"arn:aws:iam::012345678910:role/execution-role"`
89+
90+
Requires the `iam:PassRole` permission for the execution role.
91+
92+
### `deployment-configuration` (optional)
93+
94+
The minimum and maximum percentage of tasks that should be maintained during a deployment. Defaults to `100/200`
95+
96+
Example: `"0/100"`
97+
7498
## AWS Roles
7599

76-
Requires the following AWS roles to be granted to the agent running this step:
100+
At a minimum this plugin requires the following AWS permissions to be granted to the agent running this step:
101+
102+
```yml
103+
Policy:
104+
Statement:
105+
- Action:
106+
- ecr:DescribeImages
107+
- ecs:DescribeServices
108+
- ecs:RegisterTaskDefinition
109+
- ecs:UpdateService
110+
Effect: Allow
111+
Resource: '*'
112+
```
77113

78-
* TODO
114+
This plugin will create the ECS Service if it does not already exist, which additionally requires the `ecs:CreateService` permission.
79115

80116
## Developing
81117

examples/multiple-images.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"essential": true,
4+
"image": "amazon/amazon-ecs-sample",
5+
"memory": 100,
6+
"name": "sample",
7+
"portMappings": [
8+
{
9+
"containerPort": 80,
10+
"hostPort": 80
11+
}
12+
]
13+
},
14+
{
15+
"essential": true,
16+
"image": "amazon/amazon-ecs-sample",
17+
"memory": 100,
18+
"name": "sample",
19+
"portMappings": [
20+
{
21+
"containerPort": 80,
22+
"hostPort": 80
23+
}
24+
]
25+
}
26+
]

hooks/command

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,54 @@
11
#!/bin/bash
22
set -euo pipefail
33

4+
# Reads either a value or a list from plugin config
5+
function plugin_read_list() {
6+
prefix_read_list "BUILDKITE_PLUGIN_ECS_DEPLOY_$1"
7+
}
8+
9+
# Reads either a value or a list from the given env prefix
10+
function prefix_read_list() {
11+
local prefix="$1"
12+
local parameter="${prefix}_0"
13+
14+
if [[ -n "${!parameter:-}" ]]; then
15+
local i=0
16+
local parameter="${prefix}_${i}"
17+
while [[ -n "${!parameter:-}" ]]; do
18+
echo "${!parameter}"
19+
i=$((i+1))
20+
parameter="${prefix}_${i}"
21+
done
22+
elif [[ -n "${!prefix:-}" ]]; then
23+
echo "${!prefix}"
24+
fi
25+
}
26+
427
cluster=${BUILDKITE_PLUGIN_ECS_DEPLOY_CLUSTER?}
528
task_family=${BUILDKITE_PLUGIN_ECS_DEPLOY_TASK_FAMILY?}
629
service_name=${BUILDKITE_PLUGIN_ECS_DEPLOY_SERVICE?}
7-
image=${BUILDKITE_PLUGIN_ECS_DEPLOY_IMAGE?}
30+
images=()
31+
while read -r line ; do
32+
[[ -n "$line" ]] && images+=("$line")
33+
done <<< "$(plugin_read_list IMAGE)"
834
task_definition=${BUILDKITE_PLUGIN_ECS_DEPLOY_TASK_DEFINITION?}
935
desired_count=${BUILDKITE_PLUGIN_ECS_DEPLOY_DESIRED_COUNT:-"1"}
1036
task_role_arn=${BUILDKITE_PLUGIN_ECS_DEPLOY_TASK_ROLE_ARN:-""}
1137
target_group=${BUILDKITE_PLUGIN_ECS_DEPLOY_TARGET_GROUP:-""}
12-
# Resolve any runtime environment variables it has
13-
target_group=$(eval "echo $target_group")
38+
load_balancer_name=${BUILDKITE_PLUGIN_ECS_DEPLOY_LOAD_BALANCER_NAME:-""}
1439
target_container=${BUILDKITE_PLUGIN_ECS_DEPLOY_TARGET_CONTAINER_NAME:-""}
1540
target_port=${BUILDKITE_PLUGIN_ECS_DEPLOY_TARGET_CONTAINER_PORT:-""}
41+
execution_role=${BUILDKITE_PLUGIN_ECS_DEPLOY_EXECUTION_ROLE:-""}
42+
43+
# Resolve any runtime environment variables it has
44+
target_group=$(eval "echo $target_group")
45+
load_balancer_name=$(eval "echo $load_balancer_name")
46+
47+
deployment_config=${BUILDKITE_PLUGIN_ECS_DEPLOY_DEPLOYMENT_CONFIGURATION:-"100/200"}
48+
# shellcheck disable=SC2206
49+
min_max_percent=(${deployment_config//\// })
50+
min_deploy_perc=${min_max_percent[0]}
51+
max_deploy_perc=${min_max_percent[1]}
1652

1753
function create_service() {
1854
local cluster_name=$1
@@ -22,10 +58,18 @@ function create_service() {
2258
local target_group_arguments
2359
target_group_arguments=$(generate_target_group_arguments "$5" "$6" "$7")
2460

61+
# shellcheck disable=SC2016
2562
service_defined=$(aws ecs describe-services --cluster "$cluster_name" --service "$service_name" --query 'services[?status==`ACTIVE`].status' --output text |wc -l)
2663
if [[ $service_defined -eq 0 ]]; then
2764
echo "--- :ecs: Creating a Service $service_name in cluster $cluster_name"
28-
aws ecs create-service --cluster "$cluster_name" --service-name "$service_name" --task-definition "$task_definition" --desired-count "$desired_count" $target_group_arguments
65+
# shellcheck disable=SC2086
66+
aws ecs create-service \
67+
--cluster "$cluster_name" \
68+
--service-name "$service_name" \
69+
--task-definition "$task_definition" \
70+
--desired-count "$desired_count" \
71+
--deployment-configuration "maximumPercent=${max_deploy_perc},minimumHealthyPercent=${min_deploy_perc}" \
72+
$target_group_arguments
2973
fi
3074
}
3175

@@ -34,21 +78,33 @@ function generate_target_group_arguments() {
3478
local target_container=$2
3579
local target_port=$3
3680
local target_group_arguments=""
37-
if [[ -n $target_group ]] && [[ -n $target_container ]] && [[ -n $target_port ]]; then
38-
target_group_arguments="--load-balancers targetGroupArn=${target_group},containerName=${target_container},containerPort=${target_port}"
81+
if [[ -n $target_container ]] && [[ -n $target_port ]]; then
82+
local load_balancer_ref=""
83+
if [[ -n $target_group ]]; then
84+
load_balancer_ref="targetGroupArn=${target_group}"
85+
elif [[ -n $load_balancer_name ]]; then
86+
load_balancer_ref="loadBalancerName=${load_balancer_name}"
87+
else
88+
echo "+++ ^^^"
89+
# shellcheck disable=SC2016
90+
echo '+++ You must specify either `target-group` or `load-balancer-name`'
91+
exit 1
92+
fi
93+
94+
target_group_arguments="--load-balancers ${load_balancer_ref},containerName=${target_container},containerPort=${target_port}"
3995
fi
4096
echo "$target_group_arguments"
4197
}
4298

4399
## This is the template definition of your containers
44-
container_definitions_json=$(jq --arg IMAGE "$image" \
45-
'.[0].image=$IMAGE' \
46-
"$task_definition"
47-
)
48-
49-
echo "jq --arg IMAGE $image \
50-
'.taskDefinition.containerDefinitions[0].image=\$IMAGE' \
51-
$task_definition" > /tmp/foo
100+
image_idx=0
101+
container_definitions_json=$(cat "${task_definition}")
102+
for image in "${images[@]}"; do
103+
container_definitions_json=$(echo "$container_definitions_json" | jq --arg IMAGE "$image" \
104+
".[${image_idx}].image=\$IMAGE"
105+
)
106+
image_idx=$((image_idx+1))
107+
done
52108

53109
echo "--- :ecs: Registering new task definition for ${task_family}"
54110
register_command="aws ecs register-task-definition \
@@ -58,6 +114,11 @@ register_command="aws ecs register-task-definition \
58114
if [[ -n "${task_role_arn}" ]]; then
59115
register_command+=" --task-role-arn ${task_role_arn}"
60116
fi
117+
118+
if [[ -n "${execution_role}" ]]; then
119+
register_command+=" --execution-role-arn ${execution_role}"
120+
fi
121+
61122
json_output=$(eval "$register_command")
62123
register_exit_code=$?
63124

@@ -73,13 +134,17 @@ echo "Registered ${task_family}:${task_revision}"
73134
# Create service if it doesn't already exist
74135
create_service "$cluster" "${task_family}:${task_revision}" "$service_name" "$desired_count" "$target_group" "$target_container" "$target_port"
75136

137+
# shellcheck disable=SC2016
76138
lb_config=$(aws ecs describe-services --cluster "$cluster" --services "$service_name" --query 'services[?status==`ACTIVE`]' |jq -r '.[0].loadBalancers[0]')
77-
error="+++ Cannot update a service to add a load balancer. First delete the service and then run again, or rename the service to force a new one to be created"
139+
error="+++ ^^^
140+
+++ Cannot update a service to add/remove a load balancer. First delete the service and then run again, or rename the service to force a new one to be created"
78141

79142
# No easy way to tell if the target group has changed, since describe-services only returns the load balancer name
80-
if [[ "$lb_config" == "null" ]] && [[ -n $target_group ]]; then
81-
echo "$error"
82-
exit 1
143+
if [[ "$lb_config" == "null" ]]; then
144+
if [[ -n "$target_group" ]] || [[ -n "$load_balancer_name" ]]; then
145+
echo "$error"
146+
exit 1
147+
fi
83148
fi
84149

85150
if [[ "$lb_config" == "null" ]]; then
@@ -88,6 +153,12 @@ if [[ "$lb_config" == "null" ]]; then
88153
elif [[ $(echo "$lb_config" |jq -r '.containerName') != "$target_container" ]] || [[ $(echo "$lb_config" |jq -r '.containerPort') -ne $target_port ]]; then
89154
echo "$error"
90155
exit 1
156+
elif [[ -n "$target_group" ]] && [[ $(echo "$lb_config" |jq -r '.targetGroupArn') != "$target_group" ]]; then
157+
echo "$error"
158+
exit 1
159+
elif [[ -n "$load_balancer_name" ]] && [[ $(echo "$lb_config" |jq -r '.loadBalancerName') != "$load_balancer_name" ]]; then
160+
echo "$error"
161+
exit 1
91162
fi
92163

93164
echo "--- :ecs: Updating service for ${service_name}"

plugin.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@ configuration:
1515
task-family:
1616
type: string
1717
image:
18-
type: string
18+
type: [ string, array ]
1919
desired-count:
2020
type: string
2121
task-role-arn:
2222
type: string
2323
target-group:
2424
type: string
25+
load-balanccer-name:
26+
type: string
2527
target-container-name:
2628
type: string
2729
target-container-port:
2830
type: integer
31+
execution-role:
32+
type: string
33+
deployment-config:
34+
type: string
2935
required:
3036
- cluster
3137
- service

0 commit comments

Comments
 (0)