-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathfunctions
More file actions
799 lines (721 loc) · 22.2 KB
/
functions
File metadata and controls
799 lines (721 loc) · 22.2 KB
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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
#!/bin/sh
#
# Always *source* (not execute) this script at the *top* of your
# script. It will *ensure* your script is executing under POSIX shell,
# and provide some portability and utility functions. Use like this:
#
# . "$(dirname "$0")"/functions
#
# try_exec: Attempt to execute a command if it exists
# Args:
# $1 - Command to check for
# $@ - Full command with arguments to execute
# Returns:
# Does not return if successful (execs into new process)
try_exec() {
type "$1" >/dev/null 2>&1 && exec "$@"
}
# broken_posix_shell: Test if the current shell has broken POSIX 'local' support
# Returns:
# 0 if shell is broken (e.g., Solaris /bin/sh)
# 1 if shell has proper POSIX local support
broken_posix_shell() {
unset foo
# shellcheck disable=SC3043
local foo=1
test "$foo" != "1"
}
if broken_posix_shell >/dev/null 2>&1; then
try_exec /usr/xpg4/bin/sh "$0" "$@"
echo "No compatible shell script interpreter found."
echo "Please find a POSIX shell for your system."
exit 42
fi
#
# Do not set -e before switching to POSIX shell, as it will break the test
# above.
#
set -e
if [ "$_IS_FUNCTIONS_SOURCED" = yes ]; then
echo 'FATAL: Why are you sourcing "functions" script twice?!?'
exit 101
fi
# export_variables: Set up and export all environment variables needed for builds
# This function configures:
# - System information (UNAME_S, UNAME_R, UNAME_M)
# - Build directories (BASEDIR, BUILDPREFIX, PREFIX, OUTDIR)
# - PATH with platform-specific additions
# Called automatically when this file is sourced
export_variables() {
#
# See more examples at https://en.wikipedia.org/wiki/Uname
#
UNAME_S=$(uname -s) # Linux SunOS AIX HP-UX FreeBSD
UNAME_R=$(uname -r) # 4.4.0-59-generic 5.10 3 B.11.23 6.1-RELEASE-p15
UNAME_M=$(uname -m) # x86_64,i686 i86pc,sun4v 0001013AD400 ia64 i386,amd64
# uname -p # x86_64 i386,sparc powerpc ERROR
export UNAME_S UNAME_R UNAME_M
#
# BASEDIR: where all our repos have been checked out
#
if [ -z "$AUTOBUILD_PATH" ]; then
case "$0" in
*buildscripts/build-remote) SCRIPTDIR="$0" ;;
/*) SCRIPTDIR=$(dirname "$0") ;;
-*) SCRIPTDIR=$(pwd) ;; # if this script is sourced from a login shell, $0 is '-bash' or similar
*) SCRIPTDIR=$(pwd)/$(dirname "$0") ;;
esac
AUTOBUILD_PATH=$(dirname "$SCRIPTDIR")
fi
BASEDIR=$(dirname "$AUTOBUILD_PATH")
export BASEDIR
# BUILDPREFIX: which directory the packages will be installed to.
BUILDPREFIX=${BUILDPREFIX:-/var/cfengine}
export BUILDPREFIX
# same
PREFIX="$BUILDPREFIX"
export PREFIX
# Set PATH according to the needs of each platform and build-type
# TODO: remove the generic PATH setting completely,
# all should be under conditionals
case $UNAME_S in
AIX)
# We need to use GCC6 on new AIX 7 VMs (AIX 5 doesn't have the folder).
PATH="/opt/freeware/gcc6/bin/:$PATH"
# We need to use /opt/freeware/bin first, for e.g. wget and newer perl 5.34 on aix 7.1
PATH="/opt/freeware/bin:$PATH"
;;
SunOS)
# Currently we rely strongly on OpenCSW packages.
PATH="/opt/csw/bin:/usr/xpg4/bin:$PATH"
;;
HP-UX) PATH="$PATH:/usr/contrib/bin" ;;
*) ;;
esac
PATH="$PATH:/usr/local/bin:/usr/sbin:/usr/local/sbin"
PATH="$PATH:$BASEDIR/buildscripts/build-scripts"
PATH="$PATH:$BUILDPREFIX/httpd/php/bin" # TODO REMOVE
if [ -x /usr/lib64/ccache/gcc ]; then
PATH="/usr/lib64/ccache:$PATH"
fi
export PATH
# /bin/sh in Solaris 11 behaves badly only when /usr/xpg4/bin is
# first in path! In particular it uses a bad built-in "rm" instead
# of /usr/bin/rm.
if [ "$UNAME_S" = "SunOS" ] && [ "$UNAME_R" = "5.11" ]; then
ACCEPT_INFERIOR_RM_PROGRAM=yes
export ACCEPT_INFERIOR_RM_PROGRAM
fi
# Final destination of generated buildscripts output
OUTDIR="$BASEDIR/output/${OS}-${OS_VERSION}-${ARCH}"
export OUTDIR
}
export_variables
# For utilities in paths that we don't want in PATH.
# Not exported, used exclusively by func_whereis()
# TODO make it platform-dependent, add other platforms besides Solaris.
SECONDARY_PATHS="/sbin /usr/sbin /usr/sfw/bin" #"/opt/csw/gnu /usr/ccs/bin"
# grep_q: Grep with quiet output (no stdout)
# Args:
# $@ - All grep arguments
# Returns:
# Exit code from grep
grep_q() {
grep "$@" >/dev/null
}
# grep_c: Portable grep with context lines
# On HP-UX and Solaris (which lack -C option), falls back to plain grep
# Args:
# $1 - Number of context lines (ignored on HP-UX/Solaris)
# $@ - Remaining grep arguments
# Returns:
# Matching lines with context
grep_c() {
case $UNAME_S in
"HP-UX" | SunOS)
# There is no -C option on HP-UX and Solaris
shift
grep "$@"
;;
*)
# Print $1 lines of context
grep -C "$@"
;;
esac
}
#
# Dealing with packages. For platform-specific functions it's preferred
# to have them as a separate script under deps-packaging directory.
#
# uninstall_rpms: Remove RPM packages matching a pattern
# Args:
# $1 - Package name pattern (regex)
uninstall_rpms() {
PKGS=$(rpm -qa --queryformat "%{Name}-%{Version}\n" | grep "^$1" || true)
if [ -n "$PKGS" ]; then
retry_wrapper sudo rpm -e "$PKGS"
fi
}
# uninstall_debs: Remove Debian packages matching a pattern
# Args:
# $1 - Package name pattern (regex)
uninstall_debs() {
PKGS=$(dpkg -l | tail -n+6 | awk '{print $2}' | grep "^$1" || true)
if [ -n "$PKGS" ]; then
retry_wrapper sudo dpkg --purge "$PKGS"
fi
}
# uninstall_solaris_pkgs: Remove Solaris packages matching a pattern
# Args:
# $1 - Package name pattern (regex)
uninstall_solaris_pkgs() {
PKGS=$(pkginfo | awk '{print $2}' | grep "^$1$" || true)
if [ -n "$PKGS" ]; then
retry_wrapper sudo /usr/sbin/pkgrm -n "$PKGS"
fi
}
# uninstall_hpux_pkgs: Remove HP-UX packages matching a pattern
# Args:
# $1 - Package name pattern (regex)
uninstall_hpux_pkgs() {
PKGS=$(swlist | awk '{print $1}' | grep "^$1$" || true)
for p in $PKGS; do
sudo /usr/sbin/swremove "$p"
done
}
# uninstall_freebsd_pkgs: Remove FreeBSD packages matching a pattern
# Args:
# $1 - Package name pattern (regex)
uninstall_freebsd_pkgs() {
PKGS=$(pkg_info | awk '{print $1}' | grep "^$1" || true)
if [ -n "$PKGS" ]; then
retry_wrapper sudo pkg_delete "$PKGS"
fi
}
# uninstall_cfbuild: Remove all cfbuild packages (runtime and devel)
# Uses the appropriate uninstall function based on $DEP_PACKAGING
uninstall_cfbuild() {
case "$DEP_PACKAGING" in
rpm) uninstall_rpms 'cfbuild-.*' ;;
deb) uninstall_debs 'cfbuild-.*' ;;
solaris) uninstall_solaris_pkgs 'cfbuild-.*' ;;
freebsd) uninstall_freebsd_pkgs 'cfbuild-.*' ;;
hpux) uninstall_hpux_pkgs 'cfbuild-.*' ;;
*)
log_error "Unknown packaging system: $DEP_PACKAGING"
exit 1
;;
esac
}
# uninstall_cfbuild_devel: Remove cfbuild development packages
# Uses the appropriate uninstall function based on $DEP_PACKAGING
uninstall_cfbuild_devel() {
case "$DEP_PACKAGING" in
rpm) uninstall_rpms 'cfbuild-.*-devel' ;;
deb) uninstall_debs 'cfbuild-.*-devel' ;;
solaris) uninstall_solaris_pkgs 'cfbuild-.*-devel' ;;
freebsd) uninstall_freebsd_pkgs 'cfbuild-.*-devel' ;;
hpux) uninstall_hpux_pkgs 'cfbuild-.*-devel' ;;
*)
log_error "Unknown packaging system: $DEP_PACKAGING"
exit 42
;;
esac
}
# query_pkg: Check if a package is installed
# Args:
# $1 - Package name to query
# Returns:
# 0 if package is installed, 1 otherwise
query_pkg() {
case "$DEP_PACKAGING" in
rpm) rpm -qa --provides | grep_q "^$1 " ;;
deb) dpkg -s "$1" 2>&1 | grep_q '^Status: .*ok installed' ;;
*)
log_error "query_pkg() not implemented for $DEP_PACKAGING"
exit 1
;;
esac
}
# run: Execute a command and log output, failing with log tail on error
# Args:
# $1 - Descriptive name of the operation
# $@ - Command to execute
# Output:
# Appends stdout/stderr to build-remote.log
run() {
NAME="$1"
shift
echo "---> $NAME"
if "$@" >>"build-remote.log" 2>&1; then
:
else
log_error "$NAME failed. See the build-remote.log. Last lines are:"
tail build-remote.log
exit 1
fi
}
# local_script_general: Execute a build script locally without log tail (see run())
# Args:
# $1 - Script name (relative to build-scripts/)
# Uses:
# $HOST - Passed as argument to script
local_script_general() {
SCRIPT="$1"
"$BASEDIR/buildscripts/build-scripts/$SCRIPT" "$HOST"
}
# local_script: Execute a build script locally with log tail (see run())
# Args:
# $1 - Script name (relative to build-scripts/)
# Uses:
# $HOST - Passed as argument to script
local_script() {
SCRIPT="$1"
run "$SCRIPT" "$BASEDIR/buildscripts/build-scripts/$SCRIPT" "$HOST"
}
# remote_script_general: Execute a build script on a remote host via SSH
# Args:
# $1 - Script name (relative to build-scripts/)
# $2 - Login command (e.g., "ssh -o BatchMode=yes $HOST" or "sudo chroot $CHROOT_ROOT /run-in-home-dir.sh")
# $3 - Base directory on remote host
# Exports:
# Various build environment variables to remote host
remote_script_general() {
SCRIPT="$1"
LOGIN_COMMAND="$2"
SCRIPT_BASEDIR="$3"
ENVVARS="PROJECT=$PROJECT"
if [ -n "$CROSS_TARGET" ]; then
ENVVARS="$ENVVARS CROSS_TARGET=$CROSS_TARGET"
fi
if [ -n "$WIX_MACHINE" ]; then
ENVVARS="$ENVVARS WIX_MACHINE=$WIX_MACHINE"
fi
if [ -n "$BUILD_TYPE" ]; then
ENVVARS="$ENVVARS BUILD_TYPE=$BUILD_TYPE"
fi
if [ -n "$BUILD_NUMBER" ]; then
ENVVARS="$ENVVARS BUILD_NUMBER=$BUILD_NUMBER"
fi
if [ -n "$PREFIX" ]; then
ENVVARS="$ENVVARS BUILDPREFIX=$PREFIX"
fi
if [ -n "$CC" ]; then
ENVVARS="$ENVVARS CC=$CC"
fi
if [ -n "$CPPFLAGS" ]; then
ENVVARS="$ENVVARS CPPFLAGS='$CPPFLAGS'"
fi
if [ -n "$CFLAGS" ]; then
ENVVARS="$ENVVARS CFLAGS='$CFLAGS'"
fi
if [ -n "$LDFLAGS" ]; then
ENVVARS="$ENVVARS LDFLAGS='$LDFLAGS'"
fi
ENVVARS="$ENVVARS BRANCH=$REPOSITORY"
ENVVARS="$ENVVARS EXPLICIT_ROLE=$EXPLICIT_ROLE"
ENVVARS="$ENVVARS EXPLICIT_VERSION=$EXPLICIT_VERSION"
ENVVARS="$ENVVARS TEST_MACHINE=$TEST_MACHINE"
ENVVARS="$ENVVARS TEST_SHELL=$TEST_SHELL"
(eval "$LOGIN_COMMAND" env "$ENVVARS" "$SCRIPT_BASEDIR"/buildscripts/build-scripts/"$SCRIPT")
}
# remote_script: Execute a build script on remote host with logging
# Args:
# $1 - Script name (relative to build-scripts/)
# Uses:
# $HOST - Remote hostname for SSH connection
remote_script() {
SCRIPT="$1"
run "$SCRIPT" remote_script_general "$SCRIPT" "ssh -o BatchMode=yes $HOST" build
}
# projects_to_test: List CFEngine projects to test based on PROJECT and ROLE
# Returns:
# Space-separated list of project names to stdout
# Uses:
# $PROJECT - 'community' or 'enterprise'
# $ROLE - 'hub' or 'agent'
projects_to_test() {
if test "$PROJECT" = "community"; then
echo "core masterfiles"
else
if test "$ROLE" = "hub"; then
echo "core enterprise nova masterfiles"
else
echo "core enterprise masterfiles"
fi
fi
}
# generate_chroot_transfer_script: Generate rsync filter rules for chroot creation
#
# This function generates a set of file-filtering rules to be used with the rsync --filter option.
# The filtering rules lists files or directories that should be included (+) and excluded (-) when creating a chroot environment.
# The chroot command is used to change the root directory for a process in order to test it in an isolated environment.
# The first rule that matches a file or directory takes precedence over any subsequent.
#
# Returns:
# rsync filter rules to stdout
# Uses:
# $OS_FAMILY - Operating system family (aix, solaris, linux, hpux, etc.)
# $PREFIX - Installation prefix to exclude
generate_chroot_transfer_script() {
# These rules are processed in a "first that matches" fashion.
# This should go before 'Cross platform', in order not to be overwritten by
# '+ /etc' rule below
if [ "$OS_FAMILY" = solaris ]; then
echo '- /etc/svc/volatile/.inetd.uds'
fi
############# Cross platform #############
cat <<EOF
- */proc
- $PREFIX
+ /bin
+ /etc
- /lib/modules
+ /lib
- /lib64/modules
+ /lib64
+ /sbin
- /usr/src
- /usr/local/src
+ /usr
- /var/tmp
+ /var
EOF
########### Platform specific ############
case "$OS_FAMILY" in
aix)
cat <<EOF
+ /TT_DB
+ /audit
+ /lpp
+ /lppdir
+ /opt/freeware
- /opt/*
+ /opt
EOF
;;
solaris)
cat <<EOF
- /.SUNWnative/usr/jdk
- /.SUNWnative/usr/openwin
- /.SUNWnative/usr/lib/AdobeReader
+ /.SUNWnative
+ /opt/csw
- /opt/*
+ /opt
+ /system/volatile
- /system/*
+ /system
- /var/run/*
EOF
;;
linux | hpux)
cat <<EOF
+ /dev
+ /run/lock
- /run/*
+ /run
- /var/lib/lxcfs
EOF
;;
esac
# Exclude everything else.
echo "- /*"
}
# mount_procfs: Mount the /proc filesystem in a chroot environment
# Args:
# $1 - Mount point directory path
# Uses:
# $OS - Operating system name
mount_procfs() {
case "$OS" in
aix)
sudo mount -v namefs /proc "$1"
;;
solaris)
sudo mount -F proc proc "$1"
;;
hpux)
# No proc on HPUX.
true
;;
*)
sudo mount -t proc proc "$1"
;;
esac
}
#
# Generic utility and portability functions
#
# fatal: Exit with an error message and optional exit code
# Args:
# $1 - Error message
# $2 - Exit code (optional, defaults to 1)
# Examples:
# fatal "install failed" 42
# fatal "exiting with default error code"
fatal() {
echo "$(basename "$0"): FATAL ERROR: $1" >&2
exit "${2-1}"
}
# log_info: Print an informational message to stderr
# Args:
# $@ - Message to log
log_info() {
echo "$(basename "$0"): info:" "$@" 1>&2
}
# var_append: Append a string to a variable with a space separator
# Args:
# $1 - Variable name
# $2 - String to append
# Example:
# var_append V "blah" # equivalent to V="$V blah"
var_append() {
eval "$1=\$$1"\\ \$2
}
# var_contains: Check if a variable contains a specific string
# Args:
# $1 - Variable name
# $2 - String to search for
# Returns:
# 0 if found, 1 if not found
# WARNING: $2 may not contain special characters like spaces
var_contains() {
eval "case \$$1 in
*\"$2\"*) true ;;
*) false ;;
esac"
}
# func_which: Find command in PATH and print its full path
# Args:
# $@ - Command names to search for (tries each in order)
# Returns:
# 0 and prints first path found (stops search), 1 if not found
# Example:
# func_which gcc cc # tries gcc first, then cc
func_which() {
while [ x"$1" != x ]; do
if command -v "$1" 2>/dev/null; then
return 0
fi
shift
done
return 1
}
# func_whereis: Find command in PATH and SECONDARY_PATHS
# Args:
# $@ - Command names to search for (tries each in order)
# Returns:
# 0 and prints path if found, 1 if not found
# Uses:
# $SECONDARY_PATHS - Additional directories to search
func_whereis() {
args="$*"
while [ x"$1" != x ]; do
# First, search in PATH
if command -v "$1" 2>/dev/null; then
return 0
# Second, search in SECONDARY_PATHS
else
for p in $SECONDARY_PATHS; do
if command -v "$p/$1" 2>/dev/null; then
return 0
fi
done
fi
# Command $1 not found, search for next argument
shift
done
log_error "func_whereis() could not find commands: $args"
return 1
}
# func_mktemp: Create a temporary directory
# Args:
# $1 - Must be "-d" flag
# $2 - Template path with XXXX pattern (e.g., /tmp/dir.XXXX)
# Returns:
# Path to created directory
# Note:
# Fallback implementation for systems without mktemp command
func_mktemp() {
# Only works as mktemp -d
[ "$1" != -d ] && fatal "func_mktemp: error, first argument must be -d"
[ "$2" = "" ] && fatal "func_mktemp: requires two arguments"
# $RANDOM does not exist on Solaris 9 /bin/sh, use $$ as fallback
# shellcheck disable=SC3028
# > In POSIX sh, RANDOM is undefined.
# That's why we fallback to $$.
my_tmpdir=$(echo "$2" | sed 's/XX*/'${RANDOM-$$}/)
save_mktemp_umask=$(umask)
umask 0077
# Set -e will cause this to fail if it already exists
mkdir "$my_tmpdir"
umask "$save_mktemp_umask"
if [ -d "$my_tmpdir" ]; then
echo "$my_tmpdir"
else
fatal "func_mktemp: failed creating temporary directory $my_tmpdir"
fi
}
# mktempdir: Portable wrapper for creating temporary directories
# Args:
# $1 - Template path with XXXX pattern (e.g., /tmp/dir.XXXXXX)
# Returns:
# Path to created directory
# Example:
# mktempdir /tmp/dir.XXXXXX
mktempdir() {
[ "$1" = "" ] && fatal "mktempdir: TEMPLATE directory argument missing"
# HP-UX has its own non-POSIX mktemp, so override it.
# If not on HP-UX, search PATH for the 'mktemp' or 'gmktemp' command.
[ "$UNAME_S" = HP-UX ] &&
my_mktemp=func_mktemp ||
my_mktemp=$(func_which mktemp gmktemp) ||
my_mktemp=func_mktemp
$my_mktemp -d "$1"
}
# func_sha256: Calculate SHA-256 hash in a platform independent way
# Args:
# $1 - File to hash (optional, uses stdin if omitted)
# Returns:
# SHA-256 hash as hexadecimal string
func_sha256() {
if func_which sha256sum >/dev/null; then
sha256sum "$@" | cut -d ' ' -f 1
else
case "$UNAME_S" in
SunOS) digest -a sha256 "$@" ;;
AIX) openssl dgst -sha256 "$@" | cut -d ' ' -f 2 ;;
*) fatal "Can't find command for computing SHA-256" ;;
esac
fi
}
# func_decompress: Decompress files to stdout based on file extension
# Args:
# $1 - File to decompress (or uses stdin if omitted)
# Returns:
# Decompressed content to stdout
# Supported:
# .gz, .tgz (gzip), .bz2 (bzip2)
func_decompress() {
case "$1" in
*.gz | *.tgz) gzip -dc "$@" ;;
*.bz2) bzip2 -dc "$@" ;;
*) fatal "Unknown compression for file:" "$@" ;;
esac
}
# retry_wrapper: Execute a command with automatic retries on failure
# Args:
# $@ - Command to execute with arguments
# Returns:
# 0 on success, last exit code on final failure
# Retries:
# Up to 5 attempts with 30 second pauses
# WARNING: Do not pipe the output as stdout is altered!
retry_wrapper() {
operation="$*"
[ "$operation" = "" ] && fatal "retry_wrapper: no arguments"
# SLEEP: which sleep program to use
# maxtries: how many times to re try the execution
# pause: number of seconds to pause after each try
SLEEP=$(func_which sleep)
maxtries=5
pause=30
while [ "$maxtries" != 0 ]; do
echo "* retry_wrapper: $operation"
if $operation; then
echo "* SUCCESS"
return 0
else
err_ret=$?
# in case say dpkg locks are held by automatic updates or something
# shellcheck disable=SC2009
ps -efl | grep -P '(apt|dpkg|yum|dnf|zypper|rpm|pkg)'
maxtries=$((maxtries - 1))
echo "* FAILURE $err_ret"
echo "* Sleeping for: $pause seconds"
echo "* Retries left: $maxtries"
$SLEEP $pause
fi
done
return "$err_ret"
}
# rm_if_empty: Remove a file if it exists and is empty
# Args:
# $1 - Path to file to check and potentially remove
rm_if_empty() {
if [ -f "$1" ] && [ ! -s "$1" ]; then
echo "Removing empty file: $1" 1>&2
rm -f "$1"
fi
}
# run_and_print_on_failure: Run command and only show output on failure or warnings
# Args:
# $@ - Command to execute with arguments
# Returns:
# Exit code from command
# Output:
# Normally silent; shows warnings/errors on success, full output on failure
run_and_print_on_failure() {
# shellcheck disable=SC3043
local temp_output_file
if command -v mktemp >/dev/null; then
temp_output_file=$(mktemp)
else
# AIX 7.1 does not have mktemp
while true; do
# shellcheck disable=SC2021
# ^^ legacy/POSIX requires square brackets
temp_output_file="$(LC_CTYPE=C tr -dc "[A-Z][a-z][0-9]" </dev/urandom | dd count=1 bs=8 2>/dev/null)"
if [ -f "$temp_output_file" ]; then
continue
fi
break
done
touch "$temp_output_file"
fi
# shellcheck disable=SC3043
local exit_code=0
if "$@" >"$temp_output_file" 2>&1; then
# Filter output on Warnings/Errors and add two lines of context
regex='([Ww]arning:|[Ee]rror:)'
if grep_q -E "$regex" "$temp_output_file"; then
# Known-benign patterns that are not actionable and should be suppressed:
# - automake subdir-objects: forward-compatibility notice, harmless
# - libtool install warnings: normal when using DESTDIR
# shellcheck disable=SC3043
local benign='subdir-objects|libtool: warning: remember to run|libtool: warning:.*has not been installed|libtool: install:'
# shellcheck disable=SC3043
local filtered
filtered=$(grep_c 2 -E "$regex" "$temp_output_file" | grep -v -E "$benign") || true
if [ -n "$filtered" ]; then
log_debug "Found warnings/errors in output from command:" "$@"
echo "--- Start of Warnings/Errors ---"
printf '%s\n' "$filtered"
echo "--- End of Warnings/Errors ---"
fi
fi
else
# Print all output
exit_code=$? # Store exit code for later
log_error "Failed to run:" "$@"
echo "--- Start of Output ---"
cat "$temp_output_file"
echo "--- End of Output (Error Code: $exit_code) ---"
fi
rm -f "$temp_output_file"
return $exit_code
}
# log_debug: Print a debug message with script name prefix
# Args:
# $@ - Debug message
log_debug() {
echo "$(basename "$0"): debug:" "$@"
}
# log_error: Print an error message to stderr with script name prefix
# Args:
# $@ - Error message
log_error() {
echo "$(basename "$0"): error:" "$@" >&2
}
_IS_FUNCTIONS_SOURCED=yes