1 |
commit: b7c0b89992e7b3673ad3e3ba667b81ce9868b69c |
2 |
Author: Fabian Groffen <grobian <AT> gentoo <DOT> org> |
3 |
AuthorDate: Thu Feb 21 11:37:09 2019 +0000 |
4 |
Commit: Fabian Groffen <grobian <AT> gentoo <DOT> org> |
5 |
CommitDate: Thu Feb 21 11:37:09 2019 +0000 |
6 |
URL: https://gitweb.gentoo.org/repo/proj/prefix.git/commit/?id=b7c0b899 |
7 |
|
8 |
scripts/auto-bootstraps: scripts to perform unattended bootstraps |
9 |
|
10 |
This includes the scripts that generate the results output of |
11 |
bootstrap.prefix.bitzolder.nl. |
12 |
|
13 |
Signed-off-by: Fabian Groffen <grobian <AT> gentoo.org> |
14 |
|
15 |
scripts/auto-bootstraps/analyse_result.py | 178 ++++++++++++++++++++++++++++ |
16 |
scripts/auto-bootstraps/dobootstrap | 167 ++++++++++++++++++++++++++ |
17 |
scripts/auto-bootstraps/process_uploads.sh | 60 ++++++++++ |
18 |
scripts/auto-bootstraps/update_distfiles.py | 29 +++++ |
19 |
4 files changed, 434 insertions(+) |
20 |
|
21 |
diff --git a/scripts/auto-bootstraps/analyse_result.py b/scripts/auto-bootstraps/analyse_result.py |
22 |
new file mode 100755 |
23 |
index 0000000000..885c7fc9e7 |
24 |
--- /dev/null |
25 |
+++ b/scripts/auto-bootstraps/analyse_result.py |
26 |
@@ -0,0 +1,178 @@ |
27 |
+#!/usr/bin/env python3 |
28 |
+ |
29 |
+import os |
30 |
+import glob |
31 |
+import re |
32 |
+import time |
33 |
+import html |
34 |
+ |
35 |
+resultsdir='./results' |
36 |
+ |
37 |
+def find_last_stage(d): |
38 |
+ """ |
39 |
+ Returns the last stage worked on. |
40 |
+ Bootstraps define explicitly stages 1, 2 and 3, we define some more |
41 |
+ on top of those as follows: |
42 |
+ 0 - bootstrap didn't even start (?!?) or unknown status |
43 |
+ 1 - stage 1 failed |
44 |
+ 2 - stage 2 failed |
45 |
+ 3 - stage 3 failed |
46 |
+ 4 - emerge -e world failed |
47 |
+ 5 - finished successfully |
48 |
+ """ |
49 |
+ |
50 |
+ def stage_success(stagelog): |
51 |
+ with open(stagelog, 'rb') as f: |
52 |
+ line = f.readlines()[-1] |
53 |
+ res = re.match(r'^\* stage[123] successfully finished', |
54 |
+ line.decode('utf-8', 'ignore')) |
55 |
+ return res is not None |
56 |
+ |
57 |
+ if not os.path.exists(os.path.join(d, '.stage1-finished')): |
58 |
+ log = os.path.join(d, 'stage1.log') |
59 |
+ if not os.path.exists(log): |
60 |
+ return 0 # nothing exists, assume not started |
61 |
+ if not stage_success(log): |
62 |
+ return 1 |
63 |
+ |
64 |
+ if not os.path.exists(os.path.join(d, '.stage2-finished')): |
65 |
+ log = os.path.join(d, 'stage2.log') |
66 |
+ if not os.path.exists(log) or not stage_success(log): |
67 |
+ return 2 # stage1 was success, so 2 must have failed |
68 |
+ |
69 |
+ if not os.path.exists(os.path.join(d, '.stage3-finished')): |
70 |
+ log = os.path.join(d, 'stage3.log') |
71 |
+ if not os.path.exists(log) or not stage_success(log): |
72 |
+ return 3 # stage2 was success, so 3 must have failed |
73 |
+ |
74 |
+ # if stage 3 was success, we went onto emerge -e system, if that |
75 |
+ # failed, portage would have left a build.log behind |
76 |
+ logs = glob.glob(d + "/portage/*/*/temp/build.log") |
77 |
+ if len(logs) > 0: |
78 |
+ return 4 |
79 |
+ |
80 |
+ # ok, so it must have been all good then |
81 |
+ return 5 |
82 |
+ |
83 |
+def get_err_reason(arch, dte, err): |
84 |
+ rdir = os.path.join(resultsdir, arch, '%d' % dte) |
85 |
+ |
86 |
+ if err == 0: |
87 |
+ return "bootstrap failed to start" |
88 |
+ if err >= 1 and err <= 3: |
89 |
+ stagelog = os.path.join(rdir, 'stage%d.log' % err) |
90 |
+ if os.path.exists(stagelog): |
91 |
+ line = None |
92 |
+ with open(stagelog, 'rb') as f: |
93 |
+ errexp = re.compile(r'^( \* (ERROR:|Fetch failed for)|emerge: there are no) ') |
94 |
+ for line in f: |
95 |
+ res = errexp.match(line.decode('utf-8', 'ignore')) |
96 |
+ if res: |
97 |
+ break |
98 |
+ if not line: |
99 |
+ return '<a href="%s/stage%d.log">stage %d</a> failed' % \ |
100 |
+ (os.path.join(arch, '%d' % dte), err, err) |
101 |
+ return '<a href="%s/stage%d.log">stage %d</a> failed<br />%s' % \ |
102 |
+ (os.path.join(arch, '%d' % dte), err, err, \ |
103 |
+ html.escape(line.decode('utf-8', 'ignore'))) |
104 |
+ else: |
105 |
+ return 'stage %d did not start' % err |
106 |
+ if err == 4: |
107 |
+ msg = "'emerge -e system' failed while emerging" |
108 |
+ logs = glob.glob(rdir + "/portage/*/*/temp/build.log") |
109 |
+ for log in logs: |
110 |
+ cat, pkg = log.split('/')[-4:-2] |
111 |
+ msg = msg + ' <a href="%s/temp/build.log">%s/%s</a>' % \ |
112 |
+ (os.path.join(arch, '%d' % dte, "portage", cat, pkg), \ |
113 |
+ cat, pkg) |
114 |
+ return msg |
115 |
+ |
116 |
+def analyse_arch(d): |
117 |
+ last_fail = None |
118 |
+ last_succ = None |
119 |
+ fail_state = None |
120 |
+ with os.scandir(d) as it: |
121 |
+ for f in sorted(it, key=lambda x: (x.is_dir(), x.name), reverse=True): |
122 |
+ if not f.is_dir(follow_symlinks=False): |
123 |
+ continue |
124 |
+ date = int(f.name) |
125 |
+ res = find_last_stage(os.path.join(d, f.name)) |
126 |
+ if res == 5: |
127 |
+ if not last_succ: |
128 |
+ last_succ = date |
129 |
+ elif not last_fail: |
130 |
+ last_fail = date |
131 |
+ fail_state = res |
132 |
+ if last_succ and last_fail: |
133 |
+ break |
134 |
+ |
135 |
+ return (last_fail, fail_state, last_succ) |
136 |
+ |
137 |
+archs = {} |
138 |
+with os.scandir(resultsdir) as it: |
139 |
+ for f in sorted(it, key=lambda x: (x.is_dir(), x.name)): |
140 |
+ if not f.is_dir(follow_symlinks=False): |
141 |
+ continue |
142 |
+ arch = f.name |
143 |
+ fail, state, suc = analyse_arch(os.path.join(resultsdir, arch)) |
144 |
+ archs[arch] = (fail, state, suc) |
145 |
+ if not suc: |
146 |
+ color = '\033[1;31m' # red |
147 |
+ elif fail and suc < fail: |
148 |
+ color = '\033[1;33m' # yellow |
149 |
+ else: |
150 |
+ color = '\033[1;32m' # green |
151 |
+ endc = '\033[0m' |
152 |
+ print("%s%24s: suc %8s fail %8s%s" % (color, arch, suc, fail, endc)) |
153 |
+ |
154 |
+# generate html edition |
155 |
+with open(os.path.join(resultsdir, 'index.html'), "w") as h: |
156 |
+ h.write("<html>") |
157 |
+ h.write("<head><title>Gentoo Prefix bootstrap results</title></head>") |
158 |
+ h.write("<body>") |
159 |
+ h.write("<h2>Gentoo Prefix bootstraps</h2>") |
160 |
+ h.write('<table border="1px">') |
161 |
+ h.write("<th>architecture</th>") |
162 |
+ h.write("<th>last successful run</th><th>last failed run</th>") |
163 |
+ h.write("<th>failure</th>") |
164 |
+ for arch in archs: |
165 |
+ fail, errcode, suc = archs[arch] |
166 |
+ if not suc: |
167 |
+ state = 'red' |
168 |
+ elif fail and suc < fail: |
169 |
+ state = 'orange' |
170 |
+ else: |
171 |
+ state = 'limegreen' |
172 |
+ |
173 |
+ h.write('<tr>') |
174 |
+ |
175 |
+ h.write('<td bgcolor="%s" nowrap="nowrap">' % state) |
176 |
+ h.write(arch) |
177 |
+ h.write("</td>") |
178 |
+ |
179 |
+ h.write("<td>") |
180 |
+ if suc: |
181 |
+ h.write('<a href="%s/%s">%s</a>' % (arch, suc, suc)) |
182 |
+ else: |
183 |
+ h.write('<i>never</i>') |
184 |
+ h.write("</td>") |
185 |
+ |
186 |
+ h.write("<td>") |
187 |
+ if fail: |
188 |
+ h.write('<a href="%s/%s">%s</a>' % (arch, fail, fail)) |
189 |
+ else: |
190 |
+ h.write('<i>never</i>') |
191 |
+ h.write("</td>") |
192 |
+ |
193 |
+ h.write("<td>") |
194 |
+ if fail and (not suc or fail > suc): |
195 |
+ h.write(get_err_reason(arch, fail, errcode)) |
196 |
+ h.write("</td>") |
197 |
+ |
198 |
+ h.write("</tr>") |
199 |
+ h.write("</table>") |
200 |
+ now = time.strftime('%Y-%m-%d %H:%M', time.gmtime()) |
201 |
+ h.write("<p><i>generated: %s</i></p>" % now) |
202 |
+ h.write("<p>See also <a href='https://dev.azure.com/12719821/12719821/_build?definitionId=6'>awesomebytes</a></p>") |
203 |
+ h.write("</body>") |
204 |
+ h.write("</html>") |
205 |
|
206 |
diff --git a/scripts/auto-bootstraps/dobootstrap b/scripts/auto-bootstraps/dobootstrap |
207 |
new file mode 100755 |
208 |
index 0000000000..521f644acf |
209 |
--- /dev/null |
210 |
+++ b/scripts/auto-bootstraps/dobootstrap |
211 |
@@ -0,0 +1,167 @@ |
212 |
+#!/usr/bin/env bash |
213 |
+ |
214 |
+BOOTSTRAP="${BASH_SOURCE[0]%/*}/bootstrap-prefix.sh" |
215 |
+BOOTURL="http://rsync.prefix.bitzolder.nl/scripts/bootstrap-prefix.sh" |
216 |
+UPLOAD="rsync1.prefix.bitzolder.nl::gentoo-portage-bootstraps" |
217 |
+ |
218 |
+do_fetch() { |
219 |
+ local FETCHCOMMAND |
220 |
+ # Try to find a download manager, we only deal with wget, |
221 |
+ # curl, FreeBSD's fetch and ftp. |
222 |
+ if [[ x$(type -t wget) == "xfile" ]] ; then |
223 |
+ FETCH_COMMAND="wget -O -" |
224 |
+ [[ $(wget -h) == *"--no-check-certificate"* ]] && \ |
225 |
+ FETCH_COMMAND+=" --no-check-certificate" |
226 |
+ elif [[ x$(type -t curl) == "xfile" ]] ; then |
227 |
+ FETCH_COMMAND="curl -f -L" |
228 |
+ else |
229 |
+ echo "could not download ${1##*/}" |
230 |
+ exit 1 |
231 |
+ fi |
232 |
+ |
233 |
+ ${FETCH_COMMAND} "${*}" || exit 1 |
234 |
+} |
235 |
+ |
236 |
+do_prepare() { |
237 |
+ local bitw=$1 |
238 |
+ local dte=$2 |
239 |
+ local bootstrap |
240 |
+ |
241 |
+ if [[ -n ${RESUME} && -n ${bitw} && -n ${dte} ]] ; then |
242 |
+ bootstrap=bootstrap${bitw}-${dte}/bootstrap-prefix.sh |
243 |
+ elif [[ -n ${DOLOCAL} ]] ; then |
244 |
+ bootstrap=${BOOTSTRAP} |
245 |
+ else |
246 |
+ bootstrap=dobootstrap-do_prepare-$$ |
247 |
+ do_fetch ${BOOTURL} > ${bootstrap} |
248 |
+ fi |
249 |
+ |
250 |
+ local chost=$(${BASH} ${bootstrap} chost.guess x) |
251 |
+ case ${chost} in |
252 |
+ *86-*) |
253 |
+ if [[ ${bitw} == 64 ]] ; then |
254 |
+ chost=x86_64-${chost#*-} |
255 |
+ else |
256 |
+ bitw=32 |
257 |
+ chost=i386-${chost#*-} |
258 |
+ fi |
259 |
+ ;; |
260 |
+ x86_64-*) |
261 |
+ if [[ ${bitw} == 32 ]] ; then |
262 |
+ chost=i386-${chost#*-} |
263 |
+ else |
264 |
+ bitw=64 |
265 |
+ chost=x86_64-${chost#*-} |
266 |
+ fi |
267 |
+ ;; |
268 |
+ powerpc-*) |
269 |
+ bitw=32 |
270 |
+ ;; |
271 |
+ sparc-*) |
272 |
+ if [[ ${bitw} == 64 ]] ; then |
273 |
+ chost=sparcv9-${chost#*-} |
274 |
+ else |
275 |
+ bitw=32 |
276 |
+ chost=sparc-${chost#*-} |
277 |
+ fi |
278 |
+ ;; |
279 |
+ sparcv9-*|sparc64-*) |
280 |
+ if [[ ${bitw} == 32 ]] ; then |
281 |
+ chost=sparc-${chost#*-} |
282 |
+ else |
283 |
+ bitw=64 |
284 |
+ chost=sparcv9-${chost#*-} |
285 |
+ fi |
286 |
+ ;; |
287 |
+ *) |
288 |
+ echo "unhandled CHOST: ${chost}" |
289 |
+ rm -f dobootstrap-do_prepare-$$ |
290 |
+ exit 1 |
291 |
+ ;; |
292 |
+ esac |
293 |
+ |
294 |
+ [[ -z ${dte} ]] && dte=$(date "+%Y%m%d") |
295 |
+ EPREFIX=${PWD}/bootstrap${bitw}-${dte} |
296 |
+ [[ -n ${OVERRIDE_EPREFIX} ]] && EPREFIX=${OVERRIDE_EPREFIX} |
297 |
+ |
298 |
+ local bootstrapscript=$(realpath ${BASH_SOURCE[0]} 2>/dev/null) |
299 |
+ if [[ -z ${bootstrapscript} ]] ; then |
300 |
+ local b=${BASH_SOURCE[0]} |
301 |
+ cd "${b%/*}" |
302 |
+ bootstrapscript=$(pwd -P)/${b##*/} |
303 |
+ fi |
304 |
+ echo "EPREFIX=${EPREFIX}" |
305 |
+ mkdir -p "${EPREFIX}" |
306 |
+ if [[ ${bootstrap} == dobootstrap-do_prepare-$$ ]] ; then |
307 |
+ mv "${bootstrap}" "${EPREFIX}"/bootstrap-prefix.sh |
308 |
+ elif [[ ${bootstrap} != "${EPREFIX}"/bootstrap-prefix.sh ]] ; then |
309 |
+ cp "${bootstrap}" "${EPREFIX}"/bootstrap-prefix.sh |
310 |
+ fi |
311 |
+ cd "${EPREFIX}" || exit 1 |
312 |
+ |
313 |
+ # optional program to keep the machine from sleeping |
314 |
+ # macOS/BSD: caffeinate |
315 |
+ keepalive=$(type -P caffeinate) |
316 |
+ [[ -x ${keepalive} ]] && keepalive+=" -i -m -s" || keepalive= |
317 |
+ |
318 |
+ env -i \ |
319 |
+ HOME=${EPREFIX} \ |
320 |
+ SHELL=/bin/bash \ |
321 |
+ TERM=${TERM} \ |
322 |
+ USER=${USER} \ |
323 |
+ CHOST=${chost} \ |
324 |
+ GENTOO_MIRRORS="http://distfileslocal/" \ |
325 |
+ EPREFIX=${EPREFIX} \ |
326 |
+ ${DOLOCAL+DOLOCAL=1} \ |
327 |
+ ${RESUME+RESUME=1} \ |
328 |
+ ${LATEST_TREE_YES+LATEST_TREE_YES=1} \ |
329 |
+ ${TREE_FROM_SRC+TREE_FROM_SRC=}${TREE_FROM_SRC} \ |
330 |
+ ${keepalive} /bin/bash -l -c "${BASH} ${bootstrapscript} bootstrap" |
331 |
+ |
332 |
+ if [[ -n ${DOPUBLISH} ]] ; then |
333 |
+ rsync -q /dev/null ${UPLOAD}/${HOSTNAME}-$$/ |
334 |
+ rsync -q /dev/null ${UPLOAD}/${HOSTNAME}-$$/${chost}/ |
335 |
+ rsync -rltv \ |
336 |
+ --exclude=work/ \ |
337 |
+ --exclude=homedir/ \ |
338 |
+ --exclude=files \ |
339 |
+ --exclude=distdir/ \ |
340 |
+ --exclude=image/ \ |
341 |
+ {stage,.stage}* \ |
342 |
+ bootstrap-prefix.sh \ |
343 |
+ startprefix \ |
344 |
+ usr/portage/distfiles \ |
345 |
+ var/tmp/portage \ |
346 |
+ var/log/emerge.log \ |
347 |
+ ${UPLOAD}/${HOSTNAME}-$$/${chost}/${dte}/ |
348 |
+ rsync -q /dev/null ${UPLOAD}/${HOSTNAME}-$$/${chost}/${dte}/push-complete/ |
349 |
+ fi |
350 |
+} |
351 |
+ |
352 |
+do_bootstrap() { |
353 |
+ chmod 755 bootstrap-prefix.sh || exit 1 |
354 |
+ ${BASH} ./bootstrap-prefix.sh ${EPREFIX} noninteractive |
355 |
+} |
356 |
+ |
357 |
+case $1 in |
358 |
+ bootstrap) |
359 |
+ do_bootstrap |
360 |
+ ;; |
361 |
+ local) |
362 |
+ export DOLOCAL=1 |
363 |
+ do_prepare $2 |
364 |
+ ;; |
365 |
+ resume) |
366 |
+ export RESUME=1 |
367 |
+ do_prepare "$2" ${3:-${BOOTSTRAP_DATE}} |
368 |
+ ;; |
369 |
+ *) |
370 |
+ if [[ ${0} == /net/* ]] ; then |
371 |
+ echo "internal host, activating local and DOPUBLISH" |
372 |
+ export DOLOCAL=1 |
373 |
+ export DOPUBLISH=1 |
374 |
+ fi |
375 |
+ do_prepare $1 |
376 |
+ ;; |
377 |
+esac |
378 |
+ |
379 |
|
380 |
diff --git a/scripts/auto-bootstraps/process_uploads.sh b/scripts/auto-bootstraps/process_uploads.sh |
381 |
new file mode 100755 |
382 |
index 0000000000..52bb09ed7f |
383 |
--- /dev/null |
384 |
+++ b/scripts/auto-bootstraps/process_uploads.sh |
385 |
@@ -0,0 +1,60 @@ |
386 |
+#!/usr/bin/env bash |
387 |
+ |
388 |
+UPLOADDIR="./uploads" |
389 |
+RESULTSDIR="./results" |
390 |
+ |
391 |
+didsomething= |
392 |
+for d in ${UPLOADDIR}/* ; do |
393 |
+ if [[ ! -d "${d}" ]] ; then |
394 |
+ rm -f "${d}" |
395 |
+ continue |
396 |
+ fi |
397 |
+ |
398 |
+ # structure: randomid/chost/date |
399 |
+ # chost/date should be the only thing in randomid/ check this |
400 |
+ set -- "${d}"/*/* |
401 |
+ if [[ $# -ne 1 ]] || [[ ! -d "$1" ]] ; then |
402 |
+ rm -Rf "${d}" |
403 |
+ continue |
404 |
+ fi |
405 |
+ |
406 |
+ dir=${1#${d}/} |
407 |
+ # skip this thing from auto-processing if it is new platform |
408 |
+ [[ -d ${RESULTSDIR}/${dir%/*} ]] || continue |
409 |
+ # skip this thing if it already exists |
410 |
+ [[ -d ${RESULTSDIR}/${dir} ]] && continue |
411 |
+ # skip this thing if it isn't complete yet |
412 |
+ [[ -d ${d}/${dir}/push-complete ]] || continue |
413 |
+ |
414 |
+ # only copy over what we expect, so we leave any uploaded cruft |
415 |
+ # behind |
416 |
+ mkdir "${RESULTSDIR}/${dir}" |
417 |
+ for f in \ |
418 |
+ stage{1,2,3}.log \ |
419 |
+ .stage{1,2,3}-finished \ |
420 |
+ bootstrap-prefix.sh \ |
421 |
+ emerge.log \ |
422 |
+ startprefix \ |
423 |
+ distfiles ; |
424 |
+ do |
425 |
+ [[ -e "${d}/${dir}/${f}" ]] && \ |
426 |
+ mv "${d}/${dir}/${f}" "${RESULTSDIR}/${dir}"/ |
427 |
+ done |
428 |
+ if [[ -e "${d}/${dir}/portage" ]] ; then |
429 |
+ for pkg in "${d}/${dir}/portage"/*/* ; do |
430 |
+ w=${pkg#${d}/} |
431 |
+ mkdir -p "${RESULTSDIR}/${w}" |
432 |
+ [[ -e "${pkg}"/build-info ]] && \ |
433 |
+ mv "${pkg}"/build-info "${RESULTSDIR}/${w}"/ |
434 |
+ [[ -e "${pkg}"/temp ]] && \ |
435 |
+ mv "${pkg}"/temp "${RESULTSDIR}/${w}"/ |
436 |
+ done |
437 |
+ fi |
438 |
+ chmod -R o+rX,go-w "${RESULTSDIR}/${dir}" |
439 |
+ rm -Rf "${d}" |
440 |
+ |
441 |
+ [[ -e "${RESULTSDIR}/${dir}"/distfiles ]] && \ |
442 |
+ ./update_distfiles.py "${RESULTSDIR}/${dir}"/distfiles > /dev/null |
443 |
+ didsomething=1 |
444 |
+done |
445 |
+[[ -n ${didsomething} ]] && ./analyse_result.py > /dev/null |
446 |
|
447 |
diff --git a/scripts/auto-bootstraps/update_distfiles.py b/scripts/auto-bootstraps/update_distfiles.py |
448 |
new file mode 100755 |
449 |
index 0000000000..8f44f7fa20 |
450 |
--- /dev/null |
451 |
+++ b/scripts/auto-bootstraps/update_distfiles.py |
452 |
@@ -0,0 +1,29 @@ |
453 |
+#!/usr/bin/env python3.6 |
454 |
+ |
455 |
+import hashlib |
456 |
+import os |
457 |
+import sys |
458 |
+ |
459 |
+distfilessrc='./distfiles' |
460 |
+ |
461 |
+def hash_file(f): |
462 |
+ hsh = hashlib.new('sha1') |
463 |
+ with open(f, 'rb') as fle: |
464 |
+ hsh.update(fle.read()) |
465 |
+ return hsh.hexdigest() |
466 |
+ |
467 |
+with os.scandir(path=sys.argv[1]) as it: |
468 |
+ for f in it: |
469 |
+ if not f.is_file() or f.name.startswith('.'): |
470 |
+ continue |
471 |
+ srcfile = os.path.join(sys.argv[1], f.name) |
472 |
+ h = hash_file(srcfile) |
473 |
+ distname = os.path.join(distfilessrc, |
474 |
+ f.name + "@" + h).lower() |
475 |
+ if os.path.exists(distname): |
476 |
+ print("DUP %s" % distname.split('/')[-1]) |
477 |
+ os.remove(srcfile) |
478 |
+ os.link(distname, srcfile, follow_symlinks=False) |
479 |
+ else: |
480 |
+ print("NEW %s" % distname.split('/')[-1]) |
481 |
+ os.link(srcfile, distname) |