#!/usr/bin/env bash # Copyright 2018 The Tekton Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Helper functions for E2E tests. # Check if we have a specific RELEASE_YAML global environment variable to use # instead of detecting the latest released one from tektoncd/pipeline releases RELEASE_YAML=${RELEASE_YAML:-} # Define a custom kubectl path if you like KUBECTL_CMD=${KUBECTL_CMD:-kubectl} # Dependency checks ## Bash must be 4 or greater to support associative arrays if [ "${BASH_VERSINFO:-0}" -lt 4 ];then echo "this script must be executed in bash >= 4" exit 1 fi # Default registry image value to use in this script REGISTRY_IMAGE="registry" # Do the resources modifications if special PLATFORM value is specified. By default nothing happens. if [[ -n ${PLATFORM} ]] && [[ -f "$(dirname $0)/$(echo ${PLATFORM}| tr / -).sh" ]]; then # Load script specific to platform. File name should follow the pattern "os-arch.sh", for instance "linux-s390x.sh". source $(dirname $0)/$(echo ${PLATFORM}| tr / -).sh fi ## Commands function require_command() { if ! command -v ${1} &> /dev/null;then echo "required command '${1}' not be found" exit 1 fi } require_command ${KUBECTL_CMD} python3 ## Python Modules function require_python_module() { for arg in "$@"; do if ! python3 -c "import ${arg}" &> /dev/null;then echo "required 'python3' module '${arg}' not be found" echo "NOTE: the library name may be different but you you can try installing it via 'python3 -m pip install ${arg}'" exit 1 fi done } require_python_module yaml source $(dirname $0)/../vendor/github.com/tektoncd/plumbing/scripts/e2e-tests.sh # Add sidecar resources, first argument is the file # following arguments are string formed json/yaml processed by yaml.safe_load # e.g. '{"image": "registry", "name": "registry"}' function add_sidecars() { cp ${1} ${TMPF}.read SCRIPT=' import sys, yaml f=open(0, encoding="utf-8") data=yaml.load(f.read(), Loader=yaml.FullLoader) sidecars = map(lambda v: yaml.safe_load(v), sys.argv[1:]) # after -c for sidecar in sidecars: if "sidecars" in data["spec"]: data["spec"]["sidecars"].append(sidecar) else: data["spec"]["sidecars"] = [sidecar] print(yaml.dump(data, default_flow_style=False)) ' cat ${TMPF}.read | python3 -c "$SCRIPT" "${@:2}" > ${TMPF} rm -f ${TMPF}.read } # Add an internal registry as sidecar to a task so we can upload it directly # from our tests withouth having to go to an external registry. function add_sidecar_registry() { add_sidecars ${1} "{'image':${REGISTRY_IMAGE}, 'name': 'registry'}" } # Run a secure registry as a sidecar to allow the tasks to push to this registry using the certs. # It will create a configmap `sslcert` with certificate available at key `ca.crt` function add_sidecar_secure_registry() { TMD=$(mktemp -d) # Generate SSL Certificate openssl req -newkey rsa:4096 -nodes -sha256 -keyout "${TMD}"/ca.key -x509 -days 365 \ -addext "subjectAltName = DNS:registry" \ -out "${TMD}"/ca.crt -subj "/C=FR/ST=IDF/L=Paris/O=Tekton/OU=Catalog/CN=registry" # Create a configmap from these certs kubectl create -n "${tns}" configmap sslcert \ --from-file=ca.crt="${TMD}"/ca.crt --from-file=ca.key="${TMD}"/ca.key # Add a secure internal registry as sidecar kubectl create -n "${tns}" -f ${resourcedir}/tests/internal-registry/internal-registry.yaml } function add_task() { local array path_version task task=${1} if [[ "${2}" == "latest" ]];then array=($(echo task/${task}/*/|sort -u)) path_version=${array[-1]} else path_version=task/${task}/${2} if [[ ! -d ${path_version} ]];then echo "I could not find version '${2}' for the task '${task}' in ./task/" exit 1 fi fi ${KUBECTL_CMD} -n "${tns}" apply -f "${path_version}"/"${task}".yaml } function add_stepaction() { local array path_version stepaction stepaction=${1} if [[ "${2}" == "latest" ]];then array=($(echo stepaction/${stepaction}/*/|sort -u)) path_version=${array[-1]} else path_version=stepaction/${stepaction}/${2} if [[ ! -d ${path_version} ]];then echo "I could not find version '${2}' for the stepaction '${stepaction}' in ./stepaction/" exit 1 fi fi ${KUBECTL_CMD} -n "${tns}" apply -f "${path_version}"/"${stepaction}".yaml } function install_pipeline_crd() { local latestreleaseyaml echo ">> Deploying Tekton Pipelines" if [[ -n ${RELEASE_YAML} ]];then latestreleaseyaml=${RELEASE_YAML} else latestreleaseyaml="https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml" fi [[ -z ${latestreleaseyaml} ]] && fail_test "Could not get latest released release.yaml" echo "Installing ${latestreleaseyaml}" ${KUBECTL_CMD} apply -f ${latestreleaseyaml} || fail_test "Build pipeline installation failed" echo "Enabling StepActions on the cluster" ${KUBECTL_CMD} patch cm feature-flags -n tekton-pipelines -p '{"data":{"enable-step-actions":"true"}}' # Make sure that eveything is cleaned up in the current namespace. for res in stepactions tasks pipelines taskruns pipelineruns; do ${KUBECTL_CMD} delete --ignore-not-found=true ${res}.tekton.dev --all done # Wait for pods to be running in the namespaces we are deploying to wait_until_pods_running tekton-pipelines || fail_test "Tekton Pipeline did not come up" } function test_yaml_can_install() { # Validate that all the StepAction/Task CRDs in this repo are valid by creating them in a NS. ns="task-stepaction-ns" all_tasks="$*" ${KUBECTL_CMD} create ns "${ns}" || true local runtest for runtest in ${all_tasks}; do # remove task/ or stepaction/ from beginning local runtestdir=${runtest#*/} # remove /0.1/tests from end local testname=${runtestdir%%/*} runtest=${runtest//tests} # in case a stepaction/task is being removed then it's directory # doesn't exists, so skip the test for YAML [ ! -d "${runtest%%/*}/${testname}" ] && continue runtest="${runtest}${testname}.yaml" skipit= for ignore in ${TEST_YAML_IGNORES};do [[ ${ignore} == "${testname}" ]] && skipit=True done # don't test the stepactions/tasks which are deprecated cat ${runtest} | grep 'tekton.dev/deprecated: \"true\"' && skipit=True # In case if PLATFORM env variable is specified, do the tests only for matching stepactions/tasks [[ -n ${PLATFORM} ]] && [[ $(grep "tekton.dev/platforms" ${runtest} 2>/dev/null) != *"${PLATFORM}"* ]] && skipit=True [[ -n ${skipit} ]] && continue echo "Checking ${testname}" ${KUBECTL_CMD} -n ${ns} apply -f <(sed "s/namespace:.*/namespace: task-stepaction-ns/" "${runtest}") done } function show_failure() { local testname=$1 tns=$2 echo "FAILED: ${testname} task has failed to comeback properly" ; ${KUBECTL_CMD} api-resources echo "Namespace: ${tns}" echo "--- TaskRun Dump" ${KUBECTL_CMD} get --ignore-not-found=true -n ${tns} taskrun -o yaml echo "--- Task Dump" ${KUBECTL_CMD} get --ignore-not-found=true -n ${tns} task -o yaml echo "--- PipelineRun Dump" ${KUBECTL_CMD} get --ignore-not-found=true -n ${tns} pipelinerun -o yaml echo "--- Pipeline Dump" ${KUBECTL_CMD} get --ignore-not-found=true -n ${tns} pipeline -o yaml echo "--- StepAction Dump" ${KUBECTL_CMD} get --ignore-not-found=true -n ${tns} stepaction -o yaml echo "--- Container Logs" for pod in $(${KUBECTL_CMD} get pod -o name -n ${tns}); do echo "----POD_NAME: ${pod}---" ${KUBECTL_CMD} logs --all-containers -n ${tns} ${pod} || true done exit 1 } function test_resource_creation() { local runtest declare -A resource_to_wait_for for runtest in $@;do # remove stepaction/ or task/ from beginning local runtestdir=${runtest#*/} # remove /0.1/tests from end local testname=${runtestdir%%/*} # get version of the task or stepaction local version=$(basename $(basename $(dirname $runtest))) # check version is in given format [[ ${version} =~ ^[0-9]+\.[0-9]+$ ]] || { echo "ERROR: version of the resource is not set properly"; exit 1;} # replace . with - in version as not supported in namespace name version="$( echo $version | tr '.' '-' )" local tns="${testname}-${version}" local skipit= for ignore in ${TEST_TASKRUN_IGNORES};do [[ ${ignore} == ${testname} ]] && skipit=True done # remove /tests from end local resourcedir=${runtest%/*} # check whether test folder exists or not inside stepaction/task dir # if not then run the tests for next stepaction/task (if any) [ ! -d $runtest ] && skipit=True ls ${resourcedir}/*.yaml 2>/dev/null >/dev/null || skipit=True cat ${resourcedir}/*.yaml | grep 'tekton.dev/deprecated: \"true\"' && skipit=True # In case if PLATFORM env variable is specified, do the tests only for matching stepactions/tasks [[ -n ${PLATFORM} ]] && [[ $(grep "tekton.dev/platforms" ${resourcedir}/*.yaml 2>/dev/null) != *"${PLATFORM}"* ]] && skipit=True [[ -n ${skipit} ]] && continue # In case of rerun it's fine to ignore this error ${KUBECTL_CMD} create namespace ${tns} >/dev/null 2>/dev/null || : # Install the stepaction/task itself first. We can only have one YAML file yaml=$(printf ${resourcedir}/*.yaml) started=$(date '+%Hh%M:%S') echo "${started} STARTING: ${testname}/${version} " # dry-run this YAML to validate and also get formatting side-effects. ${KUBECTL_CMD} -n ${tns} create -f ${yaml} --dry-run=client -o yaml >${TMPF} [[ -f ${resourcedir}/tests/pre-apply-task-hook.sh ]] && source ${resourcedir}/tests/pre-apply-task-hook.sh function_exists pre-apply-task-hook && pre-apply-task-hook [[ -d ${resourcedir}/tests/fixtures ]] && { # Create a configmap to make every file under fixture # available to the sidecar. ${KUBECTL_CMD} -n ${tns} create configmap fixtures --from-file=${resourcedir}/tests/fixtures # The task may already have a volumes section and in that case, we # need to append fixtures volume. if [[ -n $(grep "^[[:space:]]\{2,\}volumes:$" ${TMPF}) ]]; then sed -i "s/^[[:space:]]\{2,\}volumes:$/ volumes:\\n - name: fixtures\\n configMap:\\n name: fixtures/g" ${TMPF} else cat <>${TMPF} volumes: - name: fixtures configMap: name: fixtures EOF fi cat <>${TMPF} sidecars: - image: gcr.io/tekton-releases/dogfooding/go-rest-api-test:latest name: go-rest-api volumeMounts: - name: fixtures mountPath: /fixtures env: - name: CONFIG value: | $(cat ${resourcedir}/tests/fixtures/*.yaml|sed 's/^/ /') EOF } # Make sure we have deleted the content, this is in case of rerun # and namespace hasn't been cleaned up or there is some Cluster* # stuff, which really should not be allowed. ${KUBECTL_CMD} -n ${tns} delete -f ${TMPF} >/dev/null 2>/dev/null || true ${KUBECTL_CMD} -n ${tns} create -f ${TMPF} # Install resource and run for yaml in ${runtest}/*.yaml;do cp ${yaml} ${TMPF} [[ -f ${resourcedir}/tests/pre-apply-taskrun-hook.sh ]] && source ${resourcedir}/tests/pre-apply-taskrun-hook.sh function_exists pre-apply-taskrun-hook && pre-apply-taskrun-hook # Make sure we have deleted the content, this is in case of rerun # and namespace hasn't been cleaned up or there is some Cluster* # stuff, which really should not be allowed. ${KUBECTL_CMD} -n ${tns} delete -f ${TMPF} >/dev/null 2>/dev/null || true ${KUBECTL_CMD} -n ${tns} create -f ${TMPF} done resource_to_wait_for["$testname/${version}"]="${tns}|$started" done # I would refactor this to a function but bash limitation is too great, really need a rewrite the sooner # the uglness to pass a hashmap to a function https://stackoverflow.com/a/17557904/145125 local cnt=0 local all_status='' local reason='' local maxloop=60 # 10 minutes max set +x while true;do # If we have timed out then show failures of what's remaining in # resource_to_wait_for we assume only first one fails this [[ ${cnt} == "${maxloop}" ]] && { for testname in "${!resource_to_wait_for[@]}";do target_ns=${resource_to_wait_for[$testname]} show_failure "${testname}" "${target_ns}" done } [[ -z ${resource_to_wait_for[*]} ]] && { break } for testname in "${!resource_to_wait_for[@]}";do target_ns=${resource_to_wait_for[$testname]%|*} started=${resource_to_wait_for[$testname]#*|} # sometimes we don't get all_status and reason in one go so # wait until we get the reason and all_status for 5 iterations for tektontype in pipelinerun taskrun;do for _ in {1..10}; do all_status=$(${KUBECTL_CMD} get -n ${target_ns} ${tektontype} --output=jsonpath='{.items[*].status.conditions[*].status}') reason=$(${KUBECTL_CMD} get -n ${target_ns} ${tektontype} --output=jsonpath='{.items[*].status.conditions[*].reason}') [[ ! -z ${all_status} ]] && [[ ! -z ${reason} ]] && break sleep 1 done # No need to check taskrun if pipelinerun has been set [[ ! -z ${all_status} ]] && [[ ! -z ${reason} ]] && break done if [[ -z ${all_status} || -z ${reason} ]];then echo "Could not find a created taskrun or pipelinerun in ${target_ns}" fi breakit=True for status in ${all_status};do [[ ${status} == *ERROR || ${reason} == *Fail* || ${reason} == Couldnt* ]] && show_failure ${testname} ${target_ns} if [[ ${status} != True ]];then breakit= fi done if [[ ${breakit} == True ]];then unset resource_to_wait_for[$testname] [[ -z ${CATALOG_TEST_SKIP_CLEANUP} ]] && ${KUBECTL_CMD} delete ns ${target_ns} >/dev/null echo "${started}::$(date '+%Hh%M:%S') SUCCESS: ${testname} testrun has successfully executed" ; fi done sleep 10 cnt=$((cnt+1)) done set -x }