Skip to content

Commit

Permalink
Merge pull request #66 from shizunge/readme
Browse files Browse the repository at this point in the history
Improve messages, Add tests for login registries, Add alpine patch number
  • Loading branch information
shizunge authored Nov 6, 2024
2 parents b4f8501 + f6aff72 commit d65d780
Show file tree
Hide file tree
Showing 20 changed files with 859 additions and 246 deletions.
1 change: 1 addition & 0 deletions .github/workflows/on-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
- gantry_cleanup_images_spec.sh
- gantry_common_options_spec.sh
- gantry_filters_spec.sh
- gantry_login_negative_spec.sh
- gantry_login_spec.sh
- gantry_manifest_spec.sh
- gantry_notify_spec.sh
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/on-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
- gantry_cleanup_images_spec.sh
- gantry_common_options_spec.sh
- gantry_filters_spec.sh
- gantry_login_negative_spec.sh
- gantry_login_spec.sh
- gantry_manifest_spec.sh
- gantry_notify_spec.sh
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.20.3

LABEL org.opencontainers.image.title=gantry
LABEL org.opencontainers.image.description="Updating docker swarm services"
Expand Down
27 changes: 22 additions & 5 deletions src/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

load_libraries() {
local LOCAL_LOG_LEVEL="${GANTRY_LOG_LEVEL:-""}"
_get_lib_dir() {
local LIB_DIR=
if [ -n "${GANTRY_LIB_DIR:-""}" ]; then
LIB_DIR="${GANTRY_LIB_DIR}"
Expand All @@ -29,10 +28,28 @@ load_libraries() {
elif [ -r "./lib-gantry.sh" ]; then
LIB_DIR="."
fi
echo "${LIB_DIR}"
}

_log_load_libraries() {
local LOG_LEVEL="${GANTRY_LOG_LEVEL:-""}"
local IMAGES_TO_REMOVE="${GANTRY_IMAGES_TO_REMOVE:-""}"
local LIB_DIR="${1}"
# log function is not available before loading the library.
if ! echo "${LOCAL_LOG_LEVEL}" | grep -q -i "NONE"; then
echo "Loading libraries from ${LIB_DIR}"
if echo "${LOG_LEVEL}" | grep -q -i "NONE"; then
return 0
fi
local TIMESTAMP=
if [ -z "${IMAGES_TO_REMOVE}" ]; then
TIMESTAMP="[$(date -Iseconds)] "
fi
echo "${TIMESTAMP}Loading libraries from ${LIB_DIR}" >&2
}

load_libraries() {
local LIB_DIR=
LIB_DIR=$(_get_lib_dir)
_log_load_libraries "${LIB_DIR}"
. "${LIB_DIR}/notification.sh"
. "${LIB_DIR}/docker_hub_rate.sh"
. "${LIB_DIR}/lib-common.sh"
Expand Down Expand Up @@ -155,7 +172,7 @@ main() {
if [ -n "${IMAGES_TO_REMOVE}" ]; then
# Image remover runs as a global job. The log will be collected via docker commands then formatted.
# Redefine the log function for the formater.
log() { echo "${@}"; }
log() { echo "${@}" >&2; }
gantry_remove_images "${IMAGES_TO_REMOVE}"
return $?
fi
Expand Down
59 changes: 53 additions & 6 deletions src/lib-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,15 @@ read_config() {
read_env() {
local VNAME="${1}"; shift
[ -z "${VNAME}" ] && return 1
if env | grep -q "${VNAME}="; then
eval "echo \"\${${VNAME}}\""
else
# "grep -q" will exit immediately when the first line of data matches, and leading to broken pipe errors.
# Add "cat > /dev/null" to avoid broken pipe errors.
local GREP_RETURN=
env | (grep -q "^${VNAME}="; local R=$?; cat >/dev/null; test "${R}" == "0";);
GREP_RETURN=$?
if [ "${GREP_RETURN}" != "0" ]; then
echo "${@}"
else
eval "echo \"\${${VNAME}}\""
fi
return 0
}
Expand Down Expand Up @@ -418,6 +423,46 @@ docker_version() {
echo "Docker version client ${cver} (API ${capi}) server ${sver} (API ${sapi})"
}

# echo the name of the current container.
# echo nothing if unable to find the name.
# return 1 when there is an error.
docker_current_container_name() {
local ALL_NETWORKS=
ALL_NETWORKS=$(docker network ls --format '{{.ID}}') || return 1;
[ -z "${ALL_NETWORKS}" ] && return 0;
local IPS=;
IPS=$(ip route | grep src | sed -n "s/.* src \(\S*\).*$/\1/p");
[ -z "${IPS}" ] && return 0;
local GWBRIDGE_NETWORK HOST_NETWORK;
GWBRIDGE_NETWORK=$(docker network ls --format '{{.ID}}' --filter 'name=^docker_gwbridge$') || return 1;
HOST_NETWORK=$(docker network ls --format '{{.ID}}' --filter 'name=^host$') || return 1;
local NID=;
for NID in ${ALL_NETWORKS}; do
# The output of gwbridge does not contain the container name. It looks like gateway_8f55496ce4f1/172.18.0.5/16.
[ "${NID}" = "${GWBRIDGE_NETWORK}" ] && continue;
# The output of host does not contain an IP.
[ "${NID}" = "${HOST_NETWORK}" ] && continue;
local ALL_LOCAL_NAME_AND_IP=;
ALL_LOCAL_NAME_AND_IP=$(docker network inspect "${NID}" --format "{{range .Containers}}{{.Name}}/{{println .IPv4Address}}{{end}}") || return 1;
for NAME_AND_IP in ${ALL_LOCAL_NAME_AND_IP}; do
[ -z "${NAME_AND_IP}" ] && continue;
# NAME_AND_IP will be in one of the following formats:
# '<container name>/<ip>/<mask>'
# '<container name>/' (when network mode is host)
local CNAME CIP
CNAME=$(echo "${NAME_AND_IP}/" | cut -d/ -f1);
CIP=$(echo "${NAME_AND_IP}/" | cut -d/ -f2);
# Unable to find the container IP when network mode is host.
[ -z "${CIP}" ] && continue;
for IP in ${IPS}; do
[ "${IP}" != "${CIP}" ] && continue;
echo "${CNAME}";
return 0;
done
done
done
}

docker_service_remove() {
local SERVICE_NAME="${1}"
if ! docker service inspect --format '{{.JobStatus}}' "${SERVICE_NAME}" >/dev/null 2>&1; then
Expand Down Expand Up @@ -493,13 +538,15 @@ docker_run() {
local RETRIES=0
local MAX_RETRIES=5
local SLEEP_SECONDS=10
while ! docker run "${@}" >/dev/null; do
local MSG=
while ! MSG=$(docker run "${@}" 2>&1); do
if [ ${RETRIES} -ge ${MAX_RETRIES} ]; then
echo "Failed to run docker. Reached the max retries ${MAX_RETRIES}." >&2
log ERROR "Failed to run docker. Reached the max retries ${MAX_RETRIES}. ${MSG}"
return 1
fi
RETRIES=$((RETRIES + 1))
sleep ${SLEEP_SECONDS}
echo "Retry docker run (${RETRIES})."
log WARN "Retry docker run (${RETRIES}). ${MSG}"
done
echo "${MSG}"
}
93 changes: 44 additions & 49 deletions src/lib-gantry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ _read_env_or_label() {
echo "${VALUE}"
}


_login_registry() {
local USER="${1}"
local PASSWORD="${2}"
Expand All @@ -85,24 +84,30 @@ _login_registry() {
fi
[ -z "${USER}" ] && log ERROR "USER is empty." && return 1
[ -z "${PASSWORD}" ] && log ERROR "PASSWORD is empty." && return 1
local DOCKER_CONFIG=
local CONFIG_MESSAGE=" ${HOST}"
local REGISTRY_MESSAGE="registry ${HOST}"
if [ -z "${HOST}" ]; then
log WARN "HOST is empty. Will login to the default registry."
CONFIG_MESSAGE=""
REGISTRY_MESSAGE="default registry"
fi
local DOCKER_CONFIG=
local CONFIG_TO_REPORT=
CONFIG_TO_REPORT=$(readlink -f ~/.docker)
local CONFIG_MESSAGE="with default configuration"
if [ -n "${CONFIG}" ]; then
DOCKER_CONFIG="--config ${CONFIG}"
CONFIG_MESSAGE="${CONFIG_MESSAGE} for config ${CONFIG}"
CONFIG_TO_REPORT="${CONFIG}"
CONFIG_MESSAGE="with configuration ${CONFIG}"
fi
local REGISTRY_CONFIG_MESSAGE="${REGISTRY_MESSAGE} ${CONFIG_MESSAGE}"
local LOGIN_MSG=
# SC2086: Double quote to prevent globbing and word splitting.
# shellcheck disable=SC2086
if ! LOGIN_MSG=$(echo "${PASSWORD}" | docker ${DOCKER_CONFIG} login --username="${USER}" --password-stdin "${HOST}" 2>&1); then
log ERROR "Failed to login to registry${CONFIG_MESSAGE}. ${LOGIN_MSG}"
log ERROR "Failed to login to ${REGISTRY_CONFIG_MESSAGE}. ${LOGIN_MSG}"
return 1
fi
log INFO "Logged into registry${CONFIG_MESSAGE}. ${LOGIN_MSG}"
log INFO "Logged into ${REGISTRY_CONFIG_MESSAGE}. ${LOGIN_MSG}"
_static_variable_add_unique_to_list STATIC_VAR_SERVICES_DOCKER_CONFIGS "${CONFIG_TO_REPORT}"
return 0
}

Expand Down Expand Up @@ -407,15 +412,15 @@ _remove_images() {
docker_service_remove "${SERVICE_NAME}"
}

_report_services_list() {
_report_list() {
local PRE="${1}"; shift
local POST="${1}"; shift
local LIST="${*}"
local NUM=
NUM=$(_get_number_of_elements "${LIST}")
local TITLE=
[ -n "${PRE}" ] && TITLE="${PRE} "
TITLE="${TITLE}${NUM} service(s)"
TITLE="${TITLE}${NUM}"
[ -n "${POST}" ] && TITLE="${TITLE} ${POST}"
echo "${TITLE}:"
local ITEM=
Expand All @@ -424,7 +429,7 @@ _report_services_list() {
done
}

_report_services_from_static_variable() {
_report_from_static_variable() {
local VARIABLE_NAME="${1}"
local PRE="${2}"
local POST="${3}"
Expand All @@ -435,7 +440,20 @@ _report_services_from_static_variable() {
echo "${EMPTY}"
return 0
fi
_report_services_list "${PRE}" "${POST}" "${LIST}"
_report_list "${PRE}" "${POST}" "${LIST}"
}

_report_services_from_static_variable() {
local VARIABLE_NAME="${1}"
local PRE="${2}"
local POST="${3}"
local EMPTY="${4}"
if [ -z "${POST}" ]; then
POST="service(s)"
else
POST="service(s) ${POST}"
fi
_report_from_static_variable "${VARIABLE_NAME}" "${PRE}" "${POST}" "${EMPTY}"
}

_get_number_of_elements() {
Expand Down Expand Up @@ -529,43 +547,15 @@ _current_container_name() {
local NO_CURRENT_CONTAINER_NAME=
NO_CURRENT_CONTAINER_NAME=$(_static_variable_read_list STATIC_VAR_NO_CURRENT_CONTAINER_NAME)
[ -n "${NO_CURRENT_CONTAINER_NAME}" ] && return 0
local ALL_NETWORKS=
ALL_NETWORKS=$(docker network ls --format '{{.ID}}') || return 1;
[ -z "${ALL_NETWORKS}" ] && return 0;
local IPS=;
IPS=$(ip route | grep src | sed -n "s/.* src \(\S*\).*$/\1/p");
[ -z "${IPS}" ] && return 0;
local GWBRIDGE_NETWORK HOST_NETWORK;
GWBRIDGE_NETWORK=$(docker network ls --format '{{.ID}}' --filter 'name=^docker_gwbridge$') || return 1;
HOST_NETWORK=$(docker network ls --format '{{.ID}}' --filter 'name=^host$') || return 1;
local NID=;
for NID in ${ALL_NETWORKS}; do
# The output of gwbridge does not contain the container name. It looks like gateway_8f55496ce4f1/172.18.0.5/16.
[ "${NID}" = "${GWBRIDGE_NETWORK}" ] && continue;
# The output of host does not contain an IP.
[ "${NID}" = "${HOST_NETWORK}" ] && continue;
local ALL_LOCAL_NAME_AND_IP=;
ALL_LOCAL_NAME_AND_IP=$(docker network inspect "${NID}" --format "{{range .Containers}}{{.Name}}/{{println .IPv4Address}}{{end}}") || return 1;
for NAME_AND_IP in ${ALL_LOCAL_NAME_AND_IP}; do
[ -z "${NAME_AND_IP}" ] && continue;
# NAME_AND_IP will be in one of the following formats:
# '<container name>/<ip>/<mask>'
# '<container name>/' (when network mode is host)
local CNAME CIP
CNAME=$(echo "${NAME_AND_IP}/" | cut -d/ -f1);
CIP=$(echo "${NAME_AND_IP}/" | cut -d/ -f2);
# Unable to find the container IP when network mode is host.
[ -z "${CIP}" ] && continue;
for IP in ${IPS}; do
[ "${IP}" != "${CIP}" ] && continue;
_static_variable_add_unique_to_list STATIC_VAR_CURRENT_CONTAINER_NAME "${CNAME}"
echo "${CNAME}";
return 0;
done
done
done
# Explicitly set that we cannot find the name of current container.
_static_variable_add_unique_to_list STATIC_VAR_NO_CURRENT_CONTAINER_NAME "NO_CURRENT_CONTAINER_NAME"
local CNAME=
CNAME=$(docker_current_container_name) || return 1;
if [ -n "${CNAME}" ]; then
_static_variable_add_unique_to_list STATIC_VAR_CURRENT_CONTAINER_NAME "${CNAME}"
else
# Explicitly set that we cannot find the name of current container.
_static_variable_add_unique_to_list STATIC_VAR_NO_CURRENT_CONTAINER_NAME "NO_CURRENT_CONTAINER_NAME"
fi
echo "${CNAME}"
return 0;
}

Expand Down Expand Up @@ -659,6 +649,11 @@ _get_config_from_service() {
local AUTH_CONFIG=
AUTH_CONFIG=$(_get_label_from_service "${SERVICE_NAME}" "${AUTH_CONFIG_LABEL}")
[ -z "${AUTH_CONFIG}" ] && return 0
if [ ! -d "${AUTH_CONFIG}" ]; then
log WARN "${AUTH_CONFIG} is not a directory that contains Docker configuration files."
local MSG="configuration(s) set via GANTRY_REGISTRY_CONFIG* or GANTRY_REGISTRY_CONFIGS_FILE"
_report_from_static_variable STATIC_VAR_SERVICES_DOCKER_CONFIGS "There are" "${MSG}" "There are no ${MSG}." | log_lines WARN
fi
echo "--config ${AUTH_CONFIG}"
}

Expand Down Expand Up @@ -708,7 +703,7 @@ _get_image_info() {
return 1
fi
if [ "${RETURN_VALUE}" != "0" ]; then
log ERROR "Image ${IMAGE} does not exist or it is not available. ${MSG}"
log ERROR "Image ${IMAGE} does not exist or it is not available. Docker ${MANIFEST_CMD} returns: ${MSG}"
return 1
fi
echo "${MSG}"
Expand Down
17 changes: 13 additions & 4 deletions tests/gantry_cleanup_images_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ Describe 'cleanup-images'
When run test_CLEANUP_IMAGES_false "${TEST_NAME}" "${SERVICE_NAME}"
The status should be success
The stdout should satisfy display_output
The stdout should satisfy spec_expect_no_message ".+"
The stderr should satisfy display_output
The stderr should satisfy spec_expect_no_message "${NOT_START_WITH_A_SQUARE_BRACKET}"
The stderr should satisfy spec_expect_no_message "${SKIP_UPDATING}.*${SERVICE_NAME}"
The stderr should satisfy spec_expect_message "${PERFORM_UPDATING}.*${SERVICE_NAME}.*${PERFORM_REASON_HAS_NEWER_IMAGE}"
The stderr should satisfy spec_expect_no_message "${NUM_SERVICES_SKIP_JOBS}"
Expand Down Expand Up @@ -79,7 +81,9 @@ Describe 'cleanup-images'
When run test_CLEANUP_IMAGES_OPTIONS_bad "${TEST_NAME}" "${SERVICE_NAME}"
The status should be success
The stdout should satisfy display_output
The stdout should satisfy spec_expect_no_message ".+"
The stderr should satisfy display_output
The stderr should satisfy spec_expect_no_message "${NOT_START_WITH_A_SQUARE_BRACKET}"
The stderr should satisfy spec_expect_no_message "${SKIP_UPDATING}.*${SERVICE_NAME}"
The stderr should satisfy spec_expect_message "${PERFORM_UPDATING}.*${SERVICE_NAME}.*${PERFORM_REASON_HAS_NEWER_IMAGE}"
The stderr should satisfy spec_expect_no_message "${NUM_SERVICES_SKIP_JOBS}"
Expand Down Expand Up @@ -122,7 +126,9 @@ Describe 'cleanup-images'
When run test_CLEANUP_IMAGES_OPTIONS_good "${TEST_NAME}" "${SERVICE_NAME}"
The status should be success
The stdout should satisfy display_output
The stdout should satisfy spec_expect_no_message ".+"
The stderr should satisfy display_output
The stderr should satisfy spec_expect_no_message "${NOT_START_WITH_A_SQUARE_BRACKET}"
The stderr should satisfy spec_expect_no_message "${SKIP_UPDATING}.*${SERVICE_NAME}"
The stderr should satisfy spec_expect_message "${PERFORM_UPDATING}.*${SERVICE_NAME}.*${PERFORM_REASON_HAS_NEWER_IMAGE}"
The stderr should satisfy spec_expect_no_message "${NUM_SERVICES_SKIP_JOBS}"
Expand Down Expand Up @@ -209,11 +215,14 @@ Describe 'cleanup-images'
When run test_IMAGES_TO_REMOVE_none_empty "${TEST_NAME}" "${SERVICE_NAME}" "${IMAGE_WITH_TAG}"
The status should be success
The stdout should satisfy display_output
The stdout should satisfy spec_expect_message "Removed exited container.*${SERVICE_NAME0}.*${IMAGE_WITH_TAG0}"
The stdout should satisfy spec_expect_message "${REMOVED_IMAGE}.*${IMAGE_WITH_TAG0}"
The stdout should satisfy spec_expect_message "${FAILED_TO_REMOVE_IMAGE}.*${IMAGE_WITH_TAG1}"
The stdout should satisfy spec_expect_message "There is no image.*${IMAGE_WITH_TAG2}"
The stdout should satisfy spec_expect_no_message ".+"
The stderr should satisfy display_output
# It should not use the log function from the lib-common, the messages do not start with "[".
The stderr should satisfy spec_expect_no_message "^((?:\x1b\[[0-9;]*[mG])?\[)"
The stderr should satisfy spec_expect_message "Removed exited container.*${SERVICE_NAME0}.*${IMAGE_WITH_TAG0}"
The stderr should satisfy spec_expect_message "${REMOVED_IMAGE}.*${IMAGE_WITH_TAG0}"
The stderr should satisfy spec_expect_message "${FAILED_TO_REMOVE_IMAGE}.*${IMAGE_WITH_TAG1}"
The stderr should satisfy spec_expect_message "There is no image.*${IMAGE_WITH_TAG2}"
End
End
End # Describe 'Single service'
Loading

0 comments on commit d65d780

Please sign in to comment.