pedroMMM/goss

View on GitHub
extras/kgoss/kgoss

Summary

Maintainability
Test Coverage
#! /usr/bin/env bash

set -eo pipefail

info() {
    echo -e "[INFO]: $*" >&2
}

error() {
    echo -e "[ERROR]: $*" >&2
    exit 1
}

usage() {
>&2 cat <<-'EOF'
Usage: $(basename $0) [command] [options]

## Commands:

* `run` executes goss in the pod/container with ./goss.yaml as input (by
default).
* `edit` opens a prompt inside the container to run `goss add ...`
and copies out files when complete.

## Options:

-i="image_url:tag" - full URL of container image
-d="additional directories to copy to container" - may be specified zero to
    many times
-e="envvar_key=value" - may be specified zero to many times
-p - (flag) pause container on entry
-c="cmd to run" - command to execute as container entry point
-a="args to entrypoint"

If -p, -c and -a are not specified, container will run its ENTRYPOINT.

-e and -d can be specified multiple times.

## Environment variables and default values:

GOSS_KUBECTL_BIN="$(which kubectl)": location of kubectl-compatible binary
GOSS_PATH="$(which goss)": location of goss binary
GOSS_FILES_PATH=".": location of goss.yaml and other configuration files
GOSS_VARS="": path to a goss.vars file
GOSS_OPTS="--color --format documentation": options passed to goss
GOSS_WAIT_OPTS="-r 30s -s 1s > /dev/null": options passed to goss
GOSS_CONTAINER_PATH="/tmp/goss": path to copy files in container, and working dir for tests
EOF

exit 2
}

# GOSS_PATH
if [[ -z "${GOSS_PATH}" ]]; then
    if [[ $(which goss 2> /dev/null) ]]; then
        GOSS_PATH=$(which goss 2> /dev/null)
    elif [[ -e "${HOME}/goss" ]]; then
        GOSS_PATH="${HOME}/goss"
    elif [[ -e "${HOME}/bin/goss" ]]; then 
        GOSS_PATH="${HOME}/bin/goss"
    else
        error "Couldn't find goss, please set GOSS_PATH to it"
    fi
fi

# GOSS_KUBECTL_BIN
GOSS_KUBECTL_BIN=${GOSS_KUBECTL_BIN:-$(which kubectl 2> /dev/null || true)}
if [[ -z "$GOSS_KUBECTL_BIN" ]]; then error "kgoss requires kubectl in your PATH"; fi
k=${GOSS_KUBECTL_BIN}

GOSS_FILES_PATH="${GOSS_FILES_PATH:-.}"
GOSS_OPTS=${GOSS_OPTS:-"--color --format documentation"}
GOSS_WAIT_OPTS=${GOSS_WAIT_OPTS:-"-r 30s -s 1s > /dev/null"}
GOSS_CONTAINER_PATH=${GOSS_CONTAINER_PATH:-/tmp/goss}

kgoss_cmd=run
image=
pause=0
cmd=''
args=''
to_exec=''
envs=''
include_goss_files_dir=0
dirs_array=()

cleanup() {
    set +ex
    rm -rf "$tmp_dir"
    if [[ -n "$id" ]]; then
        info "Deleting pod/container"
        ${k} delete pod "$id" > /dev/null
    fi
}

# parse checks for a bare `-d` flag and if set includes GOSS_FILES_PATH in dirs
# to upload to pod
parse() {
  # handle deprecated bare `-d`
  i=0
  original_args=("$@")
  new_args=()
  re='^-'
  for arg in "${original_args[@]}"; do
    if [[ "${arg}" == '-d' ]]; then
      # check if next word starts with '-'
      if [[ "${original_args[$(($i+1))]}" =~ $re ]]; then
        # since it does, mark to copy whole dir and remove this arg
        include_goss_files_dir=1
        i=$(($i+1))
        continue
      fi
    fi
    i=$(($i+1))
    new_args+=("${arg}")
  done
  # end handle `-d`

  # now call original parse_internal func
  parse_internal "${new_args[@]}"
}

parse_internal() {
  info "Parsing command line"
  kgoss_cmd=$1; shift
  if [[ ( ! "${kgoss_cmd}" == "run" ) && ( ! "${kgoss_cmd}" == 'edit' ) ]]; then usage; fi
  envs_array=()
  while getopts 'i:pc::a::d::e::' arg; do
    case $arg in
      i)
        image="${OPTARG}"
        info "using image: $image"
        ;;
      p)
        pause=1
        ;;
      c)
        cmd="${OPTARG}"
        ;;
      a)
        args="${OPTARG}"
        ;;
      d)
        dirs_array+=("${OPTARG}")
        ;;
      e)
        envs_array+=("${OPTARG}")
        ;;
      *)
        info "invalid option specified"
        usage
        ;;
    esac
  done

  for envvar in "${envs_array[@]}"; do
    envs+=" --env=${envvar}"
  done

  # if -p (pause) is set, then -c (command) and -a (args) should be empty and
  # we inject a pause
  if [[ $pause == 1 ]]; then
    if [[ ! ( -z "$cmd" && -z "$args" ) ]]; then
      error "cannot specify -p and -c or -a"
    fi
    to_exec="--command -- sleep 1h"
  else
    # if not -p (pause), then either:
    #   * one of -c (command) or -a (args) should be set
    #   * neither should be set and we default to entrypoint
    if [[ -n "$cmd" && -n "$args" ]]; then
      error "cannot specify both -c and -a"
    fi
    if [[ -n "$cmd" ]]; then
      to_exec="--command -- $cmd"
    fi
    if [[ -n $"args" ]]; then
      to_exec="-- $args"
    fi
  fi
  info "going to execute (may be blank): ${to_exec}"
}

# initialize starts the pod to be tested and copies goss files into it
initialize () {
    info "Preparing files to copy into container"
    cp "${GOSS_PATH}" "$tmp_dir/goss" && chmod 0775 "$tmp_dir/goss"
    [[ -e "${GOSS_FILES_PATH}/goss.yaml" ]] && cp "${GOSS_FILES_PATH}/goss.yaml" "$tmp_dir"
    [[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]] && cp "${GOSS_FILES_PATH}/goss_wait.yaml" "$tmp_dir"
    [[ ! -z "${GOSS_VARS}" ]] && [[ -e "${GOSS_FILES_PATH}/${GOSS_VARS}" ]] && cp "${GOSS_FILES_PATH}/${GOSS_VARS}" "$tmp_dir"
    if [[ ${include_goss_files_dir} == 1 ]]; then cp -r ${GOSS_FILES_PATH}/* "${tmp_dir}"; fi
    for dir in "${dirs_array[@]}"; do
      cp -r ${dir} "${tmp_dir}/"
    done

    GOSS_FILES_STRATEGY=${GOSS_FILES_STRATEGY:="cp"}
    case "$GOSS_FILES_STRATEGY" in
      cp)
        info "Creating Kubernetes pod/container to test"
        test_pod_name=kgoss-tester-${RANDOM}
        set -x
        id=$(${k} run $test_pod_name --image-pull-policy=Always --generator=run-pod/v1 --restart=Never \
          --labels='app=kgoss-test' --output=jsonpath={.metadata.name} ${envs} \
          --image=${image} ${to_exec})
        set +x
        info "Waiting for container to be ready"
        ${k} wait pod/${test_pod_name} --for=condition=Ready --timeout=60s
        info "Copying goss files into pod/container"
        ${k} cp $tmp_dir/. ${id}:${GOSS_CONTAINER_PATH}/
        info "Marking copied files as executable"
        ${k} exec "$id" -- sh -c "chmod --recursive --changes a+x ${GOSS_CONTAINER_PATH}/"
        ;;
      *) error "Wrong kgoss files strategy used! Only \"cp\" is supported."
    esac

    info "Using pod/container: ${id}"
}

# get_pod_file copies the specified file from the pod to a local path
get_pod_file() {
    if ${k} exec "$id" -- sh -c "test -e ${GOSS_CONTAINER_PATH}/$1" &> /dev/null; then
        mkdir -p "${GOSS_FILES_PATH}"
        info "Copied '$1' from pod/container to '${GOSS_FILES_PATH}'"
        ${k} cp "${id}:${GOSS_CONTAINER_PATH}/$1" "${GOSS_FILES_PATH}"
    fi
}

main() {
    kernel="$(uname -s)"
    case "${kernel}" in
        MINGW*) prefix="winpty" ;;
        *)      prefix="" ;;
    esac

    tmp_dir=$(mktemp -d /tmp/tmp.XXXXXXXXXX)
    chmod 777 "$tmp_dir"
    trap 'ret=$?; cleanup; exit $ret' EXIT

    parse "$@"
    initialize

    # execute
    case $kgoss_cmd in
        run)
            # wait for goss_wait.yaml if present
            if [[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]]; then
                info "Found goss_wait.yaml, waiting for it to pass before running tests"
                if [[ -z "${GOSS_VARS}" ]]; then
                    if ! ${k} exec "$id" -- sh -c "${GOSS_CONTAINER_PATH}/goss -g ${GOSS_CONTAINER_PATH}/goss_wait.yaml validate $GOSS_WAIT_OPTS"; then
                        error "goss_wait.yaml never passed"
                    fi
                else
                    if ! ${k} exec "$id" -- sh -c "${GOSS_CONTAINER_PATH}/goss -g ${GOSS_CONTAINER_PATH}/goss_wait.yaml --vars='${GOSS_CONTAINER_PATH}/${GOSS_VARS}' validate $GOSS_WAIT_OPTS"; then
                        error "goss_wait.yaml never passed"
                    fi
                fi
            fi

            # running tests in pod/container
            info "Running tests within pod/container"
            if [[ -z "${GOSS_VARS}" ]]; then
                ${k} exec "$id" -- sh -c "cd ${GOSS_CONTAINER_PATH}; ${GOSS_CONTAINER_PATH}/goss -g ${GOSS_CONTAINER_PATH}/goss.yaml validate $GOSS_OPTS"
            else
                ${k} exec "$id" -- sh -c "cd ${GOSS_CONTAINER_PATH}; ${GOSS_CONTAINER_PATH}/goss -g ${GOSS_CONTAINER_PATH}/goss.yaml --vars='${GOSS_CONTAINER_PATH}/${GOSS_VARS}' validate $GOSS_OPTS"
            fi
            ;;
        edit)
            info "When prompt appears you can run \`goss add\` to add resources"
            ${prefix} ${k} exec -it "$id" -- sh -c "cd ${GOSS_CONTAINER_PATH}; PATH=\"${GOSS_CONTAINER_PATH}:$PATH\" exec sh" || true
            echo "Copying goss.yaml and goss_wait.yaml files back to local dir"
            get_pod_file "goss.yaml"
            get_pod_file "goss_wait.yaml"
            [[ ! -z "${GOSS_VARS}" ]] && get_pod_file "${GOSS_VARS}"
            ;;
        *)
            echo "invalid kgoss command, valid commands are 'run' and 'edit'"
            usage
            ;;
    esac
}

main "$@"