Current section

Files

Jump to
edeliver libexec erlang
Raw

libexec/erlang

#!/usr/bin/env bash
# the local or remote (scp) directory where the built
# releases should be copied to
if [ -z "$LOCAL_RELEASE_STORE" ]
then
LOCAL_RELEASE_STORE=".deliver"
fi
if [ -z "$RELEASE_STORE" ]
then
RELEASE_STORE="$LOCAL_RELEASE_STORE"
fi
if [ -z "$REBAR_CONFIG" ]
then
REBAR_OPTIONS=""
else
REBAR_OPTIONS="-C $REBAR_CONFIG"
fi
REBAR_CMD=${REBAR_CMD:="./rebar $REBAR_OPTIONS"}
REBAR3_CMD=${REBAR3_CMD:="./rebar3 $REBAR_OPTIONS"}
MIX_CMD=${MIX_CMD:="mix"}
RELX_CMD=${RELX_CMD:="./relx"}
DOCKER_BUILD_IMAGE=${DOCKER_BUILD_IMAGE:-elixir:1.13.3}
# sets the build host, user and path as deliver hosts and path
set_build_hosts() {
# validate that there is only one build host
local _build_hosts=$(echo "$BUILD_HOST" | wc -w)
[[ $_build_hosts -gt 1 ]] && error "There are several build hosts configured:\n\n\t$BUILD_HOST\n\nPlease configure only one build host in \$BUILD_HOST.\n"
# use build host and user for remote operations
export HOSTS=$BUILD_HOST
export APP_USER=$BUILD_USER
# use deliver to as built at destination
export DELIVER_TO=$BUILD_AT
}
# sets the deploy host, user and path as deliver hosts and path
set_deploy_hosts() {
if [ "$DEPLOY_ENVIRONMENT" = "production" ]; then
export HOSTS=$PRODUCTION_HOSTS
export APP_USER=$PRODUCTION_USER
elif [ "$DEPLOY_ENVIRONMENT" = "local" ]; then
export HOSTS="localhost"
export APP_USER="$(id -un)"
export DELIVER_TO="$TEST_LOCALLY_AT"
else
export HOSTS=$STAGING_HOSTS
export APP_USER=$STAGING_USER
export DELIVER_TO=$TEST_AT
fi
}
update_hosts_app_user() {
HOSTS_APP_USER=""
for _host in $HOSTS
do
HOSTS_APP_USER="$HOSTS_APP_USER,$APP_USER@$_host"
done
HOSTS="$(__remote_friendly $HOSTS)"
HOSTS_APP_USER="$(__remote_friendly $HOSTS_APP_USER)"
}
# sets all required config variables and defaults for deploy strategies
require_deploy_config() {
DEPLOY_ENVIRONMENT="${DEPLOY_ENVIRONMENT:=staging}"
TEST_AT="${TEST_AT:=$DELIVER_TO}"
TEST_LOCALLY_AT="${TEST_LOCALLY_AT:="${PWD}/.local-release"}"
REQUIRED_CONFIGS+=("APP")
if [ "$DEPLOY_ENVIRONMENT" = "production" ]; then
REQUIRED_CONFIGS+=("PRODUCTION_HOSTS")
REQUIRED_CONFIGS+=("PRODUCTION_USER")
REQUIRED_CONFIGS+=("DELIVER_TO")
elif [ "$DEPLOY_ENVIRONMENT" = "local" ]; then
REQUIRED_CONFIGS+=("TEST_LOCALLY_AT")
PRODUCTION_HOSTS=""
STAGING_HOSTS=""
eval '__remote() {
__sync_local "$@"
}'
else
REQUIRED_CONFIGS+=("STAGING_HOSTS")
REQUIRED_CONFIGS+=("STAGING_USER")
REQUIRED_CONFIGS+=("TEST_AT")
fi
OPTIONAL_CONFIGS+=("VERSION")
}
# sets all required config variables and defaults for node execute strategies
require_node_config() {
local _node_environment_uppercase=$(echo "$NODE_ENVIRONMENT" | tr '[:lower:]' '[:upper:]')
local _node_config_variable_name="${_node_environment_uppercase}_NODES"
eval NODES=\$$_node_config_variable_name
REQUIRED_CONFIGS+=("$_node_config_variable_name")
# set user@host:/path as default value if nodes config is not set
if [ -z "$NODES" ]; then
if [ "$NODE_ENVIRONMENT" = "production" ]; then
local _user=${PRODUCTION_USER}
local _path=${DELIVER_TO}
else
local _user=${STAGING_USER}
local _path=${TEST_AT:-$DELIVER_TO}
fi
if [[ -n "$_user" ]] && [[ -n "$_path" ]]; then
local _hosts_environment_variable_name="${_node_environment_uppercase}_HOSTS"
for i in $(eval echo \$$_hosts_environment_variable_name); do
eval $_node_config_variable_name=\"\$$_node_config_variable_name ${_user}@${i}:${_path}\"
done
eval NODES=\$$_node_config_variable_name
fi
fi
}
# stops the build container whene deliver exits and a build
# container was created because the BUILD_HOST is set to "docker".
# if it exits normally the container is also removed, otherwise
# it is kept to be able to debug the build failure.
stop_build_container() {
local status="$?"
if [ "$status" = "0" ]; then
status "Removing build container"
_=$( __docker kill $DOCKER_BUILD_CONTAINER )
__docker rm $DOCKER_BUILD_CONTAINER
else
info "Stopping build container $DOCKER_BUILD_CONTAINER"
__docker kill $DOCKER_BUILD_CONTAINER
fi
}
# starts the build container with the DOCKER_BUILD_IMAGE image
# and automatically pulls it if not available locally. Pull output
# is hidden until executed with --verbose. The container is started
# detatched and the container id is set to DOCKER_BUILD_CONTAINER
start_build_container() {
status "Creating build container"
info "Starting build container with image $DOCKER_BUILD_IMAGE"
CONTAINER_BUILD_DIR="${DELIVER_TO/\~//root}" # target must be absolute
DOCKER_BUILD_DIR="$(pwd)/.docker-build"
mkdir -p "$DOCKER_BUILD_DIR"
DOCKER_BUILD_CONTAINER="$([ "$VERBOSE" != "true" ] && exec 2>/dev/null; \
__docker run --detach \
--mount type=bind,source="${DOCKER_BUILD_DIR}",target="$CONTAINER_BUILD_DIR" \
"$DOCKER_BUILD_IMAGE" tail -f /dev/null)" \
|| error "Failed to start build container"
info "Started container $DOCKER_BUILD_CONTAINER"
if [ -n "$DOCKER_BUILD_CONTAINER" ]; then
trap stop_build_container EXIT
fi
}
# clean fetch of dependencies on the remote build host
erlang_get_and_update_deps() {
__exec_if_defined "pre_erlang_get_and_update_deps"
status "Fetching / Updating dependencies"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
if [ \"$BUILD_CMD\" = \"rebar3\" ]; then
echo \"using rebar3 to fetch and update deps\"
which $REBAR3_CMD || {
echo \"installing rebar3\"
wget https://s3.amazonaws.com/rebar3/rebar3 2>&1 && chmod +x rebar3
}
$REBAR3_CMD update get-deps
elif [ \"$BUILD_CMD\" = \"rebar\" ]; then
echo \"using rebar to fetch and update deps\"
$REBAR_CMD update-deps get-deps
elif [ \"$BUILD_CMD\" = \"mix\" ]; then
echo \"using mix to fetch and update deps\"
if [ ! -f ~/.mix/rebar ] || [ ! -f ~/.mix/rebar3 ]; then
APP=\"$APP\" MIX_ENV=\"$TARGET_MIX_ENV\" $MIX_CMD local.rebar --force
else
echo \"rebar and rebar3 for mix was built already\"
fi
APP=\"$APP\" MIX_ENV=\"$TARGET_MIX_ENV\" $MIX_CMD local.hex --force
APP=\"$APP\" MIX_ENV=\"$TARGET_MIX_ENV\" $MIX_CMD deps.get --only $TARGET_MIX_ENV
fi
"
__exec_if_defined "post_erlang_get_and_update_deps"
}
# joins the arguments by the first argument
__join_string() {
trim_string $(local IFS="$1"; shift; echo -ne "$*");
}
# gets the arguments from the --auto-version=, --increment-version and --set-version
# options and concatenates them ready to be used as arguments for the release.version
# mis tasks
__get_auto_version_args() {
__join_string " " "$INCREMENT_RELEASE_VERSION" "$SET_RELEASE_VERSION" "$AUTO_RELEASE_VERSION"
}
# compiles the sources on the remote build host
erlang_clean_compile() {
__exec_if_defined "pre_erlang_clean_compile"
[[ "$SKIP_MIX_CLEAN" = "true" ]] \
&& status "Compiling sources (skipping cleaning)" \
|| status "Compiling sources"
if [ "$RELEASE_CMD" = "mix" ]; then
[[ "$MODE" = "verbose" ]] && local _auto_release_verbose="--verbose"
local _auto_version_args="$(__get_auto_version_args)"
if [[ -n "$_auto_version_args" ]] || [[ -n "$AUTO_VERSION" ]]; then
local _set_auto_version=" release.version $_auto_version_args $_auto_release_verbose,"
fi
fi
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
if [ \"$BUILD_CMD\" = \"rebar3\" ]; then
echo \"using rebar3 to compile files\"
[[ \"$SKIP_MIX_CLEAN\" != \"true\" ]] && $REBAR3_CMD clean || :
$REBAR3_CMD compile
elif [ \"$BUILD_CMD\" = \"rebar\" ]; then
echo \"using rebar to compile files\"
[[ \"$SKIP_MIX_CLEAN\" != \"true\" ]] && $REBAR_CMD clean skip_deps=true || :
$REBAR_CMD compile
elif [ \"$BUILD_CMD\" = \"mix\" ] && [ \"$RELEASE_CMD\" = \"mix\" ]; then
echo \"Checking whether deps must be compiled for mix version 1.3.[01234]\"
# see https://github.com/boldpoker/edeliver/issues/94
if $MIX_CMD --version | grep 'Mix 1.3.[01234]' >/dev/null 2>&1 ; then
echo \"Compiling deps because mix version 1.3.[01234] is used\"
APP=\"$APP\" MIX_ENV=\"$TARGET_MIX_ENV\" $MIX_CMD deps.compile
fi
if [[ \"$SKIP_MIX_CLEAN\" = \"true\" ]]; then
APP=\"$APP\" MIX_ENV=\"$TARGET_MIX_ENV\" AUTO_VERSION=\"$AUTO_VERSION\" BRANCH=\"$BRANCH\" SKIP_RELUP_MODIFICATIONS=\"$SKIP_RELUP_MODIFICATIONS\" RELUP_MODIFICATION_MODULE=\"$RELUP_MODIFICATION_MODULE\" USING_DISTILLERY=\"$USING_DISTILLERY\" $MIX_CMD do${_set_auto_version} compile
else
APP=\"$APP\" MIX_ENV=\"$TARGET_MIX_ENV\" AUTO_VERSION=\"$AUTO_VERSION\" BRANCH=\"$BRANCH\" SKIP_RELUP_MODIFICATIONS=\"$SKIP_RELUP_MODIFICATIONS\" RELUP_MODIFICATION_MODULE=\"$RELUP_MODIFICATION_MODULE\" USING_DISTILLERY=\"$USING_DISTILLERY\" $MIX_CMD do clean,${_set_auto_version} compile
fi
elif [ \"$BUILD_CMD\" = \"mix\" ]; then
echo \"using mix to compile files\"
if [[ \"$SKIP_MIX_CLEAN\" = \"true\" ]]; then
if [[ -n \"$AUTO_VERSION\" ]]; then
hint_message 'Using --auto-version together with --skip-mix-clean would not work!'
fi
APP=\"$APP\" MIX_ENV=\"$TARGET_MIX_ENV\" AUTO_VERSION=\"$AUTO_VERSION\" BRANCH=\"$BRANCH\" SKIP_RELUP_MODIFICATIONS=\"$SKIP_RELUP_MODIFICATIONS\" RELUP_MODIFICATION_MODULE=\"$RELUP_MODIFICATION_MODULE\" USING_DISTILLERY=\"$USING_DISTILLERY\" $MIX_CMD do deps.compile,${_set_auto_version} compile
else
APP=\"$APP\" MIX_ENV=\"$TARGET_MIX_ENV\" AUTO_VERSION=\"$AUTO_VERSION\" BRANCH=\"$BRANCH\" SKIP_RELUP_MODIFICATIONS=\"$SKIP_RELUP_MODIFICATIONS\" RELUP_MODIFICATION_MODULE=\"$RELUP_MODIFICATION_MODULE\" USING_DISTILLERY=\"$USING_DISTILLERY\" $MIX_CMD do clean, deps.compile,${_set_auto_version} compile
fi
fi
"
__exec_if_defined "post_erlang_clean_compile"
}
# generates the release on the remote build host
erlang_generate_release() {
__exec_if_defined "pre_erlang_generate_release"
if [ "$RELEASE_CMD" = "mix" ]; then
if [[ "$USING_DISTILLERY" = "true" ]]; then
local _mix_release_verbose_flag=" --verbose"
local _env_flag=" --env=\"$TARGET_MIX_ENV\""
local _app_flag=" --name=\"$APP\""
if [[ -n "$WITH" ]] || [[ -n "$FROM" ]]; then
local _upgrade_flag=" --upgrade"
if [[ -n "$WITH" ]]; then
_upgrade_flag="${_upgrade_flag} --upfrom=\"$WITH\""
elif [[ -n "$OLD_RELEASE_VERSION" ]]; then
_upgrade_flag="${_upgrade_flag} --upfrom=\"$OLD_RELEASE_VERSION\""
fi
fi
else # using builtin `mix release`
[[ "$MODE" = "verbose" ]] && local _mix_release_verbose_flag="" || local _mix_release_verbose_flag=" --quiet"
fi
[[ "$MODE" = "verbose" ]] && local _auto_release_verbose=" --verbose"
local _auto_version_args="$(__get_auto_version_args)"
if [[ -n "$_auto_version_args" ]] || [[ -n "$AUTO_VERSION" ]]; then
local _set_auto_version="do release.version ${_auto_version_args}${_auto_release_verbose}, "
fi
fi
status "Generating release"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
if [ \"$RELEASE_CMD\" = \"rebar3\" ]; then
echo \"using rebar3 to generate release\"
REBAR_PROFILE=\"$REBAR_PROFILE\" $REBAR3_CMD $RELEASE_CMD_OPTIONS release -n $APP
elif [ \"$RELEASE_CMD\" = \"rebar\" ]; then
echo \"using rebar to generate release\"
$REBAR_CMD $RELEASE_CMD_OPTIONS -f generate
elif [ \"$RELEASE_CMD\" = \"relx\" ]; then
echo \"using relx to generate release\"
$RELX_CMD release
elif [ \"$RELEASE_CMD\" = \"mix\" ]; then
if [ \"$USING_DISTILLERY\" = \"false\" ]; then
echo \"using mix to generate release\"
MIX_ENV=\"$TARGET_MIX_ENV\" LINK_SYS_CONFIG=\"$LINK_SYS_CONFIG\" LINK_VM_ARGS=\"$LINK_VM_ARGS\" APP=\"$APP\" AUTO_VERSION=\"$AUTO_VERSION\" \
BRANCH=\"$BRANCH\" SKIP_RELUP_MODIFICATIONS=\"$SKIP_RELUP_MODIFICATIONS\" RELUP_MODIFICATION_MODULE=\"$RELUP_MODIFICATION_MODULE\" \
USING_DISTILLERY=\"$USING_DISTILLERY\" \
$MIX_CMD $_set_auto_version release${_force_no_interaction}${_mix_release_verbose_flag}${_env_flag}${_app_flag}${_upgrade_flag}
else
echo \"using distillery to generate release\"
MIX_ENV=\"$TARGET_MIX_ENV\" LINK_SYS_CONFIG=\"$LINK_SYS_CONFIG\" LINK_VM_ARGS=\"$LINK_VM_ARGS\" APP=\"$APP\" AUTO_VERSION=\"$AUTO_VERSION\" BRANCH=\"$BRANCH\" SKIP_RELUP_MODIFICATIONS=\"$SKIP_RELUP_MODIFICATIONS\" RELUP_MODIFICATION_MODULE=\"$RELUP_MODIFICATION_MODULE\" USING_DISTILLERY=\"$USING_DISTILLERY\" $MIX_CMD $_set_auto_version distillery.release${_force_no_interaction}${_mix_release_verbose_flag}${_env_flag}${_app_flag}${_upgrade_flag}
fi
fi
"
__exec_if_defined "post_erlang_generate_release"
}
# generates an relup upgrade package with relx
# expects that in _rel folder is the old release
# and in compiled sources the new release
relx_generate_relup() {
__exec_if_defined "pre_relx_generate_relup"
__detect_remote_release_dir
RELEASE_VERSION="" # redetect new version
__detect_remote_release_version
status "Generating relup to version ${RELEASE_VERSION}"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
$RELX_CMD relup
"
__exec_if_defined "post_relx_generate_relup"
}
# creates a tar.gz of the built release
erlang_archive_release() {
__detect_remote_release_dir
__detect_remote_release_version
[[ "$RELEASE_CMD" = "mix" && "$USING_DISTILLERY" = "true" ]] && return 0 # mix archives release automatically
status "Building archive of release ${RELEASE_VERSION}"
# create tar
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
if [ \"$RELEASE_CMD\" = \"rebar\" ] || [ \"$RELEASE_CMD\" = \"rebar3\" ]; then
tar -zcpf $(dirname $RELEASE_DIR)/${APP}_${RELEASE_VERSION}.tar.gz -C $(dirname $RELEASE_DIR) ${APP}
elif [ \"$RELEASE_CMD\" = \"mix\" ]; then
echo \"using tar -zcpf to archive release\"
tar --exclude=${APP}.tar.gz -zcpf $(dirname $RELEASE_DIR)/${APP}/releases/${RELEASE_VERSION}/${APP}.tar.gz -C $(dirname $RELEASE_DIR) ${APP}
elif [ \"$RELEASE_CMD\" = \"relx\" ]; then
echo \"using relx to archive release\"
$RELX_CMD tar
fi
"
}
# copies the generated release from the remote build host
# to the release store. takes the release type as first
# parameter (release|upgrade) and the revision starting
# with a dash as optional second parameter (e.g. "-abc123")
copy_release_to_release_store() {
__detect_remote_release_dir
__detect_remote_release_version
__detect_release_store_type
local _release_type="$1"
local _release_revision="$2"
if [[ "$RELEASE_CMD" = "relx" ]]; then
local _release_file="$(dirname $RELEASE_DIR)/_rel/${APP}-${RELEASE_VERSION}.tar.gz"
elif [[ "$RELEASE_CMD" = "mix" ]]; then
local _release_file="$(dirname $RELEASE_DIR)/${APP}/releases/${RELEASE_VERSION}/${APP}.tar.gz"
else # used rebar to generate release
local _release_file="$(dirname $RELEASE_DIR)/${APP}_${RELEASE_VERSION}.tar.gz"
fi
status "Copying release ${RELEASE_VERSION} to $RELEASE_STORE_TYPE release store"
if [ "$RELEASE_STORE_TYPE" = "s3" ]; then
local _aws_script_content=$(cat $BASE_PATH/libexec/aws)
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $(dirname ${_release_file}) $SILENCE
AWS_ARGUMENTS=\"put ${AWS_BUCKET_NAME}/${APP}_${RELEASE_VERSION}${_release_revision}.${_release_type}.tar.gz $(basename ${_release_file})\" AWS_ACCESS_KEY_ID=\"$AWS_ACCESS_KEY_ID\" AWS_SECRET_ACCESS_KEY=\"$AWS_SECRET_ACCESS_KEY\" perl $SILENCE <<'EOF'
${_aws_script_content}
EOF
" "$HOSTS_APP_USER" "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $(dirname ${_release_file}) $SILENCE
AWS_ARGUMENTS=\"put ${AWS_BUCKET_NAME}/${APP}_${RELEASE_VERSION}${_release_revision}.${_release_type}.tar.gz $(basename ${_release_file})\" AWS_ACCESS_KEY_ID=\"$AWS_ACCESS_KEY_ID\" \\
AWS_SECRET_ACCESS_KEY=\"$AWS_SECRET_ACCESS_KEY\" perl $SILENCE <<'EOF'
content from libexec/aws file
EOF
"
[ "$DOCKER_PUSH" = "true" ] && hint_message "Ignoring --push flag because s3 release store is not a docker registry." || :
elif [ "$RELEASE_STORE_TYPE" = "local" ]; then
status "Copying $(basename $_release_file) to release store"
__create_directory_in_release_store "/releases"
if [ -n "$DOCKER_BUILD_CONTAINER" ]; then
__docker cp "${DOCKER_BUILD_CONTAINER}:${_release_file}" "${RELEASE_STORE}/releases/${APP}_${RELEASE_VERSION}${_release_revision}.${_release_type}.tar.gz" \
|| error "Failed to copy release from container to release store"
else
__exec "scp -o ConnectTimeout=\"$SSH_TIMEOUT\" -p $BUILD_USER@$BUILD_HOST:${_release_file} ${RELEASE_STORE}/releases/${APP}_${RELEASE_VERSION}${_release_revision}.${_release_type}.tar.gz" \
|| error "Failed to copy release to release store"
fi
[ "$DOCKER_PUSH" = "true" ] && hint_message "Ignoring --push flag because local release store is not a docker registry." || :
elif [ "$RELEASE_STORE_TYPE" = "remote" ]; then
local _release_store_path=${RELEASE_STORE#*:}
local _release_store_host=${RELEASE_STORE%:*}
local _build_hosts="${2:-"$HOSTS_APP_USER"}"
local _dest_file_name="${_release_store_path%%/}/${APP}_${RELEASE_VERSION}${_release_revision}.${_release_type}.tar.gz"
if [ "$BUILD_HOST" = "docker" ]; then
local _local_release_dir="${DOCKER_BUILD_DIR}${RELEASE_DIR#$CONTAINER_BUILD_DIR}"
[ -d "$_local_release_dir" ] || error "Local release dir '${_local_release_dir}' not found!"
_release_file="${_local_release_dir}/releases/${RELEASE_VERSION}/${APP}.tar.gz"
[ -f "$_release_file" ] || error "Local release file '${_release_file}' not found!"
scp "$_release_file" "${_release_store_host}:${_dest_file_name}"
else
local _remote_job="
[ -f $PROFILE ] && source $PROFILE
set -e
[ -f \"$_release_file\" ]
if [[ \"$_build_hosts\" = \"$_release_store_host\" ]]; then
cp $_release_file $_dest_file_name
else
cat $_release_file | ssh $_release_store_host \"cat > $_dest_file_name\"
fi
exit
"
ssh -A -S none -o ConnectTimeout="$SSH_TIMEOUT" "$_build_hosts" "$_remote_job $SILENCE" || \
error "Copying release file failed\n source: $_release_file on $_build_hosts\n destination: $_dest_file_name on $_release_store_host"
[ "$DOCKER_PUSH" = "true" ] && hint_message "Ignoring --push flag because remote release store is not a docker registry." || :
fi
elif [ "$RELEASE_STORE_TYPE" = "docker" ]; then
status "Generating docker image with release"
info "Creating image $DOCKER_RUN_IMAGE"
info "Using base image $DOCKER_RELEASE_BASE_IMAGE"
if [[ -z "$(__docker images -q "$DOCKER_RELEASE_BASE_IMAGE")" ]]; then
info "Pulling release base image $DOCKER_RELEASE_BASE_IMAGE"
__docker pull "$DOCKER_RELEASE_BASE_IMAGE" || error "Failed to pull release base image $DOCKER_RELEASE_BASE_IMAGE"
fi
local _release_container
info "Creating release container"
_release_container="$(__docker create "$DOCKER_RELEASE_BASE_IMAGE")" || error "Failed to create release container"
if [ -z "$RELEASE_DIR" ]; then
__detect_remote_release_dir || error "Failed to detect release dir"
fi
info "Copying built release into release container at /${APP}"
local _local_release_dir="${DOCKER_BUILD_DIR}${RELEASE_DIR#$CONTAINER_BUILD_DIR}"
[ -d "$_local_release_dir" ] || error "Local release dir '${_local_release_dir}' not found!"
__docker cp "$_local_release_dir" "${_release_container}:/${APP}" || error "Failed to copy release into release container"
[ "$VERBOSE" = "true" ] && __docker diff "$_release_container" || :
local _valid_docker_tag
_valid_docker_tag="$(echo "$RELEASE_VERSION" | sed 's/[^0-9A-Za-z_.-]*//g')"
info "Commiting as ${_valid_docker_tag}-latest"
local _release_command="$RELEASE_CMD"
if [ "$_release_command" = "mix" ]; then
if ! [ "$USING_DISTILLERY" = "false" ]; then
_release_command="distillery"
fi
fi
local _committed_image="${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-latest"
# __docker() function cannot be used here, because passing the -c="LABEL …" args causes
# "docker commit requires at least 1 and at most 2 arguments" error, see:
# https://stackoverflow.com/questions/60008200/docker-commit-requires-at-least-1-and-at-most-2-arguments
# and no quoting helps unfortunately. Setting the release command is required when installing the default
# container start script (if no custom script is embedded into the image) because releases built with distillery
# needs a different ERL_DIST_PORT configuration as releases built with rebar3. See: remote_extract_release_archive()
if [ "$VERBOSE" = "true" ]; then
docker commit -c="LABEL edeliver.release.command=$_release_command" "$_release_container" "$_committed_image" || error "
Failed to commit release container.
Command 'docker commit -c=\"LABEL edeliver.release.command=$_release_command\" $_release_container $_committed_image'
failed with status code $?!
Error output is displayed above.
"
else # not verbose
local _stdout
_stdout="$(docker commit -c="LABEL edeliver.release.command=$_release_command" "$_release_container" "$_committed_image")" || error "
$_stdout
Failed to commit release container.
Command 'docker commit -c=\"LABEL edeliver.release.command=$_release_command\" $_release_container $_committed_image'
failed with status code $?!
Stdout and error output is displayed above."
fi
local _built_revision _build_branch
_built_revision="$([ -n "$DOCKER_BUILD_DIR" ] && cd "$DOCKER_BUILD_DIR" 2>/dev/null && git rev-parse --short HEAD 2>/dev/null)" && {
info "Tagging also as ${_valid_docker_tag}-${_built_revision}"
__docker tag "$_committed_image" "${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-${_built_revision}" \
|| error "Failed to tag image with built revision"
} || hint_message "Failed to detect built revision"
# the built branch must not contain dashes '-' because it is treated as separator from the real release version.
_built_branch="$([ -n "$DOCKER_BUILD_DIR" ] && cd "$DOCKER_BUILD_DIR" 2>/dev/null && git rev-parse --abbrev-ref HEAD 2>/dev/null | tr "-" "_")" && {
info "Tagging also as ${_valid_docker_tag}-${_built_branch}"
__docker tag "$_committed_image" "${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-${_built_branch}" \
|| error "Failed to tag image with branch name"
} || hint_message "Failed to detect built branch"
if [ "$DOCKER_PUSH" = "true" ]; then
status "Pusing release image to registry"
info "Pushing ${_committed_image}"
__docker push "${_committed_image}" || error "Failed to push release image ${_committed_image}"
info "Pushing ${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-${_built_revision}"
__docker push "${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-${_built_revision}" || error "Failed to push release image ${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-${_built_revision}"
info "Pushing ${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-${_built_branch}"
__docker push "${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-${_built_branch}" || error "Failed to push release image ${DOCKER_RUN_IMAGE}:${_valid_docker_tag}-${_built_branch}"
fi
else
error_message "Cannot copy releases to store for store type ${RELEASE_STORE_TYPE}"; exit 2
fi
}
# copies the release from the release store
# to a local temp dir which is set to LOCAL_RELEASE_TMP_DIR
# arg1 : release type: (release|upgrade)
# arg2 : release version
copy_release_to_local_temp_dir() {
__detect_release_store_type
local _release_type="$1"
local _release_version="$2"
status "Copying release from $RELEASE_STORE_TYPE release store to local temp dir"
mkdir -p "${LOCAL_RELEASE_STORE}/.tmp"
local _local_tmp_dir=$(mktemp -d "${LOCAL_RELEASE_STORE}/.tmp/${APP}_${_release_version}.${_release_type}.XXXXXX")
[[ "$MODE" = "verbose" ]] && echo "Temp dir: ${_local_tmp_dir}"
if [ "$RELEASE_STORE_TYPE" = "s3" ]; then
download_file_from_s3_release_store "${APP}_${_release_version}.${_release_type}.tar.gz" "${_local_tmp_dir}/${APP}_${_release_version}.${_release_type}.tar.gz"
elif [ "$RELEASE_STORE_TYPE" = "remote" ]; then
download_file_from_remote_release_store "${APP}_${_release_version}.${_release_type}.tar.gz" "${_local_tmp_dir}/${APP}_${_release_version}.${_release_type}.tar.gz"
elif [ "$RELEASE_STORE_TYPE" = "local" ]; then
cp ${RELEASE_STORE}/releases/${APP}_${_release_version}.${_release_type}.tar.gz ${_local_tmp_dir}
else
error_message "Cannot copy releases from store for store type ${RELEASE_STORE_TYPE}"; exit 2
fi
LOCAL_RELEASE_TMP_DIR="$_local_tmp_dir"
}
get_relup_content_from_upgrade() {
__detect_release_store_type
local _release_file="$1"
local _release_version=$(__release_version_from_archive_name "$_release_file" "upgrade")
if [ "$RELEASE_STORE_TYPE" = "local" ]; then
tar -Oxzf ${RELEASE_STORE}/releases/${_release_file} releases/${_release_version}/relup
elif [ "$RELEASE_STORE_TYPE" = "remote" ]; then
local _release_store_path=${RELEASE_STORE#*:}
local _release_store_host=${RELEASE_STORE%:*}
local _remote_job="
[ -f $PROFILE ] && source $PROFILE
set -e
cd $_release_store_path
tar -Oxzf \"$_release_file\" releases/${_release_version}/relup
exit
"
ssh -o ConnectTimeout="$SSH_TIMEOUT" "$_release_store_host" "$_remote_job $SILENCE"
elif [ "$RELEASE_STORE_TYPE" = "s3" ]; then
local _local_tmp_dir=$(mktemp -d "${LOCAL_RELEASE_STORE}/.tmp/${_release_file}.XXXXXX")
download_file_from_s3_release_store "$_release_file" "${_local_tmp_dir}/${_release_file}"
tar -Oxzf "${_local_tmp_dir}/${_release_file}" releases/${_release_version}/relup
[[ -n "$_local_tmp_dir" ]] && [[ -d "$_local_tmp_dir" ]] && rm -rf "$_local_tmp_dir"
fi
}
# copies a local file to the local or remote release store
upload_file_to_release_store() {
__detect_release_store_type
local _source_file="$1"
local _destination_file="$2"
if [ "$RELEASE_STORE_TYPE" = "s3" ]; then
upload_file_to_s3_release_store "$_source_file" "$_destination_file"
elif [ "$RELEASE_STORE_TYPE" = "local" ]; then
status "Copying ${_source_file} to local release store"
[ "$FORCE" != "true" ] && file_exists_in_store $(basename $_destination_file) && {
read -n1 -p "${txtylw}Destination File '$_destination_file' already uploaded. Overwrite? (y/n)${txtrst}"
echo
[[ $REPLY = [yY] ]] || exit 1
}
cp $_source_file ${RELEASE_STORE}/releases/${_destination_file} || exit 2
elif [ "$RELEASE_STORE_TYPE" = "remote" ]; then
upload_file_to_remote_release_store "$_source_file" "$_destination_file"
else
error_message "Cannot copy releases to store for store type ${RELEASE_STORE_TYPE}"; exit 2
fi
}
upload_file_to_s3_release_store() {
local _source_file=$1
local _destination_file=$(basename "$2")
[ ! -f "$_source_file" ] && error "\nFAILED to upload $_source_file.\nFile does not exist.\n" && exit 2
[ "$FORCE" != "true" ] && file_exists_in_store $_destination_file && {
read -n1 -p "${txtylw}Destination File '$_destination_file' already uploaded. Overwrite? (y/n)${txtrst}"
echo
[[ $REPLY = [yY] ]] || exit 1
}
status "Uploading file $_source_file to $_destination_file"
AWS_ARGUMENTS="put ${AWS_BUCKET_NAME}/${_destination_file} ${_source_file}" AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" perl $BASE_PATH/libexec/aws || {
error "\nFAILED to upload $_source_file\n"
}
}
upload_file_to_remote_release_store() {
local _source_file=$1
local _destination_file=$(basename "$2")
local _release_store_path=${RELEASE_STORE#*:}
local _release_store_host=${RELEASE_STORE%:*}
[ ! -f "$_source_file" ] && error "\nFAILED to upload $_source_file.\nFile does not exist.\n" && exit 2
[ "$FORCE" != "true" ] && file_exists_in_store $_destination_file && {
read -n1 -p "${txtylw}Destination File '$_destination_file' already uploaded. Overwrite? (y/n)${txtrst}"
echo
[[ $REPLY = [yY] ]] || exit 1
}
status "Uploading file $_source_file to $_destination_file"
__exec "scp -o ConnectTimeout=\"$SSH_TIMEOUT\" -p ${_source_file} ${_release_store_host}:${_release_store_path%%/}/${_destination_file} $SILENCE"
}
# copies the generated appup files from the remote build host
# to the release store
copy_appups_to_release_store() {
__detect_remote_release_dir
RELEASE_VERSION="" # redetect new version
__detect_remote_release_version
local _old_version=$1
local _appup_files="$RELEASE_DIR/lib/*/ebin/*.appup"
status "Copying generated appup files to release store /appup/${_old_version}-${RELEASE_VERSION}"
# create release store directory if it is a local directory
__create_directory_in_release_store "/appup/${_old_version}-${RELEASE_VERSION}"
__exec "scp -o ConnectTimeout=\"$SSH_TIMEOUT\" -p $BUILD_USER@$BUILD_HOST:${_appup_files} ${RELEASE_STORE}/appup/${_old_version}-${RELEASE_VERSION}"
}
file_exists_in_store() {
local _file=$1
local _release_type=${2:-*}
for i in $(__get_releases_in_store "$_release_type"); do
[[ "$i" = "$_file" ]] && return 0
done
return 1;
}
download_file_from_s3_release_store() {
local _source_file=$1
local _destination_file=$2
if [ -f "$_destination_file" ] && [ "$FORCE" != "true" ]; then
read -n1 -p "${txtylw}Destination File '$_destination_file' already exists. Overwrite? (y/n)${txtrst}"
echo
[[ $REPLY = [yY] ]] || exit 1
fi
file_exists_in_store $_source_file || {
error "\nFAILED to download $_source_file.\nFile does not exist in remote release store.\n"
exit 2
}
status "Downloading $_source_file to $_destination_file"
AWS_ARGUMENTS="get ${AWS_BUCKET_NAME}/${_source_file}" AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" perl $BASE_PATH/libexec/aws 2>/dev/null > "$_destination_file" || {
[[ -f "$_destination_file" ]] && [[ ! -s "$_destination_file" ]] && rm "$_destination_file"
error "\nFAILED to download $_source_file\n"
}
}
download_file_from_remote_release_store() {
local _source_file=$1
local _destination_file=$2
local _release_store_path=${RELEASE_STORE#*:}
local _release_store_host=${RELEASE_STORE%:*}
if [ -f "$_destination_file" ] && [ "$FORCE" != "true" ]; then
read -n1 -p "${txtylw}Destination File '$_destination_file' already exists. Overwrite? (y/n)${txtrst}"
echo
[[ $REPLY = [yY] ]] || exit 1
fi
file_exists_in_store $_source_file || {
error "\nFAILED to download $_source_file.\nFile does not exist in remote release store.\n"
exit 2
}
status "Downloading $_source_file to $_destination_file"
__exec "scp -o ConnectTimeout=\"$SSH_TIMEOUT\" -p ${_release_store_host}:${_release_store_path%%/}/${_source_file} ${_destination_file} $SILENCE"
}
# reselects the release or update to deploy to productions hosts
# from the release store. Sets the RELEASE_FILE and VERSION variables
# takes the release type as parameter (release|upgrade|release_or_upgrade_with_release|*)
select_release_from_store() {
local _release_type="$1"
local _action=${2:-deploy}
if [ -n "$VERSION" ]; then
[[ "$_release_type" = "release_or_upgrade_with_release" ]] && _release_type="*" && local _autoselect_release_if_relase_and_upgrade_exists="true"
if [[ "$_release_type" = "*" ]]; then
local _release_files=( $(__get_releases_in_store "$_release_type" | grep "$VERSION" | sort -bt. -k1,1 -k2,2n -k3,3n -k4,4n -k5,5n) )
if [ ${#_release_files[@]} -eq 1 ]; then
RELEASE_FILE=$(basename ${_release_files[0]})
elif [ ${#_release_files[@]} -eq 0 ]; then
error_message "Release with version $VERSION not found in ${RELEASE_STORE_TYPE} release store."
hint_message "You can build one with the ./edeliver build $_release_type task."
exit 2
else
for _release_file in "${_release_files[@]}"; do
[[ "$_release_file" = "${APP}_${VERSION}."*".tar.gz" ]] && _release_file=${_release_file##${APP}_${VERSION}.} && _release_file=${_release_file%%.tar.gz} && _releases_types_with_version+=($_release_file)
done
if [ ${#_releases_types_with_version[@]} -eq 1 ]; then
_release_type=${_releases_types_with_version[0]}
else
if [[ "$_autoselect_release_if_relase_and_upgrade_exists" = "true" ]]; then
_release_type="release"
RELEASE_FILE="${APP}_${VERSION}.${_release_type}.tar.gz"
else
hint_message "Found ${#_releases_types_with_version[@]} different release types for version ${VERSION}."
for _release_type in "${_releases_types_with_version[@]}"; do
hint_message " $_release_type"
done
hint_message "Enter Release Type:"
read _release_type
fi
fi
RELEASE_FILE="${APP}_${VERSION}.${_release_type}.tar.gz"
fi
else
RELEASE_FILE="${APP}_${VERSION}.${_release_type}.tar.gz"
fi
else
[[ "$_release_type" = "release_or_upgrade_with_release" ]] && _release_type="*" && local _autoselect_release_if_relase_and_upgrade_exists="true"
__detect_release_store_type
local _release_files=( $(__get_releases_in_store "$_release_type") )
if [ ${#_release_files[@]} -eq 1 ]; then
RELEASE_FILE=$(basename ${_release_files[0]})
VERSION=$(__release_version_from_archive_name "$RELEASE_FILE" "$_release_type")
elif [ ${#_release_files[@]} -eq 0 ]; then
error_message "No $_release_type(s) found in ${RELEASE_STORE_TYPE} release store."
hint_message "You can build one with the ./edeliver build $_release_type task."
exit 2
else
status "Selecting release"
hint_message "Found ${#_release_files[@]} different versions to ${_action}."
hint_message "Type the version you want to ${_action}"
hint_message "or set --version=X in the command line."
hint_message "Versions:"
local _release_file
for _release_file in "${_release_files[@]}"; do
local _current_version=$(__release_version_from_archive_name "$_release_file" "$_release_type")
hint_message " $_current_version"
done
hint_message "Enter Version:"
read VERSION
if [[ "$_release_type" = "*" ]]; then
local _releases_types_with_version=()
for _release_file in "${_release_files[@]}"; do
[[ "$_release_file" = "${APP}_${VERSION}."*".tar.gz" ]] && _release_file=${_release_file##${APP}_${VERSION}.} && _release_file=${_release_file%%.tar.gz} && _releases_types_with_version+=($_release_file)
done
if [ ${#_releases_types_with_version[@]} -eq 1 ]; then
_release_type=${_releases_types_with_version[0]}
else # found release and upgrade
if [[ "$_autoselect_release_if_relase_and_upgrade_exists" = "true" ]]; then
_release_type="release"
RELEASE_FILE="${APP}_${VERSION}.${_release_type}.tar.gz"
else
hint_message "Found ${#_releases_types_with_version[@]} different release types for version ${VERSION}."
for _release_type in "${_releases_types_with_version[@]}"; do
hint_message " $_release_type"
done
hint_message "Enter Release Type:"
read _release_type
fi
fi
RELEASE_FILE="${APP}_${VERSION}.${_release_type}.tar.gz"
else
RELEASE_FILE="${APP}_${VERSION}.${_release_type}.tar.gz"
fi
fi
fi
}
# validates that the release or upgrade with the given version is in
# the release store.
# takes the release type as first parameter (release|update|upgrade)
# and the version that should be in the store as second parameter
validate_release_is_in_store() {
local _release_type="$1"
local _release_version="$2"
__detect_release_store_type
status "Validating ${_release_type} version ${_release_version} is in ${RELEASE_STORE_TYPE} release store"
local _release_files=( $(__get_releases_in_store "$_release_type") )
for _release_file in "${_release_files[@]}"; do
local _current_version=$(__release_version_from_archive_name "$_release_file" "$_release_type")
[[ "$_current_version" = "$_release_version" ]] && RELEASE_FILE="$_release_file" && return 0
done
error_message "\n${_release_type} version ${_release_version} not found in release store\n"
return 1
}
# lists the available releases in the store
list_releases_in_store() {
local _release_type="$1"
__detect_release_store_type
status "Listing ${APP} releases in ${RELEASE_STORE_TYPE} release store"
local _release_files=( $(__get_releases_in_store "$_release_type") )
local _app_column_width=${#APP}
[ "$RELEASE_STORE_TYPE" = "docker" ] && local _release_file_type="Image" || local _release_file_type="File"
[[ $_app_column_width -lt 11 ]] && _app_column_width="11"
printf "\n %${_app_column_width}s | Version | Revision | Type | $_release_file_type\n-" "Application"
for i in $(seq 1 $_app_column_width); do printf "-"; done
printf '-' {1.."$_app_column_width"}
printf "+---------+----------+---------+---------------------\n"
local _release_file
for _release_file in "${_release_files[@]}"; do
local _release_revision="" _release_version=""
local _full_release_version=$(__release_version_from_archive_name "$_release_file" "$_release_type")
if [[ "$_full_release_version" =~ - ]]; then
_release_revision=${_full_release_version##*-}
_release_version=${_full_release_version%%-*}
else
_release_version="$_full_release_version"
fi
[[ "$_release_revision" = "$_release_version" ]] && _release_revision=""
if [ "$RELEASE_STORE_TYPE" = "docker" ]; then
local _current_release_type="release"
local _release_file="${DOCKER_RUN_IMAGE}:${_full_release_version}"
else
local _removed_app_name=${_release_file/${APP}_/}
local _removed_archive_extension=${_removed_app_name/\.tar\.gz/}
local _current_release_type=${_removed_archive_extension/$_full_release_version./}
fi
printf " %${_app_column_width}s | %7s | %8s | %7s | %s\n" "$APP" "$_release_version" "$_release_revision" "$_current_release_type" "$_release_file"
done
}
# gets the names of the release packages in the release store.
__get_releases_in_store() {
local _release_type="$1"
__detect_release_store_type
if [ "$RELEASE_STORE_TYPE" = "s3" ]; then
AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" ${BASE_PATH}/libexec/aws ls ${AWS_BUCKET_NAME} 2>/dev/null | grep -o "${APP}_.*.${_release_type}.tar.gz" | sort -bt. -k1,1 -k2,2n -k3,3n -k4,4n -k5,5n
elif [ "$RELEASE_STORE_TYPE" = "local" ]; then
local _release_file;
for _release_file in $(ls -1 ${RELEASE_STORE}/releases/${APP}_*.${_release_type}.tar.gz | sort -bt. -k1,1 -k2,2n -k3,3n -k4,4n -k5,5n); do
echo $(basename $_release_file)
done
elif [ "$RELEASE_STORE_TYPE" = "remote" ]; then
local _release_store_path=${RELEASE_STORE#*:}
local _release_store_host=${RELEASE_STORE%:*}
local _remote_job="
[ -f $PROFILE ] && source $PROFILE
set -e
cd $_release_store_path
ls -1 ${APP}_*.${_release_type}.tar.gz | sort -bt. -k1,1 -k2,2n -k3,3n -k4,4n -k5,5n
exit
"
ssh -o ConnectTimeout="$SSH_TIMEOUT" "$_release_store_host" "$_remote_job $SILENCE"
elif [ "$RELEASE_STORE_TYPE" = "docker" ] && [ "$DEPLOY_ENVIRONMENT" = "local" ]; then
docker image ls --format "{{.Tag}}" "${DOCKER_RUN_IMAGE}"
elif [ "$RELEASE_STORE_TYPE" = "docker" ]; then
local _registry _name _image _out _status _tags _using_docker_hub
IFS='/' read _registry _name _image < <(echo "$DOCKER_RUN_IMAGE")
if [ -n "$_registry" ] && [ -n "$_name" ] && [ -n "$_image" ]; then
:
else # using DockerHub as registry
_image="$_name"
_name="$_registry"
_registry="index.docker.io"
_using_docker_hub="true"
# authenticate with DockerHub access token to create a Bearer token used as DOCKER_REGISTRY_TOKEN
if [ -n "$DOCKER_HUB_ACCESS_TOKEN" ] && [ -z "$DOCKER_REGISTRY_TOKEN" ]; then
DOCKER_REGISTRY_TOKEN="$(curl --silent -u "${_name}:${DOCKER_HUB_ACCESS_TOKEN}" \
--get --data-urlencode "service=registry.docker.io" \
--data-urlencode "scope=repository:${_name}/${_image}:pull" \
"https://auth.docker.io/token" | sed "s/{.*\"token\":\"\([^\"]*\).*}/\1/g")"
fi
fi
if [ -n "$DOCKER_REGISTRY_TOKEN" ]; then
_out="$(curl --fail --silent -H "Authorization: Bearer $DOCKER_REGISTRY_TOKEN" "https://${_registry}/v2/${_name}/${_image}/tags/list")"
else
_out="$(curl --fail --silent "https://${_registry}/v2/${_name}/${_image}/tags/list")"
fi
_status=$?
if [ "$_status" != "0" ]; then
echo "Failed to detect tags of image ${_name}/${_image} from registry ${_registry}" >&2
if [ -z "$DOCKER_REGISTRY_TOKEN" ]; then
echo "Maybe it is required to set DOCKER_REGISTRY_TOKEN in your edeliver config for authorization." >&2
if [ "$_using_docker_hub" = "true" ] && [ -z "$DOCKER_HUB_ACCESS_TOKEN" ]; then
echo "You can also set DOCKER_HUB_ACCESS_TOKEN to generate DOCKER_REGISTRY_TOKEN automatically." >&2
fi
fi
exit 2
else
local _image_info _tag _created_at
# e.g. for google container registry containing detaild information about tags and their timestamps:
# {"manifest": {"sha256:03302a…": {"timeCreatedMs":…, "tag":["tag1"], …}, "tags":["tag1","tag2"]}
if echo "$_out" | grep -oe '{[^}]\+\"tag\"\s*:\s*\[[^}]\+' >/dev/null 2>&1; then
for _image_info in $(echo "$_out" | grep -oe '{[^}]\+\"tag\"\s*:\s*\[[^}]\+'); do
_tag="$(echo "$_image_info" | grep -oe '\"tag\"\s*:\s*\[[^]]\+' | cut -f2 -d "[" | grep -oe '"[^"]\+"' | tr -d '"' | tr "\n" " ")"
_created_at="$(echo "$_image_info" | grep -oe '\"timeCreatedMs\"\s*:\s*\"[^\"]\+\"' | cut -f2 -d ":" | grep -oe '"[^"]\+"' | tr -d '"')"
if [ -n "$_tag" ]; then
echo "$_created_at $_tag"
fi
done | sort -n | cut -d' ' -f 2-
else
# dockerhub does not have a list of single tags with timestamps, so access the tags direclty
# from a struct like: {"name":"user-name/image-name","tags":["tag1","tag2"]}
echo "$_out" | grep -oe '\"tags\"\s*:\s*\[[^]]\+' | cut -f2 -d "[" | grep -oe '"[^"]\+"' | tr -d '"' | tr "\n" " "
fi
fi
else
error_message "Cannot detect releases in store for store type ${RELEASE_STORE_TYPE}" >&2; exit 2
fi
}
# copies the release archive to the production hosts to the given path.
# the current release file and version must be set in the
# RELEASE_FILE and VERSION variables. an optional destination directory
# can be passed as argument, which is $DELIVER_TO by default
upload_release_archive() {
local _destination_directory=${1:-"$DELIVER_TO"}
local _release_file=${2:-"$RELEASE_FILE"}
local _release_version=${3:-"$VERSION"}
__detect_release_store_type
status "Uploading archive of release $_release_version from $RELEASE_STORE_TYPE release store"
# when building releases with mix, the tar does not contain the app name as subdirectory
if [ "$RELEASE_CMD" = "mix" ]; then
case "$_release_file" in
*upgrade.tar.gz)
;;
*)
_destination_directory="${_destination_directory%%/}/${APP}"
;;
esac
fi
if [ "$RELEASE_STORE_TYPE" = "s3" ]; then
local _aws_script_content=$(cat $BASE_PATH/libexec/aws)
__remote "
[ -f $PROFILE ] && source $PROFILE
set -e
mkdir -p ${_destination_directory} $SILENCE
cd ${_destination_directory} $SILENCE
AWS_ARGUMENTS=\"get ${AWS_BUCKET_NAME}/${_release_file} \" AWS_ACCESS_KEY_ID=\"$AWS_ACCESS_KEY_ID\" AWS_SECRET_ACCESS_KEY=\"$AWS_SECRET_ACCESS_KEY\" perl > ${APP}_${_release_version}.tar.gz 2>/dev/null <<'EOF'
${_aws_script_content}
EOF
" "$HOSTS_APP_USER" "
[ -f $PROFILE ] && source $PROFILE
set -e
mkdir -p ${_destination_directory} $SILENCE
cd ${_destination_directory} $SILENCE
AWS_ARGUMENTS=\"get ${AWS_BUCKET_NAME}/${_release_file} \" AWS_ACCESS_KEY_ID=\"$AWS_ACCESS_KEY_ID\" \\
AWS_SECRET_ACCESS_KEY=\"$AWS_SECRET_ACCESS_KEY\" perl > ${APP}_${_release_version}.tar.gz 2>/dev/null <<'EOF'
content from libexec/aws file
EOF
"
elif [ "$RELEASE_STORE_TYPE" = "local" ]; then
__remote "
[ -f $PROFILE ] && source $PROFILE
set -e
mkdir -p ${_destination_directory} "
__parallel_scp "${RELEASE_STORE}/releases/${_release_file}" "${_destination_directory}/${APP}_${_release_version}.tar.gz"
elif [ "$RELEASE_STORE_TYPE" = "remote" ]; then
local _release_store_path=${RELEASE_STORE#*:}
local _release_store_host=${RELEASE_STORE%:*}
local _deploy_hosts="${HOSTS_APP_USER}"
local _dest_file_name="${_destination_directory}/${APP}_${_release_version}.tar.gz"
local _remote_job="
[ -f $PROFILE ] && source $PROFILE
set -e
cd $_release_store_path
[ -f \"$_release_file\" ]
background_jobs_pids=()
background_jobs=()
"
for _host in $_deploy_hosts; do
_remote_job="$_remote_job
if [[ \"$_host\" = \"$_release_store_host\" ]]; then
mkdir -p \"${_destination_directory}\"
cp $_release_file $_dest_file_name &
else
ssh \"$_host\" \"mkdir -p \\\"${_destination_directory}\\\" \"
cat $_release_file | ssh \"$_host\" \"cat > $_dest_file_name\" &
fi
background_jobs_pids+=(\"\$!\")
"
done
_remote_job="$_remote_job
for pid in \$(echo \"\${background_jobs_pids[@]}\"); do
while true; do
kill -0 \$pid 2>/dev/null && sleep 1 || break
done
done
exit
"
ssh -A -S none -o ConnectTimeout="$SSH_TIMEOUT" "$_release_store_host" "$_remote_job $SILENCE" || \
error "Uploading release file failed\n source: ${_release_store_path%%/}/${_release_file} on $_release_store_host\n destination: $_dest_file_name on deploy hosts"
elif [ "$RELEASE_STORE_TYPE" = "docker" ]; then
local _image_name="${DOCKER_RUN_IMAGE}:${_release_version}"
__remote "
[ -f $PROFILE ] && . $PROFILE
set -e
mkdir -p ${_destination_directory} $SILENCE
cd ${_destination_directory} $SILENCE
if [ -z \"\$(docker images -q \"$_image_name\")\" ]; then
echo \"Pulling release image $_image_name\" $SILENCE
docker pull \"$_image_name\" $SILENCE || {
echo \"Failed to pull release image $_image_name\" >&2
exit 1
}
fi
"
else
error_message "Cannot upload release for store type ${RELEASE_STORE_TYPE}"; exit 2
fi
}
# upgrades the remote hosts with running nodes to
# the new release version.
upgrade_release() {
__exec_if_defined "pre_upgrade_release"
status "Upgrading release to $VERSION"
[[ "$RELEASE_CMD" = "mix" ]] && local _upgrade_command="bin/${APP} upgrade ${VERSION} $SILENCE" \
|| local _upgrade_command="bin/${APP} upgrade ${APP}_${VERSION} $SILENCE"
__remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd ${DELIVER_TO}/${APP} $SILENCE
$_upgrade_command
"
__exec_if_defined "post_upgrade_release"
}
# deletes the $APP/releases, $APP/lib and $APP/erts*
# directories before the release is extracted
remote_clean_release_dir() {
local _release_version=${1:-"$VERSION"}
local _archive_dir=${2:-"$DELIVER_TO"}
local _archive="${APP}_${_release_version}.tar.gz"
status "Cleaning release directory"
__remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd ${_archive_dir} $SILENCE
[[ \"$RELEASE_CMD\" = \"mix\" ]] && _app_folder=\"${APP}\" || _app_folder=\$(dirname \$(dirname \$(tar -tzf ${_archive} | grep releases/RELEASES)))
[[ -n \"\$_app_folder\" ]]
if [[ -d \$_app_folder ]]; then
cd \${_app_folder}
if [[ -d releases ]]; then
echo \"deleting releases dir in \${_app_folder}\" $SILENCE
rm -rf releases $SILENCE
else
echo \"releases dir already deleted in \${_app_folder}\" $SILENCE
fi
if [[ -d lib ]]; then
echo \"deleting lib dir in \${_app_folder}\" $SILENCE
rm -rf lib $SILENCE
else
echo \"lib dir already deleted in \${_app_folder}\" $SILENCE
fi
ls -al | grep erts- > /dev/null && {
for erts_dir in erts-*; do
if [[ -n \"\$erts_dir\" ]] && [[ -d \$erts_dir ]]; then
echo \"deleting \${erts_dir} in \${_app_folder}\" $SILENCE
rm -rf \${erts_dir} $SILENCE
else
echo \"erts dir already deleted in \${_app_folder}\" $SILENCE
fi
done
} || {
echo \"erts dir already deleted in \${_app_folder}\" $SILENCE
}
else
echo \"\${_app_folder} does not exist\" $SILENCE
fi
"
}
__get_node_command() {
local _node_command="$1"
local _app_path="$2"
local _config_arg="$3"
local _rpc_command="rpc"
if declare -F "get_${_node_command}_node_command" > /dev/null; then
eval "get_${_node_command}_node_command $@"
else
if [[ "$USING_DISTILLERY" = "true" ]]; then
local _rpc_open_brackets="["
local _rpc_close_brackets="]"
else
local _rpc_open_brackets="[["
local _rpc_close_brackets="]]"
fi
[[ "${_node_command}" = start* ]] && local _is_start_command="true" || :
[[ "${_node_command}" = restart* ]] && local _is_restart_command="true" || :
echo "
[ -f \"$PROFILE\" ] && . \"$PROFILE\"
set -e
cd ${_app_path}/${APP} $SILENCE
output_lines=\"\$(bin/${APP} ping | wc -l | tr -d ' ')\"
if [ \"\$output_lines\" -gt 1 ]; then
output_filter_command='tail'
output_filter_command_options=\"-n+\$output_lines\"
else
output_filter_command='cat'
output_filter_command_options=''
fi
__edeliver_node_running() {
bin/${APP} ping 2>/dev/null >/dev/null
}
__edeliver_synchronous_start() {
bin/${APP} start | \$output_filter_command \$output_filter_command_options
sleep 1
for i in 1 2 3 4 5 6 7 8 9 10; do
__edeliver_node_running && break || :
sleep 1
done
bin/${APP} ${_rpc_command} 'Elixir.Edeliver.run_command(${_rpc_open_brackets}:monitor_startup_progress, \"$APP\", :$MODE${_rpc_close_brackets})' | \$output_filter_command \$output_filter_command_options | grep -e 'Started\\|^ok' || :
}
if [ \"$RELEASE_CMD\" = \"mix\" ] && [ \"$_is_start_command\" = \"true\" ]; then
ping_result=\"\$(bin/${APP} ping | \$output_filter_command \$output_filter_command_options || :)\"
if __edeliver_node_running; then
echo \"${txtred}already running${txtrst}\" && exit 1
else
__edeliver_synchronous_start
fi
elif [ \"$_is_restart_command\" = \"true\" ]; then
# use stop / start instead of restart command to
# not just restart the applications but also the erlang vm
# with the most recent release
if __edeliver_node_running; then
STOP_OUTPUT=\"\$(${_node_env}bin/${APP} stop ${_config_arg} | \$output_filter_command \$output_filter_command_options)\"
if [ \"\$?\" -ne 0 ]; then
cat \"\$STOP_OUTPUT\"
exit 1
fi
fi
if [ \"$RELEASE_CMD\" = \"mix\" ]; then
__edeliver_synchronous_start
else
${_node_env}bin/${APP} start ${_config_arg} > >(\$output_filter_command \$output_filter_command_options)
fi
else # no restart command
${_node_env}bin/${APP} ${_node_command} ${_config_arg} > >(\$output_filter_command \$output_filter_command_options)
fi
"
fi
}
# starts the deployed release. if release is already running,
# it is restarted.
force_start_release() {
__exec_if_defined "pre_start_deployed_release"
status "Starting deployed release"
__remote "{
$(__get_node_command "restart" "$DELIVER_TO" "")
}"
__exec_if_defined "post_start_deployed_release"
}
# installs a release at all production hosts. if a docker registry is used as
# release store, the start script is extracted from the image.
remote_extract_release_archive() {
local _release_version=${1:-"$VERSION"}
local _archive_dir=${2:-"$DELIVER_TO"}
local _archive="${APP}_${_release_version}.tar.gz"
__exec_if_defined "pre_extract_release_archive"
__detect_release_store_type
if [ "$RELEASE_STORE_TYPE" = "docker" ]; then
_archive_dir="${_archive_dir}/${APP}/bin"
local _image_name="${DOCKER_RUN_IMAGE}:${_release_version}" _release_command="$RELEASE_CMD"
if [ "$_release_command" = "mix" ]; then
if ! [ "$USING_DISTILLERY" = "false" ]; then
_release_command="distillery"
fi
fi
if [ -z "$CONTAINER_START_SCRIPT" ]; then
CONTAINER_START_SCRIPT="/$APP/bin/start_container"
fi
status "Extracting container start script into ${_archive_dir}"
if [ -n "$LINK_VM_ARGS" ]; then
local _sem_ver="${_release_version%-*}" # remove trailing "-latest", "-<revision>" or "-<branch>"
local _vm_args_dest="/${APP}/releases/${_sem_ver}/vm.args"
info "Configure mounting of vm args from $LINK_VM_ARGS to ${_vm_args_dest}"
DOCKER_OPTS+=" --mount type=bind,source="${LINK_VM_ARGS}",target=${_vm_args_dest}"
fi
# read which release command was used from the docker image lables because
# releases built with rebar3 needs a different ERL_DIST_PORT set when starting the remote console
# as releases built with distillery. For distillery it needs to be a random port (0) while
# for rebar3 it needs to be the port the actual server listens on.
_release_command="$(docker inspect --format '{{ index .Config.Labels "edeliver.release.command"}}' "${_image_name}")"
# if release command was not found in image labels, fall back to edeliver configuration if available
if [[ -z "${_release_command// }" ]]; then
_release_command="$RELEASE_CMD"
if [ "$_release_command" = "mix" ]; then
if ! [ "$USING_DISTILLERY" = "false" ]; then
_release_command="distillery"
fi
fi
fi
cat "${BASE_PATH}/docker/start_container" \
| sed "s/{{edeliver-version}}/${_release_version}/g" \
| sed "s\${{edeliver-docker-image}}\$${DOCKER_RUN_IMAGE}\$g" \
| sed "s\${{edeliver-docker-opts}}\$${DOCKER_OPTS}\$g" \
| sed "s\${{edeliver-release-command}}\$${_release_command}\$g" \
| sed "s/{{edeliver-app}}/${APP}/g" \
| __remote "
if [ \"\$0\" = \"/bin/sh\" ]; then
[ -f $PROFILE ] && . $PROFILE
set -e
else
[ -f $PROFILE ] && source $PROFILE
set -e -o pipefail
fi
mkdir -p \"${_archive_dir}\"
cd \"${_archive_dir}\" $SILENCE
docker run --rm \"${_image_name}\" cat \"$CONTAINER_START_SCRIPT\" 2>/dev/null \
| sed \"s/{{edeliver-version}}/${_release_version}/g\" \
| sed \"s\\\${{edeliver-docker-image}}\\\$${DOCKER_RUN_IMAGE}\\\$g\" \
| sed \"s\\\${{edeliver-docker-opts}}\\\$${DOCKER_OPTS}\\\$g\" \
| sed \"s\\\${{edeliver-release-command}}\\\$${_release_command}\\\$g\" \
| sed \"s/{{edeliver-app}}/${APP}/g\" \
> \"$APP\" && [ -s \"$APP\" ] && { # set -o pipefail is not avialble on non-bash shells, so file might be empty
echo \"Extracted script\"
} || {
echo \"Copied edeliver default script\"
echo \"${bldylw}Using edeliver default script.\nPlease consider to add your own version at $CONTAINER_START_SCRIPT.\n${txtrst}\" >&2
cat >\"$APP\"
}
chmod 700 \"$APP\"
" || error "Failed to extract remote start script!"
else
status "Extracting archive ${_archive} into ${_archive_dir}"
# when building releases with mix, the tar does not contain the app name as subdirectory
[[ "$RELEASE_CMD" = "mix" ]] && _archive_dir="${_archive_dir%%/}/${APP}"
__remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd ${_archive_dir} $SILENCE
tar -xzvf ${_archive} $SILENCE && rm ${_archive} $SILENCE"
fi
__exec_if_defined "post_extract_release_archive"
}
# gets the sha hash of the latest commit of the branch to deploy
get_latest_commit() {
__exec "git rev-parse $BRANCH;"
}
# checks the given commit out on the remote build host
git_checkout_remote() {
local _revision=$1
status "Checking out $_revision"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
git checkout $_revision
"
}
# removes the leading and trailing whitespaces for the arg
trim_string() {
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$@"
}
# cleans the build directory on the remote build host.
# by default all generated files are cleaned but that
# can be adjusted by the GIT_CLEAN_PATHS env. This
# function does nothing if SKIP_GIT_CLEAN="true"
git_clean_remote() {
if [[ "$SKIP_GIT_CLEAN" = "true" ]]; then
status "Skipped cleaning generated files from last build"
else
GIT_CLEAN_PATHS=${GIT_CLEAN_PATHS:="."}
status "Cleaning generated files from last build"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $BUILD_AT
echo \"cleaning files in: $GIT_CLEAN_PATHS\"
for _clean_path in ${GIT_CLEAN_PATHS[@]}
do
git clean -ffdx \"\${_clean_path}\"
done
"
fi
}
# renames the release directory on the remote build host
# by adding the release version to the release folder name
rename_release_add_version() {
local _release_description=${1:-"old"}
__detect_remote_release_dir
__detect_remote_release_version
local _release_version=${2:-"$RELEASE_VERSION"}
status "Moving ${_release_description} release to ${APP}_${_release_version}"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $(dirname $RELEASE_DIR)
mv ${APP} ${APP}_${_release_version}
"
}
# renames the release directory on the remote build host
# by removing the release version on the release folder name
rename_release_remove_version() {
__detect_remote_release_dir
local _release_version=${1:-"$RELEASE_VERSION"}
status "Removing version ${_release_version} from remote release directory"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $(dirname $RELEASE_DIR)
mv ${APP}_${_release_version} ${APP}
"
}
# removes the old release archive when building
# upgrades with `mix release`
remove_built_release_archive() {
__detect_remote_release_dir
local _release_version=${1}
local _release_file="${APP}-${_release_version}.tar.gz"
local _release_dir="releases/${_release_version}"
status "Removing built release ${_release_version} from remote release directory"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd \"$(dirname $RELEASE_DIR)/${APP}\"
[[ -f \"$_release_file\" ]] && rm \"$_release_file\"
[[ -n \"$_release_dir\" ]] && [[ -d \"$_release_dir\" ]] && rm -rf \"$_release_dir\"
"
}
# generates the upgrade from the old version to the new version
rebar_generate_appup() {
__detect_remote_release_dir
RELEASE_VERSION="" # redetect new version
__detect_remote_release_version
local _old_version=$1
status "Generating default appup scripts for release ${RELEASE_VERSION}"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
$REBAR_CMD generate-appups previous_release=${APP}_${_old_version}
"
}
# copies the custom appups from the release store to the current release
# on the build host. this overrides previously generated default appups
copy_appups_from_release_store_to_build_host() {
__detect_remote_release_dir
RELEASE_VERSION="" # redetect new version
__detect_remote_release_version
local _old_version=$1
if [ -d "$RELEASE_STORE/appup/${_old_version}-${RELEASE_VERSION}" ]; then
local _num_scripts=$(ls -1 $RELEASE_STORE/appup/${_old_version}-${RELEASE_VERSION}/*.appup | wc -l)
status "Overwriting appups with ${_num_scripts// /} custom scripts from /appup/${_old_version}-${RELEASE_VERSION}/"
for appup_file in $RELEASE_STORE/appup/${_old_version}-${RELEASE_VERSION}/*.appup; do
local _appup_base_name=$(basename $appup_file)
local _appup_app_name=${_appup_base_name%.*}
__exec "echo \"Copying $RELEASE_STORE/appup/${_old_version}-${RELEASE_VERSION}/${_appup_base_name}\" to $BUILD_USER@$BUILD_HOST:${RELEASE_DIR}/lib/${_appup_app_name}*/ebin/${_appup_base_name}"
__exec "scp -o ConnectTimeout=\"$SSH_TIMEOUT\" -p $RELEASE_STORE/appup/${_old_version}-${RELEASE_VERSION}/${_appup_base_name} $BUILD_USER@$BUILD_HOST:${RELEASE_DIR}/lib/${_appup_app_name}*/ebin/${_appup_base_name}"
done
else
status "Using default appup scripts. No custom scripts in /appup/${_old_version}-${RELEASE_VERSION}/"
fi
}
# generates the upgrade from the old version to the new version
rebar_generate_upgrade() {
__detect_remote_release_dir
RELEASE_VERSION="" # redetect new version
__detect_remote_release_version
local _old_version=$1
status "Generating upgrade to version ${RELEASE_VERSION}"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
$REBAR_CMD generate-upgrade previous_release=${APP}_${_old_version}
"
}
# generates the appup files from the old version to the new version
rebar_generate_appup() {
__detect_remote_release_dir
RELEASE_VERSION="" # redetect new version
__detect_remote_release_version
local _old_version=$1
status "Generating appups for version ${RELEASE_VERSION}"
__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO
$REBAR_CMD generate-appups previous_release=${APP}_${_old_version}
"
}
# detects the directory that contains the generated release
# on the remote build host
__detect_remote_release_dir() {
if ! [ -z "$RELEASE_DIR" ]; then
return
else
if [ "$RELEASE_CMD" = "relx" ]; then
local _relx_release_dir_exists
_relx_release_dir_exists=($(__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO $SILENCE
[[ -d ./_rel ]] && echo true || echo false
")) || error "Failed to detect generated release\n"
if [ "$_relx_release_dir_exists" = "true" ]; then
RELEASE_DIR=${DELIVER_TO%%/}/_rel
else
error_message "Failed to detect generated release at\n$APP_USER@$HOSTS:$DELIVER_TO/_rel\n"
exit 1
fi
else # built with rebar, mix or distillery
local _release_directories
if [[ "$RELEASE_CMD" = "mix" && "$USING_DISTILLERY" = "false" ]]; then
_release_file="start_erl.data"
else
_release_file="RELEASES"
fi
_release_directories=($(__sync_remote "
[ -f $PROFILE ] && source $PROFILE
set -e
cd $DELIVER_TO $SILENCE
if [ \"$RELEASE_CMD\" = \"rebar3\" ] || [[ \"$RELEASE_CMD\" = \"mix\" && \"$USING_DISTILLERY\" = \"false\" ]]; then
cd _build
fi
OS_TYPE=\$(uname)
if [[ \"\$OS_TYPE\" = \"Darwin\" ]]; then
find ./ -name $_release_file | cut -b3- | (echo -n "\$PWD" && cat) | grep -E $APP/releases/$_release_file\$
else
find ./ -name $_release_file -exec readlink -f {} \; | grep -E $APP/releases/$_release_file\$
fi
")) || error "Failed to detect generated release\n"
if [ ${#_release_directories[@]} -eq 1 ]; then
RELEASE_DIR=${_release_directories[0]/\/releases\/$_release_file/}
else
error_message "Failed to detect generated release at\n$APP_USER@$HOSTS:$DELIVER_TO\n"
error_message "Please set RELEASE_DIR in the config file to fix that,\nor check that the APP variable is set correctly."
if [ ${#_release_directories[@]} -gt 1 ]; then
hint_message "Maybe one of these directories contains the release:"
for _dir in "${_release_directories[@]}"; do
hint_message " ${_dir/\/releases\/RELEASES/}"
done
fi
exit 1
fi
fi
fi
}
# detects the current version of the generated release
# on the remote build host
__detect_remote_release_version() {
if ! [ -z $RELEASE_VERSION ]; then
return
else
local _releases
_releases=($(__sync_remote "
ls -Ad $RELEASE_DIR/releases/*/
")) || error "Failed to detect release version."
if [ ${#_releases[@]} -eq 1 ]; then
RELEASE_VERSION=`basename ${_releases[0]}`
else
error_message "Failed to detect generated release version at:\n$APP_USER@$HOSTS:$RELEASE_DIR/releases/\n"
if [ ${#_releases[@]} -gt 1 ]; then
hint_message "Detected several releases:"
for _release in "${_releases[@]}"; do
hint_message " $(basename $_release)"
done
error_message "Please rerun with RELEASE_VERSION=x environment variable set."
elif [[ ${#_releases[@]} -eq 0 && -n "$OLD_RELEASE_VERSION" && "$RELEASE_CMD" = "mix" ]]; then
__show_upgrade_version_does_not_differ_error_message
elif [[ ${#_releases[@]} -eq 0 ]]; then
hint_message "No built release found."
fi
exit 1
fi
fi
}
__show_upgrade_version_does_not_differ_error_message() {
error_message "The build upgrade has the same version as the installed version."
error_message "That release cannot be deployed as an upgrade. Alter the release version"
error_message "or consider using the --auto-version=revision|commit-count|date option"
error_message "to automatically increment the version or append the git revision."
error_message "It is recommended to enable the git-auto-revision permanently"
error_message "for automatic upgrades by setting AUTO_VERSION=revision in the config."
}
# detects the type of the release store. sets RELEASE_STORE_TYPE either
# to "local", "remote", "s3" or "docker" and also the following environment variables
# depending on the detected release store type:
#
# AWS_SECRET_ACCESS_KEY (for s3)
# AWS_SECRET_ACCESS_KEY (for s3)
# AWS_BUCKET_NAME (for s3)
#
# DOCKER_RUN_IMAGE (for docker)
__detect_release_store_type() {
if [[ "$RELEASE_STORE" =~ ^[sS]3://[^@]+@[^:]+:.+$ ]]; then
local _s3location=${RELEASE_STORE/#[sS]3:\/\//}
AWS_ACCESS_KEY_ID=${_s3location%%@*}
_s3location=${_s3location##*@}
AWS_SECRET_ACCESS_KEY=${_s3location%%:*}
_s3location=${_s3location##*:}
AWS_BUCKET_NAME=${_s3location%%*:}
fi
if [[ "$RELEASE_STORE" =~ ^[sS]3://[^@]+@[^:]+:.+$ ]] && [[ -n \"${AWS_ACCESS_KEY_ID}\" ]] && [[ -n \"${AWS_SECRET_ACCESS_KEY}\" ]] && [[ -n \"${AWS_BUCKET_NAME}\" ]]; then
RELEASE_STORE_TYPE="s3"
elif [[ "$RELEASE_STORE" =~ ^docker://.+$ ]]; then
RELEASE_STORE_TYPE="docker"
DOCKER_RUN_IMAGE=${RELEASE_STORE/#docker:\/\//}
DOCKER_RELEASE_BASE_IMAGE=${DOCKER_RELEASE_BASE_IMAGE:-edeliver/release-base:1.0}
elif [[ "$RELEASE_STORE" =~ ^[^@]+@[^:]+:.+$ ]]; then
RELEASE_STORE_TYPE="remote"
else
RELEASE_STORE_TYPE="local"
fi
}
# creates a subdirectory or subdirectories on the release store
__create_directory_in_release_store() {
local _directory=$1
# create release store directory if it is a local directory
! [[ "$RELEASE_STORE" =~ .*:.* ]] && __exec "mkdir -p ${RELEASE_STORE}${_directory}"
# TODO: create directories also on remote stores
}
# gets the release version from the release archive name
# $1 = release file name
# $2 = release type (optional)
__release_version_from_archive_name() {
local _release_type=${2}
_release_type=${_release_type:=*}
if [ "$RELEASE_STORE_TYPE" = "docker" ]; then
echo $1
else
local _removed_app_name=${1/${APP}_/}
local _removed_archive_extension=${_removed_app_name/\.tar\.gz/}
local _removed_release_type=${_removed_archive_extension%.${_release_type}}
echo ${_removed_release_type}
fi
}
# copy a file to all remote production hosts in parallel
__parallel_scp() {
local _source_file="$1"
local _destination_dir="$2"
local _hosts="${3:-"$HOSTS_APP_USER"}"
if [ "${MODE}" = "compact" ]; then
local _options=" -q"
else
local _options=""
fi
background_jobs_pids=()
background_jobs=()
__log "${_hosts} : $_remote_job"
for _host in $_hosts
do
if [ "$SKIP_COPYING_EXISTING_FILES" = "skip" ]; then
local _local_md5=$(__local_md5 $_source_file)
local _remote_md5=$(__remote_md5 $_host "$_destination_dir")
if [[ -n "$_local_md5" ]] && [[ -n "$_remote_md5" ]] && [[ "$_local_md5" = "$_remote_md5" ]]; then
__exec "echo \"file \$(basename \$_source_file) already exists on host \$_host (checksum: \$_remote_md5)\""
continue
fi
fi
__exec "echo \"copying $_source_file to $_host:$_destination_dir\""
scp -o ConnectTimeout="$SSH_TIMEOUT" $_options "$_source_file" "$_host:$_destination_dir" &
background_jobs_pids+=("$!")
local _background_job="scp -o ConnectTimeout=$SSH_TIMEOUT $_options $_source_file $_host:$_destination_dir $SILENCE"
background_jobs+=("$_background_job")
done
__monitor_background_jobs
}
# gets the md5 checksum of a release in the local release store
__local_md5() {
local _file_name="$1"
if [ -z "$LOCAL_MD5_COMMAND" ]; then
LOCAL_MD5_COMMAND=$(which md5)
if [ $? -eq 1 ]; then
LOCAL_MD5_COMMAND=$(which md5sum)
[ $? -eq 1 ] && LOCAL_MD5_COMMAND=""
fi
fi
if ! [ -z "$LOCAL_MD5_COMMAND" ]; then
$LOCAL_MD5_COMMAND ${_file_name} | grep -o "[0-9a-fA-F]\{32\}"
else
echo ""
fi
}
# gets the md5 checksum of a release file on the remote host
__remote_md5() {
local _remote_host="$1"
local _remote_file_name="$2"
_remote_job="
if [ -f \"${_remote_file_name}\" ]; then
if [ -z \"\$REMOTE_MD5_COMMAND\" ]; then
REMOTE_MD5_COMMAND=\$(which md5)
if [ \$? -eq 1 ]; then
REMOTE_MD5_COMMAND=\$(which md5sum)
[ \$? -eq 1 ] && LOCAL_MD5_COMMAND=""
fi
fi
if ! [ -z \"\$REMOTE_MD5_COMMAND\" ]; then
\$REMOTE_MD5_COMMAND ${_remote_file_name} | grep -o \"[0-9a-fA-F]\\{32\\}\"
else
echo \"\"
fi
else
echo \"\"
fi
"
ssh -o ConnectTimeout="$SSH_TIMEOUT" "$_remote_host" "$_remote_job"
}
begin() {
case "$COMMAND" in
(build)
local _message="${bldgrn}BUILDING $(upcase "${COMMAND_INFO}")${bldgrn} OF $(upcase "${APP}") APP ON BUILD HOST${txtrst}"
;;
(deploy)
local _host_color="${bldylw}"
[ "$DEPLOY_ENVIRONMENT" = "production" ] && _host_color="${bldred}"
local _message="${bldgrn}DEPLOYING ${bldylw}$(upcase "${COMMAND_INFO}")${bldgrn} OF $(upcase "${APP}") APP TO ${_host_color}$(upcase ${DEPLOY_ENVIRONMENT}) HOSTS${txtrst}"
;;
(*)
local _message="${bldgrn}$(upcase "edeliver ${APP} with $COMMAND command")${txtrst}"
;;
esac
echo -e "\n$_message\n"
__log "$_message"
}
# If we reach this step, delivery was successful
#
finish() {
case "$COMMAND" in
(build)
local _message="${bldgrn}$(upcase "${COMMAND_INFO} BUILD OF $APP WAS SUCCESSFUL!")${txtrst}"
;;
(deploy)
local _message="${bldgrn}DEPLOYED $(upcase "${COMMAND_INFO} TO ${DEPLOY_ENVIRONMENT}!")${txtrst}"
;;
(*)
local _message="${bldgrn}$(upcase "$COMMAND DONE!")${txtrst}"
;;
esac
echo -e "\n$_message\n"
__log "$_message"
__log "===========================================================\n"
}
help() {
[[ -n "$COMMAND_INFO" ]] && local _command="$COMMAND $COMMAND_INFO" || local _command="$COMMAND"
error "\nNo custom help provided for command '$_command'. Try --help option.\n"
}