Skip to content

Instantly share code, notes, and snippets.

@brodygov
Created January 10, 2020 17:27
Show Gist options
  • Select an option

  • Save brodygov/5a77db9474ecdbc633eac65d47e1f3e4 to your computer and use it in GitHub Desktop.

Select an option

Save brodygov/5a77db9474ecdbc633eac65d47e1f3e4 to your computer and use it in GitHub Desktop.

Revisions

  1. brodygov created this gist Jan 10, 2020.
    307 changes: 307 additions & 0 deletions common.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,307 @@
    #!/bin/bash

    # Common shell functions.
    # Having a library like this is a surefire sign that you are using too much
    # shell and should switch to something like Ruby. But our scripts currently
    # pass around a ton of stuff with shell environment variables, so this will
    # have to do for the time being.

    run() {
    echo >&2 "+ $*"
    "$@"
    }

    # Prompt the user for a yes/no response.
    # Exit codes:
    # 0: user entered yes
    # 2: STDIN is not a TTY
    # 10: user entered no
    #
    prompt_yn() {
    local prompt ans
    if [ $# -ge 1 ]; then
    prompt="$1"
    else
    prompt="Continue?"
    fi

    if [ ! -t 0 ]; then
    echo >&2 "$prompt [y/n]"
    echo >&2 "prompt_yn: error: stdin is not a TTY!"
    return 2
    fi

    while true; do
    read -r -p "$prompt [y/n] " ans
    case "$ans" in
    Y|y|yes|YES|Yes)
    return
    ;;
    N|n|no|NO|No)
    return 10
    ;;
    esac
    done
    }

    # usage: backup_if_exists FILE/DIR MODE
    #
    # If FILE/DIR exists, prompt to back it up.
    # MODE can be --overwrite or --abort. This is used to hint the user for what
    # will happen upon a "no" answer.
    #
    # Upon yes, move it to a timestamped backup name.
    # Upon no, return 1 if --abort is given, 0 if --abort is given.
    #
    backup_if_exists() {
    local target backup_name overwrite
    target="$1"
    mode="$2"

    case "$mode" in
    --overwrite)
    overwrite=1
    prompt="Would you like to back it up? Y=backup N=overwrite"
    ;;
    --abort)
    overwrite=
    prompt="Would you like to back it up? Y=backup N=abort"
    ;;
    *)
    echo_red >&2 "Unexpected backup mode $mode"
    return 2
    ;;
    esac

    if [ -e "$target" ]; then
    echo_yellow >&2 "warning: '$target' already exists"

    prompt_yn "$prompt" && ret=$? || ret=$?

    case "$ret" in
    0)
    backup_name="$target.backup~$(date "+%F.%H-%M-%S")"
    run mv -iv "$target" "$backup_name"
    ;;
    10)
    if [ -n "$overwrite" ]; then
    echo_yellow >&2 "OK, not backing up"
    return
    else
    echo_yellow >&2 "OK, returning error $ret"
    return "$ret"
    fi
    ;;
    *)
    echo_red >&2 "Unexpected return value $ret from prompt_yn"
    return "$ret"
    ;;
    esac
    fi
    }

    echo_color() {
    local color code
    color="$1"
    shift

    case "$color" in
    red) code=31 ;;
    green) code=32 ;;
    yellow) code=33 ;;
    blue) code=34 ;;
    purple) code=35 ;;
    cyan) code=36 ;;
    *)
    echo >&2 "echo_color: unknown color $color"
    return 1
    ;;
    esac

    if [ -t 1 ]; then
    echo -ne "\\033[1;${code}m"
    fi

    echo -n "$*"

    if [ -t 1 ]; then
    echo -ne '\033[m'
    fi

    echo
    }

    echo_blue() {
    echo_color blue "$@"
    }
    echo_green() {
    echo_color green "$@"
    }
    echo_red() {
    echo_color red "$@"
    }
    echo_yellow() {
    echo_color yellow "$@"
    }

    # Print underscores as wide as the terminal screen
    echo_color_horizontal_rule() {
    declare -i width # local integer
    width="${COLUMNS-80}"

    local color

    case $# in
    0) color=blue ;;
    1) color="$1" ;;
    *)
    echo >&2 "usage: echo_color_horizontal_rule [COLOR]"
    return 1
    ;;
    esac

    echo_color "$color" "$(printf "%0.s_" $(seq 1 "$width"))"
    }

    log() {
    local color=
    if [ "${1-}" = "--blue" ]; then
    color=34
    shift
    fi

    # print our caller if possible as the basename
    if [ "${#BASH_SOURCE[@]}" -ge 2 ]; then
    local basename
    basename="${BASH_SOURCE[1]}"
    if [[ $basename = */* ]]; then
    basename="$(basename "$basename")"
    fi
    if [ -n "$color" ] && [ -t 2 ]; then
    echo >&2 -ne "\\033[1;${color}m"
    fi
    echo >&2 -n "$basename: "
    fi

    echo >&2 -n "$*"

    if [ -n "$color" ] && [ -t 2 ]; then
    echo >&2 -ne '\033[m'
    fi

    echo >&2
    }

    get_terraform_version() {
    local output

    # checkpoint is the hashicorp thing that phones home to check versions
    output="$(CHECKPOINT_DISABLE=1 run terraform --version)" || return $?

    # we do this in two phases to avoid sending SIGPIPE to terraform, which
    # would cause it to exit with status 141
    echo "$output" | head -1 | cut -d' ' -f2
    }

    assert_file_not_exists() {
    if [ -e "$1" ]; then
    echo_red >&2 "error: \`$1' already exists!"
    return 1
    fi
    }

    assert_file_exists() {
    if [ ! -e "$1" ]; then
    echo_red >&2 "error: \`$1' does not exist!"
    return 1
    fi
    }

    # usage: check_terraform_version SUPPORTED_VERSION...
    #
    # e.g. check_terraform_version v0.8.* v0.9.*
    #
    # Check whether the current version of terraform (as reported by terraform
    # --version) is in the allowed list passed as arguments. Return 0 if so,
    # otherwise return 1.
    check_terraform_version() {
    current_tf_version="$(get_terraform_version)"

    if [ $# -eq 0 ]; then
    echo_red >&2 \
    "error: no supported versions passed to check_terraform_version"
    return 2
    fi

    for version in "$@"; do
    # version is expected to be a pattern
    # shellcheck disable=SC2053
    if [[ $current_tf_version == $version ]]; then
    echo "Terraform version $current_tf_version is supported"
    return
    fi
    done

    echo_red >&2 "Terraform version $current_tf_version is not supported"
    echo_red >&2 "Expected versions: $*"

    echo >&2 "Try using \`bin/terraform-switch.sh\` to install / switch"
    echo >&2 "to a target installed version of terraform with homebrew."

    return 1
    }

    # Similar to Ruby's Array#join
    # usage: join_by DELIMITER ELEM...
    join_by() {
    local delimiter="$1"
    shift
    if [ $# -eq 0 ]; then
    echo
    return
    fi
    # print first elem with no delimiter
    echo -n "$1"
    shift

    for elem in "$@"; do
    echo -n "$delimiter$elem"
    done

    echo
    }

    # Output shell array as a terraform-compatible string
    # (1 2 3) => '["1", "2", "3"]'
    array_to_string() {
    echo "[\"$(join_by '", "' "$@")\"]"
    }


    # Usage: verify_repo_root_unchanged REPO_ROOT_BEFORE_CD BASENAME
    #
    # Double check that the repo root has not changed after executing a cd. This is
    # useful in case you are running a script like tf-deploy or diff-deploy on your
    # PATH, since the script will cd to the script's own parent directory..
    #
    # If your prior cwd repo root was not the same as the new repo root, this means
    # that you are probably executing a different script from the one you expected.
    # Prompt to confirm, since this is probably not what the user intended.
    #
    verify_repo_root_unchanged() {
    local repo_root_before_cd repo_root_after_cd BASENAME
    repo_root_before_cd="$1"
    BASENAME="$2"

    repo_root_after_cd="$(git rev-parse --show-toplevel)"
    if [ -n "$repo_root_before_cd" ] \
    && [ -e "$repo_root_before_cd/bin/$BASENAME" ] \
    && [ "$repo_root_before_cd" != "$repo_root_after_cd" ]
    then
    echo_yellow >&2 "WARNING: your cwd is in a different directory than $BASENAME.
    Are you sure you didn't mean to run ./bin/$BASENAME instead?
    Repo root from your cwd: $repo_root_before_cd
    Repo root for $BASENAME: $repo_root_after_cd"
    prompt_yn
    fi
    }