diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..e3efa93d81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.bin +/_test/secrets/*.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..f61c1179be --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: bash + +script: + - make test-charts diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..77e1990e02 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test-charts: + @./_test/test-charts $(TEST_CHARTS) diff --git a/_test/config.sh b/_test/config.sh new file mode 100644 index 0000000000..0ca3fd8868 --- /dev/null +++ b/_test/config.sh @@ -0,0 +1,30 @@ +export TEST_ROOT_DIR="$(cd "$(dirname $(dirname $0))"; pwd)" +export HELM_ARTIFACT_REPO="${HELM_ARTIFACT_REPO:-helm-ci}" +CLUSTER_NAME="${CLUSTER_NAME:-helm-test-travis}" +GOOGLE_SDK_DIR="${HOME}/google-cloud-sdk" +export HELM_BIN="${TEST_ROOT_DIR}/helm.bin" +export TEST_DIR="${TEST_ROOT_DIR}/_test" +export SECRETS_DIR="${TEST_DIR}/secrets" +SKIP_DESTROY=${SKIP_DESTROY:-false} + +export HEALTHCHECK_TIMEOUT_SEC=120 + +GCLOUD_CREDENTIALS_FILE="${GCLOUD_CREDENTIALS_FILE:-"${SECRETS_DIR}/gcloud-credentials.json"}" + +GCLOUD_PROJECT_ID="${GCLOUD_PROJECT_ID:-${CLUSTER_NAME}}" +K8S_ZONE="${K8S_ZONE:-us-central1-b}" +K8S_CLUSTER_NAME="${K8S_CLUSTER_NAME:-${GCLOUD_PROJECT_ID}-$(openssl rand -hex 2)}" + + + +# Text color variables +txtund=$(tput sgr 0 1) # Underline +txtbld=$(tput bold) # Bold +bldred=${txtbld}$(tput setaf 1) # red +bldblu=${txtbld}$(tput setaf 4) # blue +bldwht=${txtbld}$(tput setaf 7) # white +txtrst=$(tput sgr0) # Reset + +pass="${bldblu}-->${txtrst}" +warn="${bldred}-->${txtrst}" +ques="${bldblu}???${txtrst}" diff --git a/_test/gke.sh b/_test/gke.sh new file mode 100644 index 0000000000..8b2c2f792a --- /dev/null +++ b/_test/gke.sh @@ -0,0 +1,50 @@ +function gke-install { + if [ ! -d "${GOOGLE_SDK_DIR}" ]; then + export CLOUDSDK_CORE_DISABLE_PROMPTS=1 + curl https://sdk.cloud.google.com | bash + fi + + export PATH="{GOOGLE_SDK_DIR}/bin:$PATH" + gcloud -q components update kubectl +} + +function gke-login { + if [ -f ${GCLOUD_CREDENTIALS_FILE} ]; then + gcloud -q auth activate-service-account --key-file "${GCLOUD_CREDENTIALS_FILE}" + else + log-warn "No credentials file located at ${GCLOUD_CREDENTIALS_FILE}" + log-warn "You can set this via GCLOUD_CREDENTIALS_FILE" + return 1 + fi +} + +function gke-config { + gcloud -q config set project "${GCLOUD_PROJECT_ID}" + gcloud -q config set compute/zone "${K8S_ZONE}" +} + +function gke-create-cluster { + log-lifecycle "Creating cluster ${K8S_CLUSTER_NAME}" + gcloud -q container clusters create "${K8S_CLUSTER_NAME}" + gcloud -q config set container/cluster "${K8S_CLUSTER_NAME}" + gcloud -q container clusters get-credentials "${K8S_CLUSTER_NAME}" +} + +function gke-destroy { + if [ "${SKIP_DESTROY}" == false ]; then + log-lifecycle "Destroying cluster ${K8S_CLUSTER_NAME}" + if command -v gcloud &>/dev/null; then + gcloud -q container clusters delete "${K8S_CLUSTER_NAME}" --no-wait + log-info "Cluster ${K8S_CLUSTER_NAME} scheduled for destruction. Muahaha." + fi + else + log-warn "Skipping destruction of ${K8S_CLUSTER_NAME} (SKIP_DESTROY=${SKIP_DESTROY})" + fi +} + +function setup-gke { + gke-install + gke-login + gke-config + gke-create-cluster +} diff --git a/_test/lib.sh b/_test/lib.sh new file mode 100644 index 0000000000..8d2603565e --- /dev/null +++ b/_test/lib.sh @@ -0,0 +1,144 @@ +function jigger-files-for-travis { + helm update # need ~/.helm/config.yml + + if [ "${TRAVIS}" == true ]; then + mkdir -p ${TRAVIS_HOME}/.helm/cache + cp -r . ${TRAVIS_HOME}/.helm/cache + fi +} + +function install-helm { + # Uses HELM_ARTIFACT_REPO to determine which repository to grab helm from + + log-lifecycle "Installing helm into $(pwd)/.bin" + + mkdir -p .bin + ( + cd .bin + curl -s https://get.helm.sh | sh + ) + export PATH="$(pwd)/.bin:${PATH}" + + jigger-files-for-travis +} + +function get-changed-charts { + git diff --name-only HEAD origin/HEAD -- charts \ + | cut -d/ -f 1-2 \ + | sort \ + | uniq +} + +function ensure-dirs-exist { + local dirs="${@}" + local pruned + + # ensure directories exist and just output directory name + for dir in ${dirs}; do + if [ -d ${dir} ]; then + pruned+="$(basename "${dir}")\n" + fi + done + + echo -e "${pruned}" +} + +function generate-test-plan { + ensure-dirs-exist "$(get-changed-charts)" +} + +function get-all-charts { + local chartlist="$(find . -name Chart.yaml)" + local cleanedlist + + local chart + for chart in ${chartlist}; do + cleanedlist+="$(basename $(dirname ${chart})) " + done + + echo "${cleanedlist}" +} + +function helm-test { + log-warn "Start: ${1}" + .bin/helm install ${1} + healthcheck ${1} + .bin/helm uninstall -y ${1} + log-warn "Done: ${1}" +} + +function run-test-plan { + local test_plan + + if is-pull-request; then + test_plan="$(generate-test-plan)" + else + if [ -z "${TEST_CHARTS}" ]; then + test_plan="$(get-all-charts)" + else + test_plan="${TEST_CHARTS}" + fi + fi + + log-lifecycle "Running test plan" + log-info "Charts to be tested:" + echo "${test_plan}" + + local plan + for plan in ${test_plan}; do + helm-test ${plan} + done +} + +function is-pull-request { + if [ ! -z ${TRAVIS} ] && \ + [ ${TRAVIS_PULL_REQUEST} != false ]; then + log-warn "This is a pull request." + return 0 + else + return 1 + fi +} + +function is-pod-running { + local name="${1}" + + if kubectl get pods "${name}" &> /dev/null; then + log-info "Looking for pod named ${name}" + local jq_name_query=".status.phase" + kubectl get pods ${name} -o json | jq -r "${jq_name_query}" | grep -q "Running" && return 0 + fi + + log-info "Couldn't find pod named ${name}, looking for label: app=${name}" + local jq_app_label_query=".items[] | select(.metadata.labels.app == \"${name}\") | .status.phase" + kubectl get pods -o json | jq -r "${jq_app_label_query}" | grep -q "Running" && return 0 + + log-info "Couldn't find label: app=${name}, looking for label: provider=${name}" + local jq_provider_label_query=".items[] | select(.metadata.labels.provider == \"${name}\") | .status.phase" + kubectl get pods -o json | jq -r "${jq_provider_label_query}" | grep -q "Running" && return 0 +} + +function healthcheck { + WAIT_TIME=1 + log-lifecycle "Checking: ${1}" + until is-pod-running "${1}"; do + sleep 1 + (( WAIT_TIME += 1 )) + if [ ${WAIT_TIME} -gt ${HEALTHCHECK_TIMEOUT_SEC} ]; then + return 1 + fi + done + log-lifecycle "Checked!: ${1}" +} + +function log-lifecycle { + echo "${bldblu}==> ${@}...${txtrst}" +} + +function log-info { + echo "${wht}--> ${@}${txtrst}" +} + +function log-warn { + echo "${bldred}--> ${@}${txtrst}" +} diff --git a/_test/secrets/gcloud-credentials.json.enc b/_test/secrets/gcloud-credentials.json.enc new file mode 100644 index 0000000000..0954b0ab08 Binary files /dev/null and b/_test/secrets/gcloud-credentials.json.enc differ diff --git a/_test/test-charts b/_test/test-charts new file mode 100755 index 0000000000..1090a19617 --- /dev/null +++ b/_test/test-charts @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -eo pipefail + +source _test/config.sh + +cd "${TEST_ROOT_DIR}" + +source _test/lib.sh +source _test/gke.sh + +if [ "${TRAVIS}" == true ]; then + if [ "${TRAVIS_PULL_REQUEST}" != false ]; then + log-lifecycle "This is a pull request build. Skipping chart tests." + exit 0 + fi + + # decrypt gcloud credentials if on Travis and building the master branch + openssl aes-256-cbc -K $encrypted_e967971bb2cd_key \ + -iv $encrypted_e967971bb2cd_iv \ + -in _test/secrets/gcloud-credentials.json.enc \ + -out _test/secrets/gcloud-credentials.json -d +fi + +trap gke-destroy EXIT ERR + +install-helm + +setup-gke + +run-test-plan