| #!/usr/bin/env bash |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You 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. |
| |
| DOCKER_ID=${RANDOM} |
| DOCKER_DESTRUCTIVE=true |
| |
| ## @description Verify docker exists |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @returns 1 if docker not found |
| ## @returns 0 if docker is found |
| function dockerverify |
| { |
| declare pathdocker |
| |
| if [[ -z "${DOCKERCMD}" ]]; then |
| pathdocker=$(which docker 2>/dev/null) |
| |
| if [[ ! -f "${pathdocker}" ]]; then |
| yetus_error "Docker cannot be found." |
| return 1 |
| fi |
| DOCKERCMD="${pathdocker}" |
| fi |
| |
| if [[ ! -x "${DOCKERCMD}" ]];then |
| yetus_error "Docker command ${DOCKERCMD} is not executable." |
| return 1 |
| fi |
| return 0 |
| } |
| |
| ## @description Run docker with some arguments, and |
| ## @description optionally send to debug |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function dockercmd |
| { |
| yetus_debug "${DOCKERCMD} $*" |
| "${DOCKERCMD}" "$@" |
| } |
| |
| ## @description Stop and delete all defunct containers |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_stop_exited_containers |
| { |
| declare line |
| declare id |
| declare value |
| declare size |
| declare exitfn="${PATCH_DIR}/dsec.$$" |
| |
| big_console_header "Removing stopped/exited containers" |
| |
| echo "Docker containers in exit state:" |
| |
| dockercmd ps -a | ${GREP} Exited > "${exitfn}" |
| if [[ ! -s "${exitfn}" ]]; then |
| return |
| fi |
| |
| # stop *all* containers that are in exit state for |
| # more than > 8 hours |
| while read -r line; do |
| id=$(echo "${line}" | cut -f1 -d' ') |
| value=$(echo "${line}" | cut -f2 -d' ') |
| size=$(echo "${line}" | cut -f3 -d' ') |
| |
| if [[ ${size} =~ day |
| || ${size} =~ week |
| || ${size} =~ month |
| || ${size} =~ year ]]; then |
| echo "Removing docker ${id}" |
| if [[ "${DOCKER_DESTRUCTIVE}" = true ]]; then |
| dockercmd rm "${id}" |
| else |
| echo docker rm "${id}" |
| fi |
| fi |
| |
| if [[ ${size} =~ hours |
| && ${value} -gt 8 ]]; then |
| echo "Removing docker ${id}" |
| if [[ "${DOCKER_DESTRUCTIVE}" = true ]]; then |
| dockercmd rm "${id}" |
| else |
| echo docker rm "${id}" |
| fi |
| fi |
| done < <( |
| ${SED} -e 's,ago,,g' "${exitfn}"\ |
| | ${AWK} '{print $1" "$(NF - 2)" "$(NF - 1)}') |
| rm "${exitfn}" |
| } |
| |
| ## @description Remove all containers that are not |
| ## @description are not running + older than 1 day |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_rm_old_containers |
| { |
| declare line |
| declare id |
| declare value |
| declare size |
| declare running |
| |
| big_console_header "Removing old containers" |
| |
| while read -r line; do |
| id=$(echo "${line}" | cut -f1 -d, ) |
| running=$(echo "${line}" | cut -f2 -d, ) |
| stoptime=$(echo "${line}" | cut -f3 -d, | cut -f1 -d. ) |
| |
| if [[ ${running} = true ]]; then |
| yetus_debug "${id} is still running, skipping." |
| continue |
| fi |
| |
| # believe it or not, date is not even close to standardized... |
| if [[ $(uname -s) == Linux ]]; then |
| |
| # GNU date |
| stoptime=$(date -d "${stoptime}" "+%s") |
| else |
| |
| # BSD date |
| stoptime=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${stoptime}" "+%s") |
| fi |
| |
| curtime=$(date "+%s") |
| ((difftime = curtime - stoptime)) |
| if [[ ${difftime} -gt 86400 ]]; then |
| echo "Removing docker ${id}" |
| if [[ "${DOCKER_DESTRUCTIVE}" = true ]]; then |
| dockercmd rm "${id}" |
| else |
| echo docker rm "${id}" |
| fi |
| fi |
| done < <( |
| # see https://github.com/koalaman/shellcheck/issues/375 |
| # shellcheck disable=SC2046 |
| dockercmd inspect \ |
| -f '{{.Id}},{{.State.Running}},{{.State.FinishedAt}}' \ |
| $(dockercmd ps -qa) 2>/dev/null) |
| } |
| |
| ## @description Remove untagged/unu${SED} images |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_remove_untagged_images |
| { |
| |
| big_console_header "Removing untagged images" |
| |
| # this way is a bit more compatible with older docker versions |
| # shellcheck disable=SC2016 |
| dockercmd images | tail -n +2 | ${AWK} '$1 == "<none>" {print $3}' | \ |
| xargs --no-run-if-empty docker rmi |
| } |
| |
| ## @description Remove defunct tagged images |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_remove_old_tagged_images |
| { |
| declare line |
| declare id |
| declare created |
| |
| big_console_header "Removing old tagged images" |
| |
| while read -r line; do |
| # shellcheck disable=SC2016 |
| id=$(echo "${line}" | ${AWK} '{print $1":"$2}') |
| # shellcheck disable=SC2016 |
| created=$(echo "${line}" | ${AWK} '{print $5}') |
| |
| if [[ ${created} =~ week |
| || ${created} =~ month |
| || ${created} =~ year ]]; then |
| echo "Removing docker image ${id}" |
| if [[ "${DOCKER_DESTRUCTIVE}" = true ]]; then |
| dockercmd rmi "${id}" |
| else |
| echo docker rmi "${id}" |
| fi |
| fi |
| |
| if [[ ${id} =~ yetus/${PROJECT_NAME}:date |
| || ${id} =~ test-patch- ]]; then |
| if [[ ${created} =~ day |
| || ${created} =~ hours ]]; then |
| echo "Removing docker image ${id}" |
| if [[ "${DOCKER_DESTRUCTIVE}" = true ]]; then |
| dockercmd rmi "${id}" |
| else |
| echo docker rmi "${id}" |
| fi |
| fi |
| fi |
| done < <(dockercmd images) |
| } |
| |
| ## @description Performance docker maintenance on Jenkins |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_cleanup_apache_jenkins |
| { |
| echo "==========================" |
| echo "Docker Images:" |
| dockercmd images |
| echo "==========================" |
| echo "Docker Containers:" |
| dockercmd ps -a |
| echo "==========================" |
| |
| docker_stop_exited_containers |
| |
| docker_rm_old_containers |
| |
| docker_remove_untagged_images |
| |
| docker_remove_old_tagged_images |
| } |
| |
| ## @description Clean up our old images u${SED} for patch testing |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_cleanup_yetus_images |
| { |
| declare images |
| declare imagecount |
| declare rmimage |
| declare rmi |
| |
| # we always want to leave at least one of our images |
| # so that the whole thing doesn't have to be rebuilt. |
| # This also let's us purge any old images so that |
| # we can get fresh stuff sometimes |
| # shellcheck disable=SC2016 |
| images=$(dockercmd images | ${GREP} "yetus/${PROJECT_NAME}" | ${GREP} tp | ${AWK} '{print $1":"$2}') 2>&1 |
| |
| # shellcheck disable=SC2086 |
| imagecount=$(echo ${images} | tr ' ' '\n' | wc -l) |
| ((imagecount = imagecount - 1 )) |
| |
| # shellcheck disable=SC2086 |
| rmimage=$(echo ${images} | tr ' ' '\n' | tail -${imagecount}) |
| for rmi in ${rmimage} |
| do |
| echo "Removing image ${rmi}" |
| if [[ "${DOCKER_DESTRUCTIVE}" = true ]]; then |
| dockercmd rmi "${rmi}" |
| else |
| echo docker rmi "${rmi}" |
| fi |
| done |
| } |
| |
| ## @description Perform pre-run maintenance to free up |
| ## @description resources. With --jenkins, it is a lot |
| ## @description more destructive. |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_cleanup |
| { |
| if [[ ${TESTPATCHMODE} =~ jenkins ]]; then |
| docker_cleanup_apache_jenkins |
| fi |
| |
| docker_cleanup_yetus_images |
| } |
| |
| ## @description Deterine the user name and user id of the user |
| ## @description that the docker container should use |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_determine_user |
| { |
| # On the Apache Jenkins hosts, $USER is pretty much untrustable beacuse some |
| # ... person ... sets it to an account that doesn't actually exist. |
| # so instead, we need to try and override it with something that's |
| # probably close to reality. |
| if [[ ${TESTPATCHMODE} =~ jenkins ]]; then |
| USER=$(id | cut -f2 -d\( | cut -f1 -d\)) |
| fi |
| |
| if [[ "$(uname -s)" == "Linux" ]]; then |
| USER_NAME=${SUDO_USER:=$USER} |
| USER_ID=$(id -u "${USER_NAME}") |
| GROUP_ID=$(id -g "${USER_NAME}") |
| else # boot2docker uid and gid |
| USER_NAME=${USER} |
| USER_ID=1000 |
| GROUP_ID=50 |
| fi |
| } |
| |
| ## @description Determine the revision of a dockerfile |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_getfilerev |
| { |
| ${GREP} 'YETUS_PRIVATE: gitrev=' \ |
| "${PATCH_DIR}/precommit/test-patch-docker/Dockerfile" \ |
| | cut -f2 -d= |
| } |
| |
| ## @description Start a test patch docker container |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_run_image |
| { |
| declare dockerfilerev |
| declare baseimagename |
| declare patchimagename="yetus/${PROJECT_NAME}:tp-${DOCKER_ID}" |
| declare client |
| declare server |
| |
| dockerfilerev=$(docker_getfilerev) |
| |
| baseimagename="yetus/${PROJECT_NAME}:${dockerfilerev}" |
| |
| # make a base image, if it isn't available |
| big_console_header "Building base image: ${baseimagename}" |
| dockercmd build -t "${baseimagename}" "${PATCH_DIR}/precommit/test-patch-docker" |
| |
| if [[ $? != 0 ]]; then |
| yetus_error "ERROR: Docker failed to build image." |
| add_vote_table -1 docker "Docker failed to build ${baseimagename}." |
| bugsystem_finalreport 1 |
| cleanup_and_exit 1 |
| fi |
| |
| big_console_header "Building patch image: ${patchimagename}" |
| # using the base image, make one that is patch specific |
| dockercmd build -t "${patchimagename}" - <<PatchSpecificDocker |
| FROM ${baseimagename} |
| RUN groupadd --non-unique -g ${GROUP_ID} ${USER_NAME} |
| RUN useradd -g ${GROUP_ID} -u ${USER_ID} -m ${USER_NAME} |
| RUN chown -R ${USER_NAME} /home/${USER_NAME} |
| ENV HOME /home/${USER_NAME} |
| USER ${USER_NAME} |
| PatchSpecificDocker |
| |
| if [[ $? != 0 ]]; then |
| yetus_error "ERROR: Docker failed to build image." |
| add_vote_table -1 docker "Docker failed to build ${patchimagename}." |
| bugsystem_finalreport 1 |
| cleanup_and_exit 1 |
| fi |
| |
| if [[ -f "${PATCH_DIR}/buildtool-docker-params.txt" ]]; then |
| extraargs=$(cat "${PATCH_DIR}/buildtool-docker-params.txt") |
| else |
| extraargs="" |
| fi |
| |
| client=$(dockercmd version | ${GREP} 'Client version' | cut -f2 -d: | tr -d ' ') |
| server=$(dockercmd version | ${GREP} 'Server version' | cut -f2 -d: | tr -d ' ') |
| |
| dockerversion="Client=${client} Server=${server}" |
| |
| if [[ ${PATCH_DIR} =~ ^/ ]]; then |
| exec "${DOCKERCMD}" run --rm=true -i \ |
| ${extraargs} \ |
| -v "${PWD}:/testptch/${PROJECT_NAME}" \ |
| -v "${PATCH_DIR}:/testptch/patchprocess" \ |
| -u "${USER_NAME}" \ |
| -w "/testptch/${PROJECT_NAME}" \ |
| --env=BASEDIR="/testptch/${PROJECT_NAME}" \ |
| --env=DOCKER_VERSION="${dockerversion} Image:${baseimagename}" \ |
| --env=JAVA_HOME="${JAVA_HOME}" \ |
| --env=PATCH_DIR=/testptch/patchprocess \ |
| --env=PATCH_SYSTEM="${PATCH_SYSTEM}" \ |
| --env=PROJECT_NAME="${PROJECT_NAME}" \ |
| --env=TESTPATCHMODE="${TESTPATCHMODE}" \ |
| "${patchimagename}" |
| else |
| exec "${DOCKERCMD}" run --rm=true -i \ |
| ${extraargs} \ |
| -v "${PWD}:/testptch/${PROJECT_NAME}" \ |
| -u "${USER_NAME}" \ |
| -w "/testptch/${PROJECT_NAME}" \ |
| --env=BASEDIR="/testptch/${PROJECT_NAME}" \ |
| --env=DOCKER_VERSION="${DOCKER_VERSION} Image:${baseimagename}" \ |
| --env=JAVA_HOME="${JAVA_HOME}" \ |
| --env=PATCH_DIR="${PATCH_DIR}" \ |
| --env=PATCH_SYSTEM="${PATCH_SYSTEM}" \ |
| --env=PROJECT_NAME="${PROJECT_NAME}" \ |
| --env=TESTPATCHMODE="${TESTPATCHMODE}" \ |
| "${patchimagename}" |
| fi |
| } |
| |
| ## @description Switch over to a Docker container |
| ## @audience private |
| ## @stability evolving |
| ## @replaceable no |
| ## @param args |
| function docker_handler |
| { |
| PATCH_DIR=$(relative_dir "${PATCH_DIR}") |
| |
| docker_cleanup |
| docker_determine_user |
| docker_run_image |
| } |