#!/usr/bin/env bash # Print current environnement current_env(){ if [ -z "${CTCTL_DOMAIN}" ]; then echo "Unknown container domain" kill -INT $$ fi echo "Cluster: ${CTCTL_DOMAIN}" if [ -z "${CTCTL_ENV}" ]; then echo "Unknown container environment" kill -INT $$ fi echo "Namespace: ${CTCTL_ENV}" } check_env() { if [ -n "${CTCTL_DOMAIN}" -a -n "${CTCTL_ENV}" ]; then echo 1 else echo 0 fi } # Run a shell in a container # TODO : to implement enter_ct(){ echo "Select the job" select J in $(get_job_list); do if [ "${REPLY}" -ge 1 ] && [ "${REPLY}" -le $(get_job_list | wc -w) ]; then JOB=${J} break else echo "Invalid selection" fi done } load_config(){ if [ -n "${CTCTL_DOMAIN}" -a -n "${CTCTL_ENV}" ]; then # Load env configuration if [ -e ~/.ctctl/${TARGET_DOM}/${CTCTL_ENV}.conf ]; then set -o allexport source ~/.ctctl/${TARGET_DOM}/${CTCTL_ENV}.conf set +o allexport fi # Load post login configuration if [ -e ~/.ctctl/${CTCTL_DOMAIN}/ctctl.local.conf ]; then set -o allexport source ~/.ctctl/${CTCTL_DOMAIN}/ctctl.local.conf set +o allexport fi fi } # Switch to a target environment (either from no current, or from another current env) switch_env(){ TARGET_DOM=$1 TARGET_NAMESPACE=$2 if [ -z "${TARGET_DOM}" ]; then echo "Select the container install you want to work on" TARGET_DOM=$(ls_env | ${FZF_CMD}) fi if [ ! -e ~/.ctctl/${TARGET_DOM}/ctctl.conf ]; then echo "Env ${TARGET_DOM} doesn't exist" kill -INT $$ fi # Clear any variable for VAR in $(printenv | grep -E '^(CTCTL|CONSUL|VAULT|NOMAD)_.*' | sed -E 's/^([^=]+)=.*/\1/'); do unset ${VAR} done export CTCTL_DOMAIN=${TARGET_DOM} # Load default config set -o allexport source ~/.ctctl/${CTCTL_DOMAIN}/ctctl.conf set +o allexport # Load pre login env configuration if [ -e ~/.ctctl/${CTCTL_DOMAIN}/pre-login.conf ]; then set -o allexport source ~/.ctctl/${CTCTL_DOMAIN}/pre-login.conf set +o allexport fi # Authenticate auth_env if [ -z "${TARGET_NAMESPACE}" ]; then if [ $(ls_namespace | wc -w) -eq 1 ]; then TARGET_NAMESPACE=$(ls_namespace) else echo "Select the namespace you are working in" TARGET_NAMESPACE=$(ls_namespace | ${FZF_CMD}) fi fi export CTCTL_ENV=${TARGET_NAMESPACE} # TODO : decide if we keep NOMAD_VAR_env export NOMAD_VAR_env=${TARGET_NAMESPACE} export NOMAD_NAMESPACE=${TARGET_NAMESPACE} } # Check if we have a valid token for vault check_vault_token(){ vault token lookup > /dev/null 2>&1 if vault token lookup > /dev/null 2>&1; then echo 1 elif [ -n "${VAULT_TOKEN:-}" -a -e ~/.vault-token ]; then # If VAULT_TOKEN is defined, unset it and try again. This is because we might # have a valid token in ~/.vault-token but an expired token is set in VAULT_TOKEN # and is taking precedence export VAULT_TOKEN=$(cat ~/.vault-token) if vault token lookup > /dev/null 2>&1; then echo 1 else echo 0 fi else echo 0 fi } # Check if we have a valid token for consul check_consul_token(){ CONSUL_TOKEN_VALID=0 if [ -n "${CONSUL_HTTP_TOKEN}" ]; then consul acl token read -self > /dev/null 2>&1 if [ $? == 0 ]; then echo 1 else echo 0 fi else echo 0 fi } # Check if we have a valid token for nomad check_nomad_token(){ if [ -n "${NOMAD_TOKEN}" ]; then nomad acl token self > /dev/null 2>&1 if [ $? == 0 ]; then echo 1 else echo 0 fi else echo 0 fi } # Auth on vault, consul and nomad on the current env auth_env(){ if [ -z "${CTCTL_DOMAIN}" ]; then echo "Unknown environment" kill -INT $$ fi if [ "$(check_vault_token)" != "1" ]; then echo "You're not connected on vault. Please enter your account password" export VAULT_TOKEN=$(vault login -field=token ${VAULT_AUTH_CONFIG:--method=ldap username=${CTCTL_USER:-$(whoami | sed -r 's/\@.*//')}} || kill -INT $$) echo "Logged on vault successfuly" else echo "Your vault token is valid" vault token renew > /dev/null 2>&1 fi VAULT_TOKEN_INFO=$(vault token lookup -format=json) # TODO make the role selection more generic if [ "$(echo $VAULT_TOKEN_INFO | jq '.data.policies | any(. == "admin-policy" or .== "admin")')" == "true" ]; then NOMAD_ROLE=admin CONSUL_ROLE=admin else NOMAD_ROLE=user CONSUL_ROLE=user fi # Root CA vault read -field certificate pki/root/cert/ca > ~/.ctctl/${CTCTL_DOMAIN}/root_ca.crt # Consul certificate # Get/renew cert if required. # Note 1: as the template is using pkiCert, the cert won't be renewed, unless necessary # Note 2: don't pass CONSUL_CLIENT_CERT CONSUL_CLIENT_KEY and CONSUL_CACERT because they would prevent consul-template from starting # to get/renew the cert if they are absent, or expired env -u CONSUL_CLIENT_CERT \ -u CONSUL_CLIENT_KEY \ -u CONSUL_CACERT \ consul-template -config ~/.ctctl/${CTCTL_DOMAIN}/consul/consul-template.hcl -once # Get/renew cert for Nomad now consul-template -config ~/.ctctl/${CTCTL_DOMAIN}/nomad/consul-template.hcl -once # Check if we have a valid nomad token already if [ "$(check_nomad_token)" != "1" ]; then echo "Fecthing a Nomad token from vault" NOMAD_CREDS=$(vault read -format=json ${VAULT_PREFIX:-}nomad/creds/${NOMAD_ROLE}) export NOMAD_TOKEN=$(echo -n ${NOMAD_CREDS} | jq -r .data.secret_id) export NOMAD_LEASE=$(echo -n ${NOMAD_CREDS} | jq -r .lease_id) unset NOMAD_CREDS else echo "Nomad token is valid, renewing lease" vault lease renew ${NOMAD_LEASE} >/dev/null fi # Check if we have a valid consul token already if [ "$(check_consul_token)" != "1" ]; then echo "Fetching a Consul token from vault" CONSUL_CREDS=$(vault read -format=json ${VAULT_PREFIX:-}consul/creds/${CONSUL_ROLE}) export CONSUL_HTTP_TOKEN=$(echo -n ${CONSUL_CREDS} | jq -r .data.token) export CONSUL_LEASE=$(echo -n ${CONSUL_CREDS} | jq -r .lease_id) unset CONSUL_CREDS else echo "Consul token is valid, renewing lease" vault lease renew ${CONSUL_LEASE} >/dev/null fi load_config } renew_leases(){ # Renew vault token [ -n "${VAULT_TOKEN}" ] && vault token renew >/dev/null [ -n "${NOMAD_LEASE}" ] && vault lease renew ${NOMAD_LEASE} >/dev/null [ -n "${CONSUL_LEASE}" ] && vault lease renew ${CONSUL_LEASE} > /dev/null } # Logout from the current env logout_env(){ if [ -z "${CTCTL_DOMAIN}" ]; then echo "Unknown environment" kill -INT $$ fi echo "Disconecting from ${CTCTL_DOMAIN} environment" vault token revoke -self for VAR in $(printenv | perl -ne '/^((CTCTL|CONSUL|VAULT|NOMAD|LOKI)_[^=]+)=.*/ && print "$1\n"'); do unset $VAR done rm -f ~/.vault-token } # List available env ls_env(){ find ~/.ctctl/ -name ctctl.conf | xargs dirname | xargs basename -a } # List available namespaces ls_namespace(){ nomad namespace list -json | jq -r ".[] | .Name" } # List buildable Docker images ls_build_docker_images(){ (for JOB in $(find . -maxdepth 1 \( -name \*.nomad -o -name \*.nomad.hcl \)); do nomad run -output $JOB | jq '.Job.TaskGroups' | jq '.[] | .Tasks' | jq -r '.[] | .Config.image' 2>/dev/null done) | grep -E "${CTCTL_DOCKER_BUILD_REPO_REGEX:-docker-repo.ehtrace.com}" | sort -u } # Load policies for vault, Consul and Nomad load_policies(){ if [ "$(check_env)" = "0" ]; then echo "Not currently in a valid env. Run ctctl (with no argument) and select your env first" kill -INT $$ fi for DIR in ./output ./; do if [ -d "${DIR}/vault/policies" ]; then if [ "$(check_vault_token)" != "1" ]; then echo "No valid vault token. You have to authenticate first" kill -INT $$ fi for PFILE in $(ls ${DIR}/vault/policies/*.hcl 2>/dev/null); do PNAME=$(basename ${PFILE} .hcl) echo "Loading vault policy ${PNA}" replace_conf_var ${PFILE} | vault policy write ${PNAME} - done fi if [ -d "${DIR}/consul/policies" ]; then if [ "$(check_consul_token)" != "1" ]; then echo "No valid consul token. You have to authenticate first" kill -INT $$ fi CONSUL_CUR_POLICIES=$(consul acl policy list -format=json) for PFILE in $(ls ${DIR}/consul/policies/*.hcl 2>/dev/null); do PNAME=$(basename ${PFILE} .hcl) # Consul do not use the same command to create a new policy and to update an existing one # so we need to detect if the policy already exists if [ "$(echo ${CONSUL_CUR_POLICIES} | jq -r '.[] | select(.Name=='\"${PNAME}\"') | .Name')" == "${PNAME}" ]; then echo "Updating consul policy ${PNAME}" replace_conf_var ${PFILE} | consul acl policy update -name=${PNAME} -rules=- else echo "Adding new consul policy ${PNAME}" replace_conf_var ${PFILE} | consul acl policy create -name=${PNAME} -rules=- fi done fi if [ -d "${DIR}/nomad/policies" ]; then if [ "$(check_nomad_token)" != "1" ]; then echo "No valid nomad token. You have to authenticate first" kill -INT $$ fi for PFILE in $(ls ${DIR}nomad/policies/*.hcl 2>/dev/null); do PNAME=$(basename ${PFILE} .hcl) echo "Loading Nomad policy ${PNAME}" replace_conf_var ${PFILE} | nomad acl policy apply ${PNAME} - done fi done } # Load consul config load_consul_conf(){ for DIR in ./output ./; do if [ -d "${DIR}/consul/config" ]; then if [ "$(check_consul_token)" != "1" ]; then echo "No valid consul token. You have to authenticate first" kill -INT $$ fi # Note : service-defaults should be loaded before the others # but that should be the case for FILE in $(ls ${DIR}/consul/config/*.hcl 2>/dev/null); do echo "Loading consul conf from ${FILE}" TEMP=$(mktemp) replace_conf_var ${FILE} > ${TEMP} consul config write ${TEMP} rm -f ${TEMP} done # Support storing consul config in subdir eg consul/config/service-defaults/foo.hcl # Or you can even omit service and use consul/config/defaults/bar.hcl, consul/config/intentions/bar.hcl for KIND in service-defaults service-intentions service-router service-resolver proxy-defaults; do if [ -d ${DIR}/consul/config/${KIND} ]; then for FILE in $(ls ${DIR}/consul/config/${KIND}/*.hcl 2>/dev/null); do echo "Loading consul conf from ${FILE}" TEMP=$(mktemp) replace_conf_var ${FILE} > ${TEMP} consul config write ${TEMP} rm -f ${TEMP} done fi done fi done } # Build all images for the current project build_required_images(){ for DOCKER_IMAGE in $(ls_build_docker_images); do if ! docker manifest inspect ${DOCKER_IMAGE} > /dev/null 2>&1; then build_image ${DOCKER_IMAGE} else echo "Image ${DOCKER_IMAGE} already available" fi done } # Build selected images build_selected_images(){ local NO_CACHE=$1 for DOCKER_IMAGE in $(ls_build_docker_images | ${FZF_CMD} -m); do build_image "${DOCKER_IMAGE}" ${NO_CACHE} done } # Build a single image build_image(){ local DOCKER_IMAGE=$1 local NO_CACHE=$2 export DOCKER_BUILDKIT=1 echo "Building image ${DOCKER_IMAGE}" # Extract the basename of the image, removing the repo and the tag local IMAGE_NAME=$(echo -n ${DOCKER_IMAGE} | sed -E 's/.+\/([^\/]+):.*/\1/') export DOCKER_IMAGE=${DOCKER_IMAGE} local LATEST=$(echo ${DOCKER_IMAGE} | sed 's/:.*/:latest/') if [ "${NO_CACHE}" != "" ]; then NO_CACHE="--no-cache" else NO_CACHE="" fi local FOUND=0 for DIR in ./images ./output/images; do if [ -d $DIR/${IMAGE_NAME} ]; then docker build ${NO_CACHE} -t ${DOCKER_IMAGE} ${CTCTL_DOCKER_BUILD_OPTS:-} --progress=plain $DIR/${IMAGE_NAME} &&\ docker push ${DOCKER_IMAGE} &&\ docker push ${LATEST} FOUND=1 break fi done if [ "${FOUND}" = "0" ]; then echo "Couldn't find Docker image directory" kill -INT $$ fi unset DOCKER_BUILDKIT } # Run all executable in the prep.d directory handle_prep_scripts(){ for DIR in ./output ./; do if [ -d "${DIR}/prep.d" ]; then for H in $(find ${DIR}/prep.d -type f -o -type l | sort); do if [ -x "${H}" ]; then echo "Running prep script ${H}" $H $1 else echo "Skiping prep script $H (not executable)" fi done fi done } # Update ctctl bundles with git update_submodules(){ if [ -e ctctl.bundle.conf ]; then set -o allexport source ./ctctl.bundle.conf set +o allexport for ID in $(printenv | grep -P '^CTCTL_BUNDLE_\d+_NAME=' | sed -E 's/CTCTL_BUNDLE_([0-9]+)_NAME.*/\1/'); do local NAME=$(printenv CTCTL_BUNDLE_${ID}_NAME) local URL=$(printenv CTCTL_BUNDLE_${ID}_URL) local BRANCH=$(printenv CTCTL_BUNDLE_${ID}_BRANCH || echo master) echo "Working on the ${NAME} bundle" if [ ! -d bundles/${NAME} ]; then echo "Adding sub module from ${URL} (branch ${BRANCH})" git submodule add --branch ${BRANCH} --name ${NAME} --force ${URL} bundles/${NAME} else echo "Updating ${NAME} from ${URL} (branch ${BRANCH})" git submodule set-branch --branch ${BRANCH} bundles/${NAME} fi git submodule update --init --recursive --remote --merge bundles/${NAME} done fi } # Render templates using gomplate (or levant for backward compat) render_templates(){ # If a ctctl.bundle.conf file exist, use the new gomplate rendering method if [ -e ctctl.bundle.conf ]; then set -o allexport source ./ctctl.bundle.conf set +o allexport mkdir -p bundles for ID in $(printenv | grep -P '^CTCTL_BUNDLE_\d+_NAME=' | sed -E 's/CTCTL_BUNDLE_([0-9]+)_NAME.*/\1/'); do local NAME=$(printenv CTCTL_BUNDLE_${ID}_NAME) local URL=$(printenv CTCTL_BUNDLE_${ID}_URL) local BRANCH=$(printenv CTCTL_BUNDLE_${ID}_BRANCH || echo master) echo "Working on the ${NAME} bundle" if [ ! -d bundles/${NAME} ]; then update_submodules fi # Use [[ and ]] so it won't clash with consul-template fragments local GOMPLATE_COMMON_ARGS=(--left-delim '[[' --right-delim ']]') # The context will merge various configuration files to get the variables used to render the templates GOMPLATE_COMMON_ARGS+=(--context) # Build a list of configuration file to merge # Files are in order of precedence (firsts win) I=0 local VAR_FILES="" for FILE in ${NOMAD_NAMESPACE}.yml \ ${NOMAD_NAMESPACE}.yaml \ variables.yml \ variables.yaml \ ../${NOMAD_NAMESPACE}.yml \ ../${NOMAD_NAMESPACE}.yaml \ ../variables.yml \ ../variables.yaml \ bundles/${NAME}/variables.yml \ bundles/${NAME}/variables.yaml \ bundles/${NAME}/deps/*/variables.yml \ bundles/${NAME}/deps/*/variables.yaml; do if [ -e ${FILE} ]; then if [ $I -eq 0 ]; then VAR_FILES+='.=merge:' else VAR_FILES+='|' fi VAR_FILES+="${FILE}" I=$((I+1)) fi done GOMPLATE_COMMON_ARGS+=("${VAR_FILES}") for TEMPLATE_DIR in $(find bundles -type d -name templates); do GOMPLATE_COMMON_ARGS+=(--template "$(basename $(dirname ${TEMPLATE_DIR}))=${TEMPLATE_DIR}") # Also declare sub-folders as external templates for SUBFOLDER in $(find ${TEMPLATE_DIR} -mindepth 1 -maxdepth 2 -type d); do GOMPLATE_COMMON_ARGS+=(--template "$(basename $(dirname ${TEMPLATE_DIR}))/$(basename ${SUBFOLDER})=${SUBFOLDER}") done done local GOMPLATE_BUNDLE_ARGS=(--input-dir "bundles/${NAME}") # Do not render templates from dependencies, variables files and images (images will be handled later) GOMPLATE_BUNDLE_ARGS+=(--exclude .git* --exclude deps/** --exclude variables.yml --exclude images/** --exclude templates/**) # This is used for two things # - Add the env.suffix to every files (except job files). This permit ctctl to simply infer the policy name from the file name # - Put job files in the current dir for conveniance, and everything else in the output dir GOMPLATE_BUNDLE_ARGS+=(--output-map) GOMPLATE_BUNDLE_ARGS+=('[[ if (regexp.Match ".*\\.nomad(\\.hcl)?" .in) ]][[ .in ]][[ else ]]output/[[ .in | path.Dir ]]/[[ .in | path.Base | regexp.Replace "^([^\\.]+)\\.(.*)$" (printf "%s%s.%s" "$1" .ctx.env.suffix "$2") ]][[ end ]]') echo echo "Redering bundles with gomplate ${GOMPLATE_COMMON_ARGS[@]} ${GOMPLATE_BUNDLE_ARGS[@]}" # First, cleanup any previously rendered files rm -rf output ./*.nomad ./*.nomad.hcl gomplate "${GOMPLATE_COMMON_ARGS[@]}" "${GOMPLATE_BUNDLE_ARGS[@]}" for IMGDIR in $(find . -name images -type d); do for DOCKER_IMAGE in $(find ${IMGDIR} -mindepth 1 -maxdepth 1 -type d); do echo "Redering Docker image $(basename ${DOCKER_IMAGE})" gomplate "${GOMPLATE_COMMON_ARGS[@]}" --input-dir ${DOCKER_IMAGE} --exclude resources/** --exclude root/** --output-dir output/images/$(basename ${DOCKER_IMAGE})/ for ROOT in resources root; do if [ -d "${DOCKER_IMAGE}/${ROOT}" ]; then cp -r "${DOCKER_IMAGE}/${ROOT}" output/images/$(basename ${DOCKER_IMAGE})/ fi done done done echo echo "Formating job files" find ./ -maxdepth 1 -type f \( -name \*nomad.hcl -o -name \*.nomad \) -exec nomad fmt {} \; # Run prep.d scripts handle_prep_scripts done # Cleanup variables for VAR in $(printenv | grep -E '^CTCTL_BUNDLE_.*' | sed -E 's/^([^=]+)=.*/\1/'); do unset ${VAR} done else # backward compatible, levant based rendering MERGED_CONF=$(mktemp tmp.XXXXXXXX.yml) get_merged_conf > ${MERGED_CONF} # TODO : handle prep scripts with the new bundles system handle_prep_scripts ${MERGED_CONF} for TEMPLATE in $(find . -type f -name \*.tpl ! -path "*templates/*"); do local DIR=$(dirname ${TEMPLATE}) local FILE=$(basename ${TEMPLATE} .tpl) local DEST=${DIR}/${FILE} echo "Rendering ${TEMPLATE} into ${DEST}" # Note: render twice, so included templates get rendered too levant render -var-file ${MERGED_CONF} -log-level=WARN <(levant render -var-file ${MERGED_CONF} -log-level=WARN ${TEMPLATE}) > ${DEST} nomad fmt ${DEST} done rm -f ${MERGED_CONF} fi } # Print Consul and Nomad tokens (not vault, for security reasons) print_tokens(){ if [ "$(check_nomad_token)" == "1" ]; then echo "Nomad token: ${NOMAD_TOKEN}" else echo "No valid Nomad token, you should auth with ctctl auth" fi if [ "$(check_consul_token)" == "1" ]; then echo "Consul token: ${CONSUL_HTTP_TOKEN}" else echo "No valid Consul token, you should auth with ctctl auth" fi } # Follow current jobs logs job_logs(){ # Remove the first arg passed to ctctl, which is logs shift local SELECTOR local LOGCLI_CMD if [ -z "${LOKI_ADDR}" ]; then echo "You need to configure loki first (LOKI_ADDR, LOKI_USERNAME and LOKI_PASSWORD or LOKI_PWD_CMD)" kill -INT $$ fi if [ -n "${LOKI_PWD_CMD}" ]; then export LOKI_PASSWORD=$(${LOKI_PWD_CMD}) fi LOGCLI_CMD="logcli query --include-label=job --include-label=group --include-label=task" echo -n "$*" | grep -qP '\{.+\}' >/dev/null 2>&1 # If a logcli filter was given, use it. Else, build one for jobs in the current dir if [ $? == 0 ]; then echo "Running ${LOGCLI_CMD} $@" ${LOGCLI_CMD} $@ else # Exclude connect-proxy logs as it's often not wanted SELECTOR='{job=~"'$(get_job_list | sed 's/\s/|/g')'", task!~"connect-proxy-.+|tls-proxy|metrics-proxy"}' echo "Running ${LOGCLI_CMD} $@ ${SELECTOR}" ${LOGCLI_CMD} $@ "${SELECTOR}" fi unset LOKI_PASSWORD } ### Helpers ### # Merge the configuration files for the current env and return the result (as string) get_merged_conf() { CONF_FILES="" if [ -e "./vars/defaults.yml" ]; then CONF_FILES="./vars/defaults.yml" fi if [ -e "jobs/common/vars/${CTCTL_ENV}.yml" ]; then CONF_FILES="${CONF_FILES} jobs/common/vars/${CTCTL_ENV}.yml" fi if [ -e "../common/vars/${CTCTL_ENV}.yml" ]; then CONF_FILES="${CONF_FILES} ../common/vars/${CTCTL_ENV}.yml" fi if [ -e "./vars/${CTCTL_ENV}.yml" ]; then CONF_FILES="${CONF_FILES} ./vars/${CTCTL_ENV}.yml" fi if [ "${CONF_FILES}" != "" ]; then echo "---" yq ea '. as $item ireduce ({}; . * $item | ... comments="")' $CONF_FILES else echo -n "" fi } # Replace ${local.conf.foo} or ${foo} with the value of foo from the various merged configuration files # This is used to have policies (vault, consul, nomad) and config (consul intentions etc.) with variables replace_conf_var() { MERGED_CONF=$(mktemp) get_merged_conf > $MERGED_CONF RES=$(cat $1 | \ # Replace ${local.conf.foo} or ${foo} with the value of foo from the various merged configuration files \ # This is used to have policies (vault, consul, nomad) and config (consul intentions etc.) with variables \ perl -pe 'sub replace($) { my $val = shift; chomp(my $res = qx(yq .$val '$MERGED_CONF')); return $res; }; s!\$\{(local\.conf\.)?([^\}]+)\}! replace($2) !ge' | \ # Replace $(foo) with the output of foo command, mainly used to fetch secrets from vault \ perl -pe 'sub replace($) { my $val = shift; chomp(my $res = qx($val)); return $res; }; s!\$\(([^\)]+)\)! replace($1) !ge' ) rm -f $MERGED_CONF echo "${RES}" } # Get a value from the conf get_conf(){ get_merged_conf | yq ".$1" } # Return a space separated list of jobs the current dir get_job_list(){ local JOBS="" for JOBFILE in $(find . -maxdepth 1 \( -name \*.nomad -o -name \*.nomad.hcl \)); do JOBS="${JOBS} $(nomad run -output ${JOBFILE} | jq -r '.Job.Name')" done echo $JOBS } FZF_CMD=${CTCTL_FZF_CMD:-fzf --height=~10% --cycle --bind 'space:toggle' --marker='*'} case $1 in current) current_env renew_leases ;; auth) auth_env ;; disconnect) logout_env ;; ls|list) ls_env renew_leases ;; render) render_templates renew_leases ;; prep) update_submodules render_templates load_policies load_consul_conf build_required_images renew_leases ;; build) build_selected_images renew_leases ;; build-no-cache) build_selected_images "no-cache" renew_leases ;; tokens) print_tokens renew_leases ;; logs) renew_leases job_logs "$@" ;; conf) renew_leases get_merged_conf ;; sh) enter_ct ;; switch) shift switch_env "$@" ;; *) switch_env "$@" ;; esac