Skip to content

Commit fb591eb

Browse files
authored
Merge pull request #26 from improbable-eng/support-gcr-1
2 parents 8e735da + 4f78c96 commit fb591eb

12 files changed

+582
-206
lines changed

README.md

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -152,49 +152,74 @@ steps:
152152
- docker#v3.3.0
153153
```
154154

155-
### Specifying an ECR repository name
155+
### Changing the max cache time
156156

157-
The plugin pushes and pulls Docker images to and from an ECR repository named
158-
`build-cache/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}`. You can
159-
optionally use a custom repository name:
157+
By default images are kept in ECR for up to 30 days. This can be changed by specifying a `max-age-days` parameter:
160158

161159
```yaml
162160
steps:
163161
- command: 'echo wow'
164162
plugins:
165163
- seek-oss/docker-ecr-cache#v1.7.0:
166-
ecr-name: my-unique-repository-name
167-
ecr-tags:
168-
Key: Value
169-
Key2: Value2
164+
max-age-days: 7
170165
- docker#v3.3.0
171166
```
172167

173-
### Changing the max cache time
168+
### Changing the name of exported variable
174169

175-
By default images are kept in ECR for up to 30 days. This can be changed by specifying a `max-age-days` parameter:
170+
By default image name and computed tag are exported to the Docker buildkite plugin env variable `BUILDKITE_PLUGIN_DOCKER_IMAGE`. In order to chain the plugin with a different plugin, this can be changed by specifying a `export-env-variable` parameter:
176171

177172
```yaml
178173
steps:
179174
- command: 'echo wow'
180175
plugins:
181176
- seek-oss/docker-ecr-cache#v1.7.0:
182-
max-age-days: 7
183-
- docker#v3.3.0
177+
export-env-variable: BUILDKITE_PLUGIN_MY_CUSTOM_PLUGIN_CACHE_IMAGE
178+
- my-custom-plugin#v1.0.0:
184179
```
185180

186-
### Changing the name of exported variable
181+
### AWS ECR specific options
187182

188-
By default image name and computed tag are exported to the Docker buildkite plugin env variable `BUILDKITE_PLUGIN_DOCKER_IMAGE`. In order to chain the plugin with a different plugin, this can be changed by specifying a `export-env-variable` parameter:
183+
#### Specifying an ECR repository name
184+
185+
The plugin pushes and pulls Docker images to and from an ECR repository named
186+
`build-cache/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}`. You can
187+
optionally use a custom repository name:
189188

190189
```yaml
191190
steps:
192191
- command: 'echo wow'
193192
plugins:
194193
- seek-oss/docker-ecr-cache#v1.7.0:
195-
export-env-variable: BUILDKITE_PLUGIN_MY_CUSTOM_PLUGIN_CACHE_IMAGE
196-
- my-custom-plugin#v1.0.0
197-
```
194+
ecr-name: my-unique-repository-name
195+
ecr-tags:
196+
Key: Value
197+
Key2: Value2
198+
- docker#v3.3.0
199+
```
200+
201+
### GCP GCR specific configuration
202+
203+
[Overview of Google Container Registry](https://cloud.google.com/container-registry/docs/overview)
204+
205+
Example:
206+
207+
```yaml
208+
- command: 'echo wow'
209+
plugins:
210+
- seek-oss/docker-ecr-cache#v1.7.0:
211+
registry-provider: gcr
212+
gcp-project: foo-bar-123456
213+
```
214+
215+
#### Required GCR configuration
216+
217+
- `registry-provider`: this must be `gcr` to aim at a google container registry.
218+
- `gcp-project`: this must be supplied. It is the GCP project your GCR is set up inside.
219+
220+
#### Optional GCR configuration
221+
222+
- `registry-hostname` (default: `gcr.io`). The location your image will be stored. [See upstream docs](https://cloud.google.com/container-registry/docs/overview#registry_name) for options.
198223

199224
## Design
200225

@@ -218,7 +243,7 @@ automatically applied to expire images after 30 days (configurable via `max-age-
218243

219244
To run the tests of this plugin, run
220245

221-
```
246+
```bash
222247
docker-compose run --rm tests
223248
```
224249

changelog.md

Whitespace-only changes.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
login() {
2+
$(aws ecr get-login --no-include-email)
3+
}
4+
5+
get_registry_url() {
6+
local repository_name
7+
repository_name="$(get_ecr_repository_name)"
8+
aws ecr describe-repositories \
9+
--repository-names "${repository_name}" \
10+
--output text \
11+
--query 'repositories[0].repositoryUri'
12+
}
13+
14+
ecr_exists() {
15+
local repository_name="${1}"
16+
aws ecr describe-repositories \
17+
--repository-names "${repository_name}" \
18+
--output text \
19+
--query 'repositories[0].registryId'
20+
}
21+
22+
get_ecr_arn() {
23+
local repository_name="${1}"
24+
aws ecr describe-repositories \
25+
--repository-names "${repository_name}" \
26+
--output text \
27+
--query 'repositories[0].repositoryArn'
28+
}
29+
30+
get_ecr_tags() {
31+
local result=$(cat <<EOF
32+
{
33+
"tags": []
34+
}
35+
EOF
36+
)
37+
while IFS='=' read -r name _ ; do
38+
if [[ $name =~ ^(BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_ECR_TAGS_) ]] ; then
39+
# Handle plain key=value, e.g
40+
# ecr-tags:
41+
# KEY_NAME: 'key-value'
42+
key_name=$(echo "${name}" | sed 's/^BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_ECR_TAGS_//')
43+
key_value=$(env | grep "$name" | sed "s/^$name=//")
44+
result=$(echo $result | jq ".tags[.tags| length] |= . + {\"Key\": \"${key_name}\", \"Value\": \"${key_value}\"}")
45+
fi
46+
done < <(env | sort)
47+
48+
echo $result
49+
}
50+
51+
get_ecr_repository_name() {
52+
echo "${BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_ECR_NAME:-"$(get_default_image_name)"}"
53+
}
54+
55+
configure_registry_for_image_if_necessary() {
56+
local repository_name
57+
repository_name="$(get_ecr_repository_name)"
58+
local max_age_days="${BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_MAX_AGE_DAYS:-30}"
59+
local ecr_tags="$(get_ecr_tags)"
60+
local num_tags=$(echo $ecr_tags | jq '.tags | length')
61+
62+
if ! ecr_exists "${repository_name}"; then
63+
aws ecr create-repository --repository-name "${repository_name}" --cli-input-json "${ecr_tags}"
64+
else
65+
if [ "$num_tags" -gt 0 ]; then
66+
local ecr_arn=$(get_ecr_arn "${repository_name}")
67+
aws ecr tag-resource --resource-arn ${ecr_arn} --cli-input-json "${ecr_tags}"
68+
fi
69+
fi
70+
71+
# As of May 2019 ECR lifecycle policies can only have one rule that targets "any"
72+
# Due to this limitation, only the max_age policy is applied
73+
policy_text=$(cat <<EOF
74+
{
75+
"rules": [
76+
{
77+
"rulePriority": 1,
78+
"description": "Expire images older than ${max_age_days} days",
79+
"selection": {
80+
"tagStatus": "any",
81+
"countType": "sinceImagePushed",
82+
"countUnit": "days",
83+
"countNumber": ${max_age_days}
84+
},
85+
"action": {
86+
"type": "expire"
87+
}
88+
}
89+
]
90+
}
91+
EOF
92+
)
93+
94+
# Always set the lifecycle policy to update repositories automatically
95+
# created before PR #9.
96+
#
97+
# When using a custom repository with a restricted Buildkite role this might
98+
# not succeed. Ignore the error and let the build continue.
99+
aws ecr put-lifecycle-policy \
100+
--repository-name "${repository_name}" \
101+
--lifecycle-policy-text "${policy_text}" || true
102+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
login() {
2+
# Currently assume the use of docker-credential-gcr to manage AuthN transparently.
3+
echo "Plugin currently assumes that docker-credential-gcr is on PATH and configured. See https://github.com/GoogleCloudPlatform/docker-credential-gcr#configuration-and-usage if later docker pull/push fail."
4+
}
5+
6+
configure_registry_for_image_if_necessary() {
7+
# GCR does not have a concept of a repository for images within a registry like ECR does.
8+
echo ""
9+
}
10+
11+
get_registry_url() {
12+
if [[ -z "${BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_GCP_PROJECT:-}" ]]; then
13+
log_fatal "gcp-project in plugin settings must have a value." 34
14+
fi
15+
if [[ -z "${BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_REGISTRY_HOSTNAME:-}" ]]; then
16+
echoerr "registry-hostname had no value, defaulting to gcr.io"
17+
BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_REGISTRY_HOSTNAME="gcr.io"
18+
fi
19+
echo "${BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_REGISTRY_HOSTNAME}/${BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_GCP_PROJECT}/${BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_ECR_NAME:-"$(get_default_image_name)"}"
20+
}

hooks/lib/stdlib.bash

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
echoerr() {
2+
echo "$@" 1>&2;
3+
}
4+
5+
log_fatal() {
6+
echoerr "In $(pwd)"
7+
echoerr "${@}"
8+
# use the last argument as the exit code
9+
exit_code="${*: -1}"
10+
if [[ "${exit_code}" =~ ^[0-9]+$ ]]; then
11+
exit "${exit_code}"
12+
fi
13+
exit 1
14+
}
15+
16+
read_build_args() {
17+
read_list_property 'BUILD_ARGS'
18+
for arg in ${result[@]+"${result[@]}"}; do
19+
build_args+=("--build-arg=${arg}")
20+
done
21+
}
22+
23+
# read a plugin property of type [array, string] into a Bash array. Buildkite
24+
# exposes a string value at BUILDKITE_PLUGIN_{NAME}_{KEY}, and array values at
25+
# BUILDKITE_PLUGIN_{NAME}_{KEY}_{IDX}.
26+
read_list_property() {
27+
local base_name="BUILDKITE_PLUGIN_DOCKER_ECR_CACHE_${1}"
28+
29+
result=()
30+
31+
if [[ -n ${!base_name:-} ]]; then
32+
result+=("${!base_name}")
33+
fi
34+
35+
while IFS='=' read -r item_name _; do
36+
if [[ ${item_name} =~ ^(${base_name}_[0-9]+) ]]; then
37+
result+=("${!item_name}")
38+
fi
39+
done < <(env | sort)
40+
}
41+
42+
get_default_image_name() {
43+
echo "build-cache/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}"
44+
}
45+
46+
compute_tag() {
47+
local docker_file="$1"
48+
local sums
49+
50+
echoerr '--- Computing tag'
51+
52+
echoerr 'DOCKERFILE'
53+
echoerr "+ ${docker_file}:${target:-"<target>"}"
54+
sums="$(sha1sum "${docker_file}")"
55+
sums+="$(echo "${target}" | sha1sum)"
56+
57+
echoerr 'ARCHITECTURE'
58+
echoerr "+ $(uname -m)"
59+
sums+="$(uname -m | sha1sum)"
60+
61+
echoerr 'BUILD_ARGS'
62+
read_list_property 'BUILD_ARGS'
63+
for arg in ${result[@]+"${result[@]}"}; do
64+
echoerr "+ ${arg}"
65+
66+
# include underlying environment variable after echo
67+
if [[ ${arg} != *=* ]]; then
68+
arg+="=${!arg:-}"
69+
fi
70+
71+
sums+="$(echo "${arg}" | sha1sum)"
72+
done
73+
74+
# expand ** in cache-on properties
75+
shopt -s globstar
76+
77+
echoerr 'CACHE_ON'
78+
read_list_property 'CACHE_ON'
79+
for glob in ${result[@]+"${result[@]}"}; do
80+
echoerr "${glob}"
81+
for file in ${glob}; do
82+
echoerr "+ ${file}"
83+
sums+="$(sha1sum "${file}")"
84+
done
85+
done
86+
87+
echo "${sums}" | sha1sum | cut -c-7
88+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
login() {
2+
echo "stubbed login"
3+
}
4+
5+
configure_registry_for_image_if_necessary() {
6+
echo "stubbed configure_registry_for_image_if_necessary"
7+
}
8+
9+
get_registry_url() {
10+
echo "pretend.host/path/segment/image"
11+
}
12+
13+
# BATS/bats-mock does not allow stubbing a function, currently.
14+
# So, override it to avoid needing to repeat all the stub'd sha1sum etc inside the end-to-end tests.
15+
compute_tag() {
16+
echo "stubbed-computed-tag"
17+
}

0 commit comments

Comments
 (0)