-
Notifications
You must be signed in to change notification settings - Fork 87
/
shepherd
executable file
·216 lines (186 loc) · 8.03 KB
/
shepherd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#!/bin/bash
set -euo pipefail
server_version() {
docker version -f "{{.Server.Version}}"
}
logger() {
local log="$1"
local is_verbose_log="${2:-${VERBOSE:-true}}"
if [[ "$is_verbose_log" == "true" ]]; then
echo "$(date) $log"
fi
}
in_list() {
local list="$1"
local searched_item="$2"
for item in $list; do
[[ "$item" == "$searched_item" ]] && echo "true"
done
echo "false"
}
authenticate_to_registries() {
local config host user password
if [[ -n "$registry_user" ]]; then
logger "Trying to login into registry $registry_host"
echo "$registry_password" | docker login --username="$registry_user" --password-stdin "$registry_host"
fi
if [[ -n "${REGISTRIES_FILE:-""}" ]]; then
while IFS=$'\t' read -r -a reg_info; do
# skip comments and invalid lines
if [[ "${reg_info[0]}" =~ "#" ]] || [[ ${#reg_info[@]} -ne 4 ]]; then
continue
fi
config="${reg_info[0]}"
host="${reg_info[1]}"
user="${reg_info[2]}"
password="${reg_info[3]}"
logger "Trying to login into registry $host for config $config with user $user"
echo "$password" | docker --config "$config" login --username="$user" --password-stdin "$host"
done <"$REGISTRIES_FILE"
fi
}
update_services() {
local ignorelist="$1"
local supports_detach_option=$2
local supports_registry_auth=$3
local supports_insecure_registry=$4
local supports_no_resolve_image=$5
local image_autoclean_limit=$6
local registry_user="$7"
local registry_password="$8"
local registry_host="$9"
local detach_option=""
local registry_auth=""
local insecure_registry_flag=""
local no_resolve_image_flag=""
local name
local apprise_sidecar_url="${APPRISE_SIDECAR_URL:-}"
local hostname
hostname="${HOSTNAME:-$(hostname)}"
[ "$supports_detach_option" = true ] && detach_option="--detach=false"
[ "$supports_registry_auth" = true ] && registry_auth="--with-registry-auth"
[ "$supports_insecure_registry" = true ] && insecure_registry_flag="--insecure"
[ "$supports_no_resolve_image" = true ] && no_resolve_image_flag="--no-resolve-image"
authenticate_to_registries
# iterate over all services
for name in $(IFS=$'\n' docker service ls --quiet --filter "${FILTER_SERVICES}" --format '{{.Name}}'); do
# skip ignored services
if [[ "$(in_list "$ignorelist" "$name")" != "false" ]]; then
continue # continue with next service
fi
local image_with_digest image auth_config config_flag
image_with_digest="$(docker service inspect "$name" -f '{{.Spec.TaskTemplate.ContainerSpec.Image}}')"
image=$(echo "$image_with_digest" | cut -d@ -f1)
auth_config=$(docker service inspect "$name" -f '{{index .Spec.Labels "shepherd.auth.config"}}')
if [[ -z "$auth_config" ]]; then
config_flag=()
else
config_flag=(--config "$auth_config")
fi
if ! DOCKER_CLI_EXPERIMENTAL=enabled docker "${config_flag[@]}" manifest inspect $insecure_registry_flag "$image" > /dev/null; then
logger "Error updating service $name! Image $image does not exist or it is not available"
else
logger "Trying to update service $name with image $image" "true"
num_tasks=$(docker service ls -f "name=$name" | awk 'NR==2{print $4}')
if [[ $num_tasks = 0/* ]]; then
detach_option="--detach=true"
fi
# shellcheck disable=SC2086
if ! timeout "${TIMEOUT:-300}" docker "${config_flag[@]}" service update "$name" $detach_option $registry_auth $no_resolve_image_flag ${UPDATE_OPTIONS} --image="$image" > /dev/null; then
logger "Service $name update failed on $hostname!"
if [[ "${ROLLBACK_ON_FAILURE+x}" ]]; then
logger "Rolling $name back"
# shellcheck disable=SC2086
docker "${config_flag[@]}" service update "$name" $detach_option $registry_auth $no_resolve_image_flag ${ROLLBACK_OPTIONS} --rollback > /dev/null
fi
if [[ "$apprise_sidecar_url" != "" ]]; then
title="[Shepherd] Service $name update failed on $hostname"
body="$(date) Service $name failed to update to $(docker service inspect "$name" -f '{{.Spec.TaskTemplate.ContainerSpec.Image}}')"
curl -X POST -H "Content-Type: application/json" --data "{\"title\": \"$title\", \"body\": \"$body\"}" "$apprise_sidecar_url"
fi
continue # continue with next service
fi
local previous_image current_image
previous_image=$(docker service inspect "$name" -f '{{.PreviousSpec.TaskTemplate.ContainerSpec.Image}}')
current_image=$(docker service inspect "$name" -f '{{.Spec.TaskTemplate.ContainerSpec.Image}}')
if [ "$previous_image" == "$current_image" ]; then
logger "No updates to service $name!" "true"
else
logger "Service $name was updated!"
if [[ "$apprise_sidecar_url" != "" ]]; then
title="[Shepherd] Service $name updated on $hostname"
body="$(date) Service $name was updated from $previous_image to $current_image"
curl -X POST -H "Content-Type: application/json" --data "{\"title\": \"$title\", \"body\": \"$body\"}" "$apprise_sidecar_url"
fi
if [[ "$image_autoclean_limit" != "" ]]; then
logger "Cleaning up old docker images, leaving last $image_autoclean_limit"
img_name=$(docker service inspect "$name" -f '{{.Spec.TaskTemplate.ContainerSpec.Image}}' | awk -F':' '{print $1}')
image_ids=$(docker images -aq --filter=reference="$img_name")
image_ids_count=$(echo "$image_ids" | wc -w)
if [[ $image_ids_count > $image_autoclean_limit ]]; then
docker container prune -f
read -ra images_to_remove < <(echo "$image_ids" | xargs -n 1 | tail -n $(("$image_ids_count" - "$image_autoclean_limit")))
docker rmi "${images_to_remove[@]}"
fi
fi
fi
fi
done
}
# retrieve registry password from docker secrets or environment, in this order
get_registry_password() {
local password_file_path="/run/secrets/shepherd_registry_password"
if [[ -f "$password_file_path" ]]; then
cat "$password_file_path"
else
echo "${REGISTRY_PASSWORD:-}"
fi
}
main() {
local sleep_time supports_detach_option supports_registry_auth image_autoclean_limit ignorelist
local registry_user=""
local registry_password=""
local registry_host=""
ignorelist="${IGNORELIST_SERVICES:-}"
sleep_time="${SLEEP_TIME:-5m}"
image_autoclean_limit="${IMAGE_AUTOCLEAN_LIMIT:-}"
logger "Timezone set to $TZ"
supports_detach_option=false
if [[ $(expr "$(server_version)" ">=" "17.05") == "1" ]]; then
supports_detach_option=true
logger "Enabling synchronous service updates"
fi
supports_registry_auth=false
if [[ ${REGISTRY_USER+x} ]]; then
supports_registry_auth=true
registry_user="${REGISTRY_USER}"
registry_password="$(get_registry_password)"
registry_host="${REGISTRY_HOST:-}"
logger "Send given registry authentication details to swarm agents"
fi
if [[ ${WITH_REGISTRY_AUTH+x} ]]; then
supports_registry_auth=true
logger "Send registry authentication details to swarm agents"
fi
supports_insecure_registry=false
if [[ ${WITH_INSECURE_REGISTRY+x} ]]; then
supports_insecure_registry=true
logger "Connection to insecure registry available"
fi
supports_no_resolve_image=false
if [[ ${WITH_NO_RESOLVE_IMAGE+x} ]]; then
supports_no_resolve_image=true
logger "Deployment without resolving image"
fi
[[ "$ignorelist" != "" ]] && logger "Excluding services: $ignorelist"
if [[ ${RUN_ONCE_AND_EXIT+x} ]]; then
update_services "$ignorelist" "$supports_detach_option" "$supports_registry_auth" "$supports_insecure_registry" "$supports_no_resolve_image" "$image_autoclean_limit" "$registry_user" "$registry_password" "$registry_host"
else
while true; do
update_services "$ignorelist" "$supports_detach_option" "$supports_registry_auth" "$supports_insecure_registry" "$supports_no_resolve_image" "$image_autoclean_limit" "$registry_user" "$registry_password" "$registry_host"
logger "Sleeping $sleep_time before next update" "true"
sleep "$sleep_time"
done
fi
}
main "$@"