github.com/imran-kn/cilium-fork@v1.6.9/contrib/release/lib/common.sh (about) 1 #!/bin/bash 2 # 3 # Copyright 2016 The Kubernetes Authors All rights reserved. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 # Provide a default $PROG (for use in most functions that use a $PROG: prefix 18 : ${PROG:="common"} 19 export PROG 20 21 ############################################################################## 22 # Common library of useful functions and GLOBALS. 23 ############################################################################## 24 25 set -o errtrace 26 27 # TODO: 28 # - Figure out a way to share common bits with other Kubernetes sub repos 29 # - cleanup / function headers 30 31 ############################################################################## 32 # COMMON CONSTANTS 33 # 34 TOOL_LIB_PATH=${TOOL_LIB_PATH:-$(dirname $(readlink -ne $BASH_SOURCE))} 35 TOOL_ROOT=${TOOL_ROOT:-$(readlink -ne $TOOL_LIB_PATH/..)} 36 PATH=$TOOL_ROOT:$PATH 37 LOCAL_CACHE="/tmp/buildresults-cache.$$" 38 # Provide a default EDITOR for those that don't have this set 39 : ${EDITOR:="vi"} 40 export PATH TOOL_ROOT TOOL_LIB_PATH EDITOR 41 42 # Pretty curses stuff for terminals 43 if [[ -t 1 ]]; then 44 # Set some video text attributes for use in error/warning msgs. 45 declare -A TPUT=([BOLD]=$(tput bold 2>/dev/null)) 46 TPUT+=( 47 [REVERSE]=$(tput rev 2>/dev/null) 48 [UNDERLINE]=$(tput smul 2>/dev/null) 49 [BLINK]=$(tput blink 2>/dev/null) 50 [GREEN]=${TPUT[BOLD]}$(tput setaf 2 2>/dev/null) 51 [RED]=${TPUT[BOLD]}$(tput setaf 1 2>/dev/null) 52 [YELLOW]=${TPUT[BOLD]}$(tput setaf 3 2>/dev/null) 53 [OFF]=$(tput sgr0 2>/dev/null) 54 [COLS]=$(tput cols 2>/dev/null) 55 ) 56 57 # HR 58 HR="$(for ((i=1;i<=${TPUT[COLS]};i++)); do echo -en '\u2500'; done)" 59 60 # Save original TTY State 61 TTY_SAVED_STATE="$(stty -g)" 62 else 63 HR="$(for ((i=1;i<=80;i++)); do echo -en '='; done)" 64 fi 65 66 # Set some usable highlighted keywords for functions like logrun -s 67 YES="${TPUT[GREEN]}YES${TPUT[OFF]}" 68 OK="${TPUT[GREEN]}OK${TPUT[OFF]}" 69 DONE="${TPUT[GREEN]}DONE${TPUT[OFF]}" 70 PASSED="${TPUT[GREEN]}PASSED${TPUT[OFF]}" 71 FAILED="${TPUT[RED]}FAILED${TPUT[OFF]}" 72 FATAL="${TPUT[RED]}FATAL${TPUT[OFF]}" 73 NO="${TPUT[RED]}NO${TPUT[OFF]}" 74 WARNING="${TPUT[YELLOW]}WARNING${TPUT[OFF]}" 75 ATTENTION="${TPUT[YELLOW]}ATTENTION${TPUT[OFF]}" 76 MOCK="${TPUT[YELLOW]}MOCK${TPUT[OFF]}" 77 FOUND="${TPUT[GREEN]}FOUND${TPUT[OFF]}" 78 NOTFOUND="${TPUT[YELLOW]}NOT FOUND${TPUT[OFF]}" 79 80 # Ensure USER is set 81 USER=${USER:-$LOGNAME} 82 83 # Set a PID for use throughout. 84 export PID=$$ 85 86 # Save original cmd-line. 87 ORIG_CMDLINE="$*" 88 89 PROGSTATE=/tmp/$PROG-runstate 90 91 ############################################################################### 92 # Define logecho() function to display to both log and stdout. 93 # As this is widely used and to reduce clutter, we forgo the common:: prefix 94 # Options can be -n or -p or -np/-pn. 95 # @optparam -p Add $PROG: prefix to stdout 96 # @optparam -r Exclude log prefix (used to output status' like $OK $FAILED) 97 # @optparam -n no newline (just like echo -n) 98 # @param a string to echo to stdout 99 logecho () { 100 local log_prefix="$PROG::${FUNCNAME[1]:-"main"}(): " 101 local prefix 102 # Dynamically set fmtlen 103 local fmtlen=$((${TPUT[COLS]:-"80"})) 104 local n 105 local raw=0 106 #local -a sed_pat=() 107 108 while [[ "$#" -gt 0 ]]; do 109 case "$1" in 110 -r) raw=1; shift ;; 111 -n) n="-n"; shift ;; 112 -p) prefix="$PROG: "; ((fmtlen+=${#prefix})); shift ;; 113 *) break ;; 114 esac 115 done 116 117 if ((raw)) || [[ -z "$*" ]]; then 118 # Clean log_prefix for blank lines 119 log_prefix="" 120 #else 121 # Increase fmtlen to account for control characters 122 #((fmtlen+=$(echo "$*" grep -o '[[:cntrl:]]' |wc -l))) 123 #sed_pat=(-e '2,${s}/^/ ... /g') 124 fi 125 126 # Allow widespread use of logecho without having to 127 # determine if $LOGFILE exists first. 128 [[ -f $LOGFILE ]] || LOGFILE="/dev/null" 129 ( 130 # If -n is set, do not provide autoformatting or you lose the -n effect 131 # Use of -n should only be used on short status lines anyway. 132 if ((raw)) || [[ $n == "-n" ]]; then 133 echo -e $n "$log_prefix$*" 134 else 135 # Add FUNCNAME to line prefix, but strip it from visible output 136 # Useful for viewing log detail 137 echo -e "$*" | fmt -$fmtlen | sed -e "1s,^,$log_prefix,g" "${sed_pat[@]}" 138 fi 139 ) | tee -a "$LOGFILE" |sed "s,^$log_prefix,$prefix,g" 140 } 141 142 ############################################################################### 143 # logrun() function to run commands to both log and stdout. 144 # As this is widely used and to reduce clutter, we forgo the common:: prefix 145 # 146 # The calling function is added to the line prefix. 147 # NOTE: All optparam's for logrun() (obviously) must preceed the command string 148 # @optparam -v Run verbosely 149 # @optparam -s Provide a $OK or $FAILED status from running command 150 # @optparam -m MOCK command by printing out command line rather than running it. 151 # @optparam -r Retry attempts. Integer arg follows -r (Ex. -r 2) 152 # Typically used together with -v to show retry attempts. 153 # @param a command string 154 # GLOBALS used in this function: 155 # * LOGFILE (Set by common::logfileinit()), if set, gets full command output 156 # * FLAGS_verbose (Set by caller - defaults to false), if true, full output to stdout 157 logrun () { 158 local mock=0 159 local status=0 160 local arg 161 local retries=0 162 local try 163 local retry_string 164 local scope="::${FUNCNAME[1]:-main}()" 165 local ret 166 local verbose=0 167 168 while [[ "$#" -gt 0 ]]; do 169 case "$1" in 170 -v) verbose=1; shift ;; 171 -s) status=1; shift ;; 172 -m) mock=1; shift ;; 173 -r) retries=$2; shift 2;; 174 *) break ;; 175 esac 176 done 177 178 for ((try=0; try<=$retries; try++)); do 179 if [[ $try -gt 0 ]]; then 180 if ((verbose)) || ((FLAGS_verbose)); then 181 # if global FLAGS_verbose, be very verbose 182 logecho "Retry #$try..." 183 elif ((status)); then 184 # if we're reporting a status (-v), then just ... 185 logecho -n "." 186 fi 187 # Add some minimal wait between retries assuming we're retrying due to 188 # something resolvable by waiting 'just a bit' 189 sleep 2 190 fi 191 192 # if no args, take stdin 193 if (($#==0)); then 194 if ((verbose)) || ((FLAGS_verbose)); then 195 tee -a $LOGFILE 196 else 197 tee -a $LOGFILE &>/dev/null 198 fi 199 ret=$? 200 elif [[ -f "$LOGFILE" ]]; then 201 printf "\n$PROG$scope: %s\n" "$*" >> $LOGFILE 202 203 if ((mock)); then 204 logecho "($MOCK)" 205 logecho "(CMD): $@" 206 return 0 207 fi 208 209 # Special case "cd" which cannot be run through a pipe (subshell) 210 if (! ((FLAGS_verbose)) && ! ((verbose)) ) || [[ "$1" == "cd" ]]; then 211 "${@:-:}" >> $LOGFILE 2>&1 212 else 213 printf "\n$PROG$scope: %s\n" "$*" 214 "${@:-:}" 2>&1 | tee -a $LOGFILE 215 fi 216 ret=${PIPESTATUS[0]} 217 else 218 if ((mock)); then 219 logecho "($MOCK)" 220 logecho "(CMD): $@" 221 return 0 222 fi 223 224 if ((verbose)) || ((FLAGS_verbose)); then 225 printf "\n$PROG$scope: %s\n" "$*" 226 "${@:-:}" 227 else 228 "${@:-:}" &>/dev/null 229 fi 230 ret=${PIPESTATUS[0]} 231 fi 232 233 [[ "$ret" = 0 ]] && break 234 done 235 236 [[ -n "$retries" && $try > 0 ]] && retry_string=" (retry #$try)" 237 238 if ((status)); then 239 [[ "$ret" = 0 ]] && logecho -r "$OK$retry_string" 240 [[ "$ret" != 0 ]] && logecho -r "$FAILED" 241 fi 242 243 return $ret 244 } 245 246 ############################################################################### 247 # common::timestamp() Capture block timings and display them 248 # The calling function is added to the line prefix. 249 # NOTE: All optparam's for logrun() (obviously) must preceed the command string 250 # @param begin|end|done 251 # @optparam section defaults to main, but can be specified to time sub sections 252 common::timestamp () { 253 local action=$1 254 local section=${2:-main} 255 # convert illegal characters to (legal) underscore 256 section=${section//[-\.:\/]/_} 257 local start_var="${section}start_seconds" 258 local end_var="${section}end_seconds" 259 local elapsed 260 local d 261 local h 262 local m 263 local s 264 local prettyd 265 local prettyh 266 local prettym 267 local prettys 268 local pretty 269 270 case $action in 271 begin) 272 273 # Get time(date) for display and calc. 274 eval $start_var=$(date '+%s') 275 276 # Print BEGIN message for $PROG. 277 echo "$PROG: BEGIN $section on ${HOSTNAME%%.*} $(date)" 278 279 if [[ $section == "main" ]]; then 280 echo 281 fi 282 ;; 283 end|done) 284 # Check for "START" values before calcing. 285 if [[ -z ${!start_var} ]]; then 286 #display_time="EE:EE:EE - 'end' run without 'begin' in this scope or sourced script using common::timestamp" 287 return 1 288 fi 289 290 # Get time(date) for display and calc. 291 eval $end_var=$(date '+%s') 292 293 elapsed=$(( ${!end_var} - ${!start_var} )) 294 d=$(( elapsed / 86400 )) 295 h=$(( (elapsed % 86400) / 3600 )) 296 m=$(( (elapsed % 3600) / 60 )) 297 s=$(( elapsed % 60 )) 298 (($d>0)) && local prettyd="${d}d" 299 (($h>0)) && local prettyh="${h}h" 300 (($m>0)) && local prettym="${m}m" 301 prettys="${s}s" 302 pretty="$prettyd$prettyh$prettym$prettys" 303 304 [[ $section == "main" ]] && echo 305 echo "$PROG: DONE $section on ${HOSTNAME%%.*} $(date) in $pretty" 306 ;; 307 esac 308 } 309 310 # Write our own trap to capture signal 311 common::trap () { 312 local func="$1" 313 shift 314 local sig 315 316 for sig; do 317 trap "$func $sig" "$sig" 318 done 319 } 320 321 common::trapclean () { 322 local sig=$1 323 local frame=0 324 325 # If user ^C's at read then tty is hosed, so make it sane again. 326 [[ -n "$TTY_SAVED_STATE" ]] && stty "$TTY_SAVED_STATE" 327 328 logecho;logecho 329 logecho "Signal $sig caught!" 330 logecho 331 logecho "Traceback (line function script):" 332 while caller $frame; do 333 ((frame++)) 334 done 335 common::exit 2 "Exiting..." 336 } 337 338 ############################################################################# 339 # Clean exit with an ending timestamp 340 # @param Exit code 341 common::cleanexit () { 342 # Display end common::timestamp when an existing common::timestamp begin 343 # was run. 344 [[ -n ${mainstart_seconds} ]] && common::timestamp end 345 exit ${1:-0} 346 } 347 348 ############################################################################# 349 # common::cleanexit() entry point with some formatting and message printing 350 # @param Exit code 351 # @param message 352 common::exit () { 353 local etype=${1:-0} 354 shift 355 356 [[ -n "$1" ]] && (logecho;logecho "$@";logecho) 357 common::cleanexit $etype 358 } 359 360 ############################################################################# 361 # Simple yes/no prompt 362 # 363 # @optparam default -n(default)/-y/-e (default to n, y or make (e)xplicit) 364 # @param message 365 common::askyorn () { 366 local yorn 367 local def=n 368 local msg="y/N" 369 370 case $1 in 371 -y) # yes default 372 def="y" msg="Y/n" 373 shift 374 ;; 375 -e) # Explicit 376 def="" msg="y/n" 377 shift 378 ;; 379 -n) shift 380 ;; 381 esac 382 383 while [[ $yorn != [yYnN] ]]; do 384 logecho -n "$*? ($msg): " 385 read yorn 386 : ${yorn:=$def} 387 done 388 389 # Final test to set return code 390 [[ $yorn == [yY] ]] 391 } 392 393 ############################################################################### 394 # Print step header text in a consistent way 395 common::stepheader () { 396 # If called with no args, assume the key is the caller's function name 397 local msg="$*" 398 399 logecho 400 logecho -r $HR 401 logecho "$msg" 402 logecho -r $HR 403 logecho 404 } 405 406 # Save a specified number of backups to a file 407 common::rotatelog () { 408 local file=$1 409 local num=$2 410 local tmpfile=/tmp/rotatelog.$PID 411 local counter=$num 412 413 # Quiet exit 414 [[ ! -f "$file" ]] && return 415 416 cp -p $file $tmpfile 417 418 while ((counter>=0)); do 419 if ((counter==num)); then 420 rm -f $file.$counter 421 elif ((counter==0)); then 422 if [[ -f "$file" ]]; then 423 next=$((counter+1)) 424 mv $file $file.$next 425 fi 426 else 427 next=$((counter+1)) 428 [[ -f $file.$counter ]] && mv $file.$counter $file.$next 429 fi 430 ((counter==0)) && break 431 ((counter--)) 432 done 433 434 mv $tmpfile $file 435 } 436 437 # --norotate assumes you're passing in a unique LOGFILE. 438 # $2 then indicates the number of unique filenames prefixed up to the last 439 # dot extension that will be saved. The rest of those files will be deleted 440 # For example, common::logfileinit --norotate foo.log.234 100 441 # common::logfileinit maintains up to 100 foo.log.* files. Anything else named 442 # foo.log.* > 100 are removed. 443 common::logfileinit () { 444 local nr=false 445 446 if [[ "$1" == "--norotate" ]]; then 447 local nr=true 448 shift 449 fi 450 LOGFILE=${1:-$PWD/$PROG.log} 451 local num=$2 452 453 # Ensure LOG directory exists 454 mkdir -p $(dirname $LOGFILE 2>&-) 455 456 # Initialize Logfile. 457 if ! $nr; then 458 common::rotatelog "$LOGFILE" ${num:-3} 459 fi 460 # Truncate the logfile. 461 > "$LOGFILE" 462 463 echo "CMD: $PROG $ORIG_CMDLINE" >> "$LOGFILE" 464 465 # with --norotate, remove the list of files that start with $PROG.log 466 if $nr; then 467 ls -1tr ${LOGFILE%.*}.* |head --lines=-$num |xargs rm -f 468 fi 469 } 470 471 # An alternative that has a dependency on external program - pandoc 472 # store markdown man pages in companion files. Allow prog -man to still read 473 # those and display a man page using: 474 # pandoc -s -f markdown -t man prog.md |man -l - 475 common::manpage () { 476 [[ "$usage" == "yes" ]] && set -- -usage 477 [[ "$man" == "yes" ]] && set -- -man 478 [[ "$comments" == "yes" ]] && set -- -comments 479 480 case $1 in 481 -*usage|"-?") 482 sed -n '/#+ SYNOPSIS/,/^#+ DESCRIPTION/p' $0 |sed '/^#+ DESCRIPTION/d' |\ 483 envsubst | sed -e 's,^#+ ,,g' -e 's,^#+$,,g' 484 exit 1 485 ;; 486 -*man|-h|-*help) 487 grep "^#+" "$0" |\ 488 sed -e 's,^#+ ,,g' -e 's,^#+$,,g' |envsubst |${PAGER:-"less"} 489 exit 1 490 ;; 491 esac 492 } 493 494 ############################################################################### 495 # General command-line parser converting -*arg="value" to $FLAGS_arg="value" 496 # Set -name/--name booleans to FLAGS_name=1 497 # As a convenience, flags can contain dashes or underscores, but dashes are 498 # converted to underscores in the final FLAGS_name to conform to variable 499 # naming standards. 500 # Sets global array POSITIONAL_ARGV holding all non-dash command-line arguments 501 common::namevalue () { 502 local arg 503 local name 504 local value 505 local -A arg_aliases=([v]="verbose" [n]="dryrun") 506 507 for arg in "$@"; do 508 case $arg in 509 -*[[:alnum:]]*) # Strip off any leading - or -- 510 arg=$(printf "%s\n" $arg |sed 's/^-\{1,2\}//') 511 # Handle global aliases 512 arg=${arg_aliases[$arg]:-"$arg"} 513 if [[ $arg =~ =(.*) ]]; then 514 name=${arg%%=*} 515 value=${arg#*=} 516 # change -'s to _ in name for legal vars in bash 517 eval export FLAGS_${name//-/_}=\""$value"\" 518 else 519 # bool=1 520 # change -'s to _ in name for legal vars in bash 521 eval export FLAGS_${arg//-/_}=1 522 fi 523 ;; 524 *) POSITIONAL_ARGV+=("$arg") 525 ;; 526 esac 527 done 528 } 529 530 ############################################################################### 531 # Print vars in simple or pretty format with text highlighting, columnized, 532 # logged. 533 # Prints the shell-quoted values of all of the given variables. 534 # Arrays and associative arrays are supported; all their elements will be 535 # printed. 536 # @optparam -p Pretty print the values 537 # @param space separated list of variables 538 common::printvars () { 539 local var 540 local var_str 541 local key 542 local tmp 543 local pprint=0 544 local pprintvar 545 local pprintval 546 local -a quoted 547 548 # Pretty/format print? 549 if [[ "$1" == "-p" ]]; then 550 pprint=1 551 pprintvar=$2 552 shift 2 553 fi 554 555 for var in "$@"; do 556 (($pprint)) && var_str=$var 557 558 # if var is an array, do special tricks 559 # bash wizardry courtesy of 560 # http://stackoverflow.com/questions/4582137/bash-indirect-array-addressing 561 if [[ "$(declare -p $var 2>/dev/null)" =~ ^declare\ -[aA] ]]; then 562 tmp="$var[@]" 563 quoted=("${!tmp}") # copy the variable 564 for key in "${!quoted[@]}"; do 565 # shell-quote each element 566 quoted[$key]="$(printf %q "${quoted[$key]}")" 567 done 568 if (($pprint)); then 569 logecho -r "$(printf '%-32s%s\n' "${var_str}:" "${quoted[*]}")" 570 else 571 printf '%s=%s\n' "$var" "${quoted[*]}" 572 fi 573 else 574 if (($pprint)); then 575 pprintval=$(eval echo \$$pprintvar) 576 logecho -r \ 577 "$(printf '%-32s%s\n' "${var_str}:" "${!var/$pprintval\//\$$pprintvar/}")" 578 else 579 echo "$var=${!var}" 580 fi 581 fi 582 done 583 } 584 585 586 ############################################################################### 587 # Simple argc validation with a usage return 588 # @param num - number of POSITIONAL_ARGV that should be on the command-line 589 # return 1 if any number other than num 590 common::argc_validate () { 591 local args=$1 592 593 # Validate number of args 594 if ((${#POSITIONAL_ARGV[@]}>args)); then 595 logecho 596 logecho "Exceeded maximum argument limit of $args!" 597 logecho 598 $PROG -? 599 logecho 600 common::exit 1 601 fi 602 } 603 604 605 ############################################################################### 606 # Get the md5 hash of a file 607 # @param file - The file 608 # @print the md5 hash 609 common::md5 () { 610 local file=$1 611 612 if which md5 >/dev/null 2>&1; then 613 md5 -q "$1" 614 else 615 md5sum "$file" | awk '{print $1}' 616 fi 617 } 618 619 ############################################################################### 620 # Get the sha1 hash of a file 621 # @param file - The file 622 # @param algo - Algorithm 1 (default), 224, 256, 384, 512, 512224, 512256 623 # @print the sha hash 624 common::sha () { 625 local file=$1 626 local algo=${2:-1} 627 628 which shasum >/dev/null 2>&1 && LANG=C shasum -a$algo $file | awk '{print $1}' 629 } 630 631 # Check for and source security layer 632 # This function looks for an additional security layer and activates 633 # special code paths to allow for enhanced features. 634 # The pointer to this file is set with FLAGS_security_layer: 635 # * --security_layer=/path/to/script_to_source 636 # * $HOME/${PROG}rc (FLAGS_security_layer=/path/to/source) 637 # SECURITY_LAYER global defaulted here. Set to 1 in external source 638 common::security_layer () { 639 local rcfile=$HOME/.kubernetes-releaserc 640 SECURITY_LAYER=0 641 642 # Quietly attempt to source the include 643 source $rcfile >/dev/null 2>&1 || true 644 645 # If not there attempt to set it from env 646 FLAGS_security_layer=${FLAGS_security_layer:-""} 647 648 if [[ -n $FLAGS_security_layer ]]; then 649 if [[ -r $FLAGS_security_layer ]]; then 650 source $FLAGS_security_layer >/dev/null 2>&1 651 else 652 logecho "$FATAL! $FLAGS_security_layer is not readable." 653 return 1 654 fi 655 elif [[ "$HOSTNAME" =~ google.com ]]; then 656 logecho "$FATAL! Googler, this session is incomplete." \ 657 "$PROG is running with missing functionality. See go/$PROG" 658 return 1 659 fi 660 } 661 662 ############################################################################### 663 # Check PIP packages 664 # @param package - A space separated list of PIP packages to verify exist 665 # 666 common::check_pip_packages () { 667 local prereq 668 local -a missing=() 669 670 # Make sure a bunch of packages are available 671 logecho -n "Checking required PIP packages: " 672 673 for prereq in $*; do 674 pip list | fgrep -w $prereq > /dev/null || missing+=($prereq) 675 done 676 677 if ((${#missing[@]}>0)); then 678 logecho -r "$FAILED" 679 logecho "PREREQ: Missing prerequisites: ${missing[@]}" \ 680 "Run the following and try again:" 681 logecho 682 for prereq in ${missing[@]}; do 683 logecho "$ sudo pip install $prereq" 684 done 685 return 1 686 fi 687 logecho -r "$OK" 688 } 689 690 691 ############################################################################### 692 # Check packages for a K8s release 693 # @param package - A space separated list of packages to verify exist 694 # 695 common::check_packages () { 696 local prereq 697 local packagemgr 698 local distro 699 local -a missing=() 700 701 # Make sure a bunch of packages are available 702 logecho -n "Checking required system packages: " 703 704 distro=$(lsb_release -si) 705 case $distro in 706 Fedora) 707 packagemgr="dnf" 708 for prereq in $*; do 709 rpm --quiet -q $prereq 2>/dev/null || missing+=($prereq) 710 done 711 ;; 712 Ubuntu) 713 packagemgr="apt-get" 714 for prereq in $*; do 715 dpkg --get-selections 2>/dev/null | fgrep -qw $prereq || missing+=($prereq) 716 done 717 ;; 718 *) 719 logecho "Unsupported distribution. Only Fedora and Ubuntu are supported" 720 return 1 721 ;; 722 esac 723 724 if ((${#missing[@]}>0)); then 725 logecho -r "$FAILED" 726 logecho "PREREQ: Missing prerequisites: ${missing[@]}" \ 727 "Run the following and try again:" 728 logecho 729 for prereq in ${missing[@]}; do 730 if [[ -n ${PREREQUISITE_INSTRUCTIONS[$prereq]} ]]; then 731 logecho "# See ${PREREQUISITE_INSTRUCTIONS[$prereq]}" 732 else 733 logecho "$ sudo $packagemgr install $prereq" 734 fi 735 done 736 return 1 737 fi 738 logecho -r "$OK" 739 } 740 741 742 ############################################################################### 743 # Check disk space 744 # @param disk - a path 745 # @param threshold - int in GB 746 # 747 common::disk_space_check () { 748 local disk=$1 749 local threshold=$2 750 local avail=$(df -BG $disk |\ 751 sed -nr -e "s|^\S+\s+\S+\s+\S+\s+([0-9]+).*$|\1|p") 752 753 logecho -n "Checking for at least $threshold GB on $disk: " 754 755 if ((threshold>avail)); then 756 logecho -r "$FAILED" 757 logecho "AVAILABLE SPACE: $avail" 758 logecho "THRESHOLD: $threshold" 759 return 1 760 else 761 logecho -r "$OK" 762 fi 763 } 764 765 ############################################################################### 766 # Run a function and display time metrics 767 # @param function - a function name to run and time 768 common::runstep () { 769 local function=$1 770 local finishtime 771 local retcode 772 773 common::timestamp begin $function &>/dev/null 774 775 $* 776 retcode=$? 777 778 finishtime=$(common::timestamp end $function | sed 's/.* //') 779 logecho "${TPUT[BOLD]}>>>>>>>> $PROG::$function() finished in" \ 780 "$finishtime${TPUT[OFF]}" 781 return $retcode 782 } 783 784 ############################################################################### 785 # Absolutify incoming path 786 # 787 # @param relative or absolute path 788 # @print absolute path 789 common::absolute_path () { 790 local arg=$1 791 792 [[ -z "$arg" ]] && return 0 793 794 [[ "$arg" =~ ^/ ]] || dir="$PWD/$arg" 795 logecho $arg 796 } 797 798 ############################################################################### 799 # Strip all control characters out of a text file 800 # Useful for stripping color codes and things from text files after runs 801 # @param file text file 802 common::strip_control_characters () { 803 local file=$1 804 805 sed -ri -e "s/\x1B[\[(]([0-9]{1,2}(;[0-9]{1,2})?)?[m|K|B]//g" \ 806 -e 's/\o015$//g' $file 807 } 808 809 ############################################################################### 810 # General log sanitizer 811 # @param file text file 812 common::sanitize_log () { 813 local file=$1 814 815 sed -i 's/[a-f0-9]\{40\}:x-oauth-basic/__SANITIZED__:x-oauth-basic/g' $file 816 } 817 818 ############################################################################### 819 # Print a number of characters (with no newline) 820 # @param char single character 821 # @param num number to print 822 common::print_n_char () { 823 local char=$1 824 local num=$2 825 local sep 826 827 printf -v sep '%*s' $num 828 echo "${sep// /$char}" 829 } 830 831 ############################################################################### 832 # Generate a (github) markdown TOC between BEGIN/END tags 833 # @param file The file to update in place 834 # 835 common::mdtoc () { 836 local file=$1 837 local indent 838 local anchor 839 local heading 840 local begin_block="<!-- BEGIN MUNGE: GENERATED_TOC -->" 841 local end_block="<!-- END MUNGE: GENERATED_TOC -->" 842 local tmpfile=/tmp/$PROG-cm.$$ 843 844 declare -A count 845 846 while read level heading; do 847 indent="$(echo $level |sed -e "s,^#,,g" -e 's,#, ,g')" 848 # make a valid anchor 849 anchor=${heading,,} 850 anchor=${anchor// /-} 851 anchor=${anchor//[\.\?\*\,\/\[\]:=\<\>()]/} 852 # Keep track of dups and identify 853 if [[ -n ${count[$anchor]} ]]; then 854 ((count[$anchor]++)) ||true 855 anchor+="-${count[$anchor]}" 856 else 857 # initialize value 858 count[$anchor]=0 859 fi 860 echo "${indent}- [$heading](#$anchor)" 861 done < <(egrep "^#+ " $file) > $tmpfile 862 863 # Insert new TOC 864 sed -ri "/^$begin_block/,/^$end_block/{ 865 /^$begin_block/{ 866 n 867 r $tmpfile 868 } 869 /^$end_block/!d 870 }" $file 871 872 logrun rm -f $tmpfile 873 } 874 875 ############################################################################### 876 # Set the global GSUTIL and GCLOUD binaries 877 # Returns: 878 # 0 if both GSUTIL and GCLOUD are set to executables 879 # 1 if both GSUTIL and GCLOUD are not set to executables 880 common::set_cloud_binaries () { 881 882 logecho -n "Checking/setting cloud tools: " 883 884 for GSUTIL in $(which gsutil) /opt/google/google-cloud-sdk/bin/gsutil; do 885 [[ -x $GSUTIL ]] && break 886 done 887 888 # gcloud should be in the same place 889 GCLOUD=${GSUTIL/gsutil/gcloud} 890 891 if [[ -x "$GSUTIL" && -x "$GCLOUD" ]]; then 892 logecho -r $OK 893 return 0 894 else 895 logecho -r $FAILED 896 return 1 897 fi 898 } 899 900 ############################################################################### 901 # sendmail/mailer front end. 902 # @optparam (flag) -h - Send html formatted 903 # @param to - To 904 # @param from - From 905 # @param reply_to - Reply To 906 # @param subject - Subject 907 # @param cc - cc 908 # @param file - file to send 909 # 910 common::sendmail () { 911 local cc_arg 912 local html=0 913 914 while [[ "$#" -gt 0 ]]; do 915 case "$1" in 916 -h) html=1; shift ;; 917 *) break ;; 918 esac 919 done 920 921 local to="$1" 922 local from="$2" 923 local reply_to="$3" 924 local subject="$4" 925 local cc="$5" 926 local file="$6" 927 928 if [[ "$HOSTNAME" =~ google.com ]]; then 929 logecho "$FAILED! sendmail unavailable at Google." 930 return 1 931 fi 932 933 ( 934 cat <<EOF+ 935 To: "$to" 936 From: "$from" 937 Subject: "$subject" 938 Cc: "$cc" 939 Reply-To: "$reply_to" 940 EOF+ 941 ((html)) && echo "Content-Type: text/html" 942 cat $file 943 ) |/usr/sbin/sendmail -t 944 } 945 946 # Stubs for security_layer functions 947 security_layer::auth_check () { 948 logecho "Skipping $FUNCNAME..." 949 return 0 950 } 951 952 # Set a common::trap() to capture ^C's and other unexpected exits and do the 953 # right thing in common::trapclean(). 954 common::trap common::trapclean ERR SIGINT SIGQUIT SIGTERM SIGHUP 955 956 # parse cmdline 957 common::namevalue "$@" 958 959 # Run common::manpage to show usage and man pages 960 common::manpage "$@"