-
Notifications
You must be signed in to change notification settings - Fork 0
/
git-live
executable file
·366 lines (309 loc) · 9.1 KB
/
git-live
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
#!/bin/bash
set -euo pipefail
# set -x
# 15min
DEFAULT_INTERVAL_SEC="900"
# Remove target directory if it exists, uncomment to disable
DELETE_IF_EXISTS="true"
# ==============================
# Example config
# ==============================
#
# ------------------------------
# Secret
# ------------------------------
# apiVersion: v1
# kind: Secret
# metadata:
# name: git-secret
# type: ananace/git-live
# data:
# username: 'ZXhhbXBsZQo='
# password: 'c2VjcmV0Cg=='
#
# ------------------------------
# Pod
# ------------------------------
# apiVersion: v1
# kind: Pod
# metadata:
# name: git-example
# spec:
# containers:
# - name: busybox
# image: busybox
# command:
# - sh
# - -c
# - |
# ls -l /data
# tail -f /data/README.md
# volumeMounts:
# - name: data
# mountPath: /data
# volumes:
# - name: data
# flexVolume:
# driver: ananace/git-live
# options:
# repo: https://github.com/ananace/flexvolume-git-live
# interval: "1h 30s" # Plain numbers are counted as seconds
# readOnly: true
# secretRef:
# name: git-secret
# ------------------------------
# Uncomment the following lines to see how this plugin is called:
# echo >> /tmp/git-live.log
# date >> /tmp/git-live.log
# echo "$@" >> /tmp/git-live.log
exitNotSupported() {
echo '{"status":"Not supported"}'
exit 1
}
exitError() {
jq -Mcn --arg message "Error in git-live script occurred on line $(caller)" '{"status":"Failure","message":$message}'
exit 1
}
exitFailure() {
local message="${1:-'Unknown error in the ananace/git-live flexvolume script'}"
jq -Mcn --arg message "$message" '{"status":"Failure","message":$message}'
exit 1
}
exitSuccess() {
local message="${1:-'Ok'}"
jq -Mcn --arg message "$message" '{"status":"Success","message":$message}'
exit 0
}
assertBinary() {
local binary="$1"
if ! command -v "$binary" > /dev/null; then
exitFailure "Failed to initialize the ananace/git-live flexvolume, unable to find the $binary binary"
fi
}
parseTimespan() {
timespan=$(printf '%s' "${@}")
local time=0
parts=($(grep -Eo '[[:digit:]]+([^[:digit:]]+)?' <<<"$timespan"))
local timespan
for part in "${parts[@]}"; do
length=${part//[a-zA-Z]/}
scale_unit=${part//[0-9]/}
[[ -z "$length" ]] && scale_unit='err'
case $scale_unit in
y|year|years)
scale=31557600
;;
M|month|months)
scale=2630016
;;
w|week|weeks)
scale=604800
;;
d|day|days)
scale=86400
;;
h|hr|hour|hours)
scale=3600
;;
m|min|minute|minutes)
scale=60
;;
s|sec|second|seconds|'')
scale=1
;;
*)
exitFailure "Unable to parse '${part}' as timespan segment"
esac
time=$((time + (length * scale)))
done
echo "$time"
}
startPuller() {
if [[ -f /run/git-live/active-unit-information ]]; then
# Puller timer is already running
return
fi
# TODO: Allow configuring the background update rate
script="$(realpath "$0")"
if ! unit=$(systemd-run --on-active="30s" --on-unit-active="30s" "$script" git-refresh 2>&1); then
exitFailure 'Failed to start the refresh timer'
fi
local script
echo "$unit" > /run/git-live/active-unit-information
local unit
}
stopPuller() {
if ! [[ -f /run/git-live/active-unit-information ]]; then
# Puller timer is already stopped
return
fi
unit_info=$(cat /run/git-live/active-unit-information)
if [[ "$unit_info" =~ ([^ ]+\.timer) ]]; then
timer_unit=${BASH_REMATCH[1]}
systemctl stop "$timer_unit"
local timer_unit
rm -f /run/git-live/active-unit-information
#else
# TODO Log failure somewhere?
fi
local unit_info
}
init() {
assertBinary awk
assertBinary base64
assertBinary git
assertBinary grep
assertBinary jq
assertBinary realpath
assertBinary sha256sum
assertBinary systemd-run
mkdir -p /run/git-live/indexes
echo '{"status":"Success","message":"The ananace/git-live flexvolume initialized successfully.","capabilities":{"attach":false}}'
}
doTick() {
liveStatus=""
now=$(date +%s)
while read -r repo; do
if ! cd "$repo"; then
echo "Failed to cd to $repo, skipping"
# TODO: Assume missed unmount and clean data?
continue
fi
liveStatus="has active"
# Just to avoid any issues due to the running pod
mountPointSha="$(pwd | sha256sum | awk '{print $1}')"
local gitIndex="/run/git-live/indexes/${mountPointSha}"
local mountPointSha
if ! interval=$(cat "${gitIndex}/git-live-interval" 2>/dev/null); then
interval=$DEFAULT_INTERVAL_SEC
fi
if ! last_update=$(stat "${gitIndex}/git-live-interval" -c %Y 2>/dev/null); then
last_update=0
fi
if [[ $((last_update + interval)) -lt $now ]]; then
echo $interval > "${gitIndex}/git-live-interval"
echo "gitdir: $gitIndex" > .git
if ! ref="$(git symbolic-ref --short HEAD)"; then
echo "Failed to find head for $repo, skipping"
continue
fi
# Run entire git update in an all-or-nothing attempt, and don't abort the
# entire git refresh because a single repo failed to update
(
git remote update origin &>/dev/null && \
git reset -q --hard "origin/$ref" && \
git submodule -q sync --recursive && \
git submodule -q update --init --recursive
) || continue
local ref
fi
local interval
local last_update
done </run/git-live/active-repos
if [[ -z "$liveStatus" ]]; then
echo "No active git repos"
stopPuller
fi
}
doMount() {
if [[ -z ${1:-} || -z ${2:-} ]] ; then
exitFailure "git-live mount: syntax error. usage: git-live mount <mount dir> <json options>"
fi
local mountPoint="$1"
mountPointSha="$(echo "$mountPoint" | sha256sum | awk '{print $1}')"
local gitIndex="/run/git-live/indexes/${mountPointSha}"
local mountPointSha
shift
if [[ -d "$mountPoint" ]] && [[ -d "$gitIndex" ]]; then
exitSuccess "git-live mount: already mounted."
fi
json=$(printf '%s ' "${@}")
if ! jq -e . > /dev/null 2>&1 <<< "$json" ; then
exitFailure "git-live mount: syntax error. invalid json: '$json'"
fi
local options="--separate-git-dir \"${gitIndex}\" --single-branch --depth 1"
if ! repo="$(jq --raw-output -e '.repo' <<< "$json" 2>/dev/null)"; then
exitFailure 'git-live mount: option repo missing in flexvolume configuration.'
fi
if [[ "$repo" != "http"* ]]; then
exitFailure 'git-live mount: currently only supports HTTP(S) checkouts'
fi
if branch="$(jq --raw-output -e '.branch' <<< "$json")"; then
options="$options --branch \"${branch}\""
fi
local branch
if ! interval="$(jq --raw-output -e '.interval' <<< "$json")"; then
interval=$DEFAULT_INTERVAL_SEC
fi
interval="$(parseTimespan "$interval")"
if gitUsernameBase64="$(jq --raw-output -e '.["kubernetes.io/secret/username"]' <<< "$json" 2>/dev/null)"; then
if ! gitPasswordBase64="$(jq --raw-output -e '.["kubernetes.io/secret/password"]' <<< "$json" 2>/dev/null)"; then
exitFailure "git-live mount: password not found. the flexVolume definition must contain a secretRef to a secret with username and password."
fi
if ! gitUsername="$(base64 --decode <<< "$gitUsernameBase64" 2>/dev/null)"; then
exitFailure "git-live mount: username secret is not base64 encoded."
fi
if ! gitPassword="$(base64 --decode <<< "$gitPasswordBase64" 2>/dev/null)"; then
exitFailure "git-live mount: password secret is not base64 encoded."
fi
local gitPasswordBase64
repo="${repo/:\/\//:\/\/${gitUsername}:${gitPassword}@}"
local gitUsername
local gitPassword
fi
local gitUsernameBase64
if [ -n "${DELETE_IF_EXISTS:-}" ]; then
rm --one-file-system -rf "$gitIndex" "$mountPoint" &> /dev/null
fi
if ! eval git clone "$options" -- "$repo" "$mountPoint" &> /dev/null; then
exitFailure 'git-live mount: git clone failed'
fi
cd "$mountPoint"
if ! eval git submodule update --init --recursive &> /dev/null; then
exitFailure 'git-live mount: git submodule init failed'
fi
local repo
echo "$interval" > "${gitIndex}/git-live-interval"
echo "$mountPoint" >> /run/git-live/active-repos
startPuller || true
exitSuccess 'Mount succeeded'
}
doUnmount() {
if [[ -z "${1:-}" ]]; then exitFailure 'git-live unmount: syntax error. Usage: git-live unmount <directory>'; fi
local mountPoint="$1"
if ! [[ -d "$mountPoint" ]]; then exitFailure "git-live unmount: Error, $mountPoint does not exist, can't umount."; fi
mountPointSha="$(echo "$mountPoint" | sha256sum | awk '{print $1}')"
local gitIndex="/run/git-live/indexes/${mountPointSha}"
local mountPointSha
sed -e "\|${mountPoint}|d" -i /run/git-live/active-repos || true
rm -rf "$mountPoint" "$gitIndex"
exitSuccess 'Unmount succeeded'
}
trap exitError ERR
command=${1:-}
if [[ -n $command ]]; then
shift
fi
case "$command" in
init)
init "$@"
;;
deinit)
stopPuller
;;
mount)
doMount "$@"
;;
unmount)
doUnmount "$@"
;;
git-refresh)
doTick "$@"
;;
parse)
parseTimespan "$@"
;;
*)
exitNotSupported
;;
esac