-
-
Save anniethiessen/b046f013229b9448e94bf9e834c04864 to your computer and use it in GitHub Desktop.
| #!/usr/bin/env bash | |
| : ' | |
| Script file to prepare and deploy AWS ElasticBeanstalk applications. | |
| Ensure AWS EB CLI is installed and configured, | |
| define script variables, then execute script. | |
| First argument must be ElasticBeanstalk environment name, | |
| secondly optional option -c <configuration>, and | |
| all remaining options are passed to 'eb deploy' command. | |
| Accepts -v (verbose output) and -q (quiet output) arguments. | |
| Prerequisites: | |
| - awsebcli 3.20.10 -> https://pypi.org/project/awscli/ | |
| Script Variables: | |
| - CONDA_ENV: The conda environment that has required packages installed. | |
| Optional, if current environment has them installed. | |
| - APPLICATION_PATH: The path to the application root. | |
| Should be set if this script file is not in application root. | |
| Optional, defaults to null. | |
| - PERFORM_PREPARE: Whether to perform the ""prepare"" step. | |
| The ""prepare"" function prepares config files | |
| by copying them into application root from '.elasticbeanstalk/env_configs/env_name/'. | |
| This is useful if different environments have unique configurations. | |
| Should only be set to "false" if configuration files are already in their directories | |
| and in this case ""PERFORM_CLEAN"" should also be set to "false". | |
| Optional, defaults to "true". | |
| - PERFORM_DEPLOY: Whether to perform the ""deploy"" step. | |
| The ""deploy"" function deploys application to ElasticBeanstalk. | |
| Optional, defaults to "true". | |
| - PERFORM_CLEAN: Whether to perform the ""clean"" step. | |
| The ""clean"" function deletes configuration files copied in the ""prepare"" step. | |
| Should only be set to "false" if ""PERFORM_PREPARE"" is set to "false" | |
| Optional, defaults to "true". | |
| Usage Example: | |
| CONDA_ENV=xxx | |
| APPLICATION_PATH=xxx | |
| wget -cO - "<this_file_url>" > "temp.sh" | |
| chmod +x "temp.sh" | |
| source "temp.sh" "$@" | |
| rm -rf "temp.sh" | |
| ' | |
| #-------------------------------------------- | |
| #----------------- CONSTANTS ---------------- | |
| #-------------------------------------------- | |
| CONDA_ENV="${CONDA_ENV}" | |
| APPLICATION_PATH="${APPLICATION_PATH}" | |
| PERFORM_PREPARE=${PERFORM_PREPARE:-true} | |
| PERFORM_DEPLOY=${PERFORM_DEPLOY:-true} | |
| PERFORM_CLEAN=${PERFORM_CLEAN:-true} | |
| #------- DO NOT EDIT BELOW THIS LINE -------- | |
| FORMAT_SCRIPT_URL="https://gist.githubusercontent.com/anniethiessen/efb6bc0e52ccfc8b330aa41364b53e97/raw/0012edc1f009a36d196f03f09fda68e70691860b/shell_script_essentials.sh" | |
| FORMAT_SCRIPT_NAME="shell_script_essentials.sh" | |
| #-------------------------------------------- | |
| #--------------- FUNCTIONS ----------------- | |
| #-------------------------------------------- | |
| function set_opts { | |
| if [ $# -eq 0 ]; then | |
| echo "ElasticBeanstalk environment not specified." ${PROMPT_VERBOSE} | |
| exit 1 | |
| fi | |
| ENV=$1; shift; | |
| while getopts ":c:qv" flag; do | |
| case "${flag}" in | |
| c) CONFIG=${OPTARG}; shift 2;; | |
| q) OUTPUT_FLAG="-q"; shift;; | |
| v) OUTPUT_FLAG="-v"; shift;; | |
| *) | |
| esac | |
| done | |
| CONFIG="${CONFIG:-${ENV}}" | |
| OUTPUT_FLAG="${OUTPUT_FLAG:--q}" | |
| EB_OPTS=("$@") | |
| WORKING_PATH=$( pwd ) | |
| if [[ "${APPLICATION_PATH: -1}" == "/" ]]; then APPLICATION_PATH="${APPLICATION_PATH%?}"; fi | |
| if [[ "${APPLICATION_PATH:0:1}" == "/" ]]; then APPLICATION_PATH="${APPLICATION_PATH:1}"; fi | |
| if [[ -z "${APPLICATION_PATH}" ]]; then | |
| APPLICATION_PATH="${WORKING_PATH}" | |
| else APPLICATION_PATH="${WORKING_PATH}/${APPLICATION_PATH}"; fi | |
| } | |
| function activate_env { | |
| local retval | |
| output_info_message "Activating local environment '${CONDA_ENV}'." | |
| if [ -n "${CONDA_ENV}" ]; then | |
| eval "$(conda shell.bash hook)" | |
| conda activate "${CONDA_ENV}" | |
| fi | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "Local environment activated." | |
| else | |
| output_error_message "Local environment activation error." ${PROMPT_VERBOSE} | |
| exit_script | |
| fi | |
| if [[ "${OUTPUT}" == "${VERBOSE_OUTPUT}" ]]; then | |
| PYTHON_VERSION=$( python --version 2>&1 ) | |
| AWSEBCLI_VERSION=$( eb --version 2>&1 ) | |
| output_warning_message "Using ${PYTHON_VERSION} and ${AWSEBCLI_VERSION}" | |
| fi | |
| } | |
| function run_format_script { | |
| wget -cO - ${FORMAT_SCRIPT_URL} > ${FORMAT_SCRIPT_NAME} | |
| chmod +x ${FORMAT_SCRIPT_NAME} | |
| source ${FORMAT_SCRIPT_NAME} "$@" | |
| rm -rf ${FORMAT_SCRIPT_NAME} | |
| } | |
| function cancel_abort_clean_exit_trap { | |
| trap '' EXIT | |
| output_warning_message "Deployment to ElasticBeanstalk environment '${ENV}' cancelled." | |
| abort | |
| clean | |
| exit_script | |
| } | |
| function cancel_clean_trap { | |
| trap '' EXIT | |
| output_warning_message "Deployment to ElasticBeanstalk environment '${ENV}' cancelled." | |
| clean | |
| } | |
| function check_status { | |
| local ret status | |
| cd "${APPLICATION_PATH}" | |
| ret=$( eb status "${ENV}" ) | |
| if [[ "${ret}" == "ERROR: NotFoundError - Environment \"${ENV}\" not Found." ]]; then | |
| status="Not Found" | |
| else | |
| status=$( awk -F"Status: " '{print $2}' <<< "${ret}" ) | |
| status=$( echo "${status##*$'\n'}" ) | |
| status=$( sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "${status}" ) | |
| fi | |
| cd "${WORKING_PATH}" | |
| echo "${status}" | |
| } | |
| function abort { | |
| cd "${APPLICATION_PATH}" | |
| status=$( check_status ) | |
| if [[ "${status}" == "Updating" ]]; then | |
| output_warning_message "Aborting deployment to ElasticBeanstalk environment '${ENV}'." | |
| eb abort "${ENV}" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "Deployment aborted." | |
| else | |
| output_error_message "Abort deployment error." ${PROMPT_VERBOSE} | |
| fi | |
| fi | |
| cd "${WORKING_PATH}" | |
| } | |
| function prepare { | |
| if [ "${PERFORM_PREPARE}" == false ]; then | |
| output_warning_message "Preparation skipped" | |
| return | |
| fi | |
| local retval | |
| output_info_message "Copying config files '${CONFIG}'." | |
| cd "${APPLICATION_PATH}" | |
| trap cancel_clean_trap EXIT | |
| if [ -d ".elasticbeanstalk/env_configs/${CONFIG}/.ebextensions/" ]; then | |
| output_info_message "Copying '.ebextensions' config files from '${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.ebextensions/'." | |
| cp -R ".elasticbeanstalk/env_configs/${CONFIG}/.ebextensions/." "${APPLICATION_PATH}/.ebextensions/" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'.ebextensions' config files copied." | |
| else | |
| output_error_message "'.ebextensions' config file copy error." ${PROMPT_VERBOSE} | |
| exit_script | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.ebextensions/' directory does not exist, no files copied." | |
| fi | |
| if [ -d ".elasticbeanstalk/env_configs/${CONFIG}/.platform/" ]; then | |
| output_info_message "Copying '.platform' config files from '${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.platform/'." | |
| cp -R ".elasticbeanstalk/env_configs/${CONFIG}/.platform/." "${APPLICATION_PATH}/.platform/" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'.platform' config files copied." | |
| else | |
| output_error_message "'.platform' config file copy error." ${PROMPT_VERBOSE} | |
| exit_script | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.platform/' directory does not exist, no files copied." | |
| fi | |
| if [ -f ".elasticbeanstalk/env_configs/${CONFIG}/env.yaml" ]; then | |
| output_info_message "Copying 'env' config file '${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/env.yaml'." | |
| cp ".elasticbeanstalk/env_configs/${CONFIG}/env.yaml" "${APPLICATION_PATH}/env.yaml" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'env.yaml' config file copied." | |
| else | |
| output_error_message "'env.yaml' config file copy error." ${PROMPT_VERBOSE} | |
| exit_script | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/env.yaml' file does not exist, no file copied." | |
| fi | |
| if [ -f ".elasticbeanstalk/env_configs/${CONFIG}/requirements.txt" ]; then | |
| output_info_message "Copying 'requirements' config file '${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/requirements.txt'." | |
| cp ".elasticbeanstalk/env_configs/${CONFIG}/requirements.txt" "${APPLICATION_PATH}/requirements.txt" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'requirements.txt' config file copied." | |
| else | |
| output_error_message "'requirements.txt' config file copy error." ${PROMPT_VERBOSE} | |
| exit_script | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/requirements.txt' file does not exist, no file copied." | |
| fi | |
| if [ -f ".elasticbeanstalk/env_configs/${CONFIG}/.ebignore" ]; then | |
| output_info_message "Copying 'ebignore' config file '${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.ebignore'." | |
| cp ".elasticbeanstalk/env_configs/${CONFIG}/.ebignore" "${APPLICATION_PATH}/.ebignore" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'.ebignore' config file copied." | |
| else | |
| output_error_message "'.ebignore' config file copy error." ${PROMPT_VERBOSE} | |
| exit_script | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.ebignore' file does not exist, no file copied." | |
| fi | |
| cd "${WORKING_PATH}" | |
| } | |
| function deploy { | |
| if [ "${PERFORM_DEPLOY}" == false ]; then | |
| output_warning_message "Deploy skipped" | |
| return | |
| fi | |
| local rv retval create | |
| output_info_message "Deploying to ElasticBeanstalk environment '${ENV}'" | |
| trap cancel_clean_trap EXIT | |
| status=$( check_status ) | |
| if [[ "${status}" == "Ready" ]]; then | |
| output_info_message "ElasticBeanstalk environment '${ENV}' ready." | |
| create=false | |
| elif [[ "${status}" == "Not Found" ]]; then | |
| output_info_message "ElasticBeanstalk environment '${ENV}' will be created." | |
| create=true | |
| else | |
| output_error_message "ElasticBeanstalk environment '${ENV}' not ready, status is '${status}'." | |
| exit_script | |
| fi | |
| cd "${APPLICATION_PATH}" | |
| trap cancel_abort_clean_exit_trap SIGINT | |
| if [[ "${create}" == true ]]; then | |
| output_info_message "Running 'create' command with options:" | |
| output_info_message "${EB_OPTS[*]}" | |
| eb create "${ENV}" "${EB_OPTS[@]}" &> ${OUTPUT} | |
| else | |
| output_info_message "Running 'deploy' command with options:" | |
| output_info_message "${EB_OPTS[*]}" | |
| eb deploy "${ENV}" "${EB_OPTS[@]}" &> ${OUTPUT} | |
| fi | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "Application deployed." | |
| else | |
| output_error_message "Application deployment error." ${PROMPT_VERBOSE} | |
| exit_script | |
| fi | |
| cd "${WORKING_PATH}" | |
| } | |
| function clean { | |
| if [ "${PERFORM_CLEAN}" == false ]; then | |
| output_warning_message "Clean skipped" | |
| return | |
| fi | |
| local retval | |
| output_info_message "Deleting artifact config files." | |
| trap '' EXIT | |
| cd "${APPLICATION_PATH}" | |
| if [ -d ".elasticbeanstalk/env_configs/${CONFIG}/.ebextensions/" ]; then | |
| output_info_message "Deleting '.ebextensions' artifact config files in '${APPLICATION_PATH}/.ebextensions/'." | |
| rm -rf ".ebextensions/" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'.ebextensions' artifact config files deleted." | |
| else | |
| output_error_message "'.ebextensions' artifact config file delete error." ${PROMPT_VERBOSE} | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.ebextensions/' directory does not exist, no files deleted." | |
| fi | |
| if [ -d ".elasticbeanstalk/env_configs/${CONFIG}/.platform/" ]; then | |
| output_info_message "Deleting '.platform' artifact config files in '${APPLICATION_PATH}/.platform/'." | |
| rm -rf ".platform/" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'.platform' artifact config files deleted." | |
| else | |
| output_error_message "'.platform' artifact config file delete error." ${PROMPT_VERBOSE} | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.platform/' directory does not exist, no files deleted." | |
| fi | |
| if [ -f ".elasticbeanstalk/env_configs/${CONFIG}/env.yaml" ]; then | |
| output_info_message "Deleting 'env.yaml' artifact config file '${APPLICATION_PATH}/env.yaml'." | |
| rm -f "env.yaml" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'env.yaml' artifact config file deleted." | |
| else | |
| output_error_message "'env.yaml' artifact config file delete error." ${PROMPT_VERBOSE} | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/env.yaml' file does not exist, file not deleted." | |
| fi | |
| if [ -f ".elasticbeanstalk/env_configs/${CONFIG}/requirements.txt" ]; then | |
| output_info_message "Deleting 'requirements.txt' artifact config file '${APPLICATION_PATH}/requirements.txt'." | |
| rm -f "requirements.txt" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'requirements.txt' artifact config file deleted." | |
| else | |
| output_error_message "'requirements.txt' artifact config file delete error." ${PROMPT_VERBOSE} | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/requirements.txt' file does not exist, file not deleted." | |
| fi | |
| if [ -f ".elasticbeanstalk/env_configs/${CONFIG}/.ebignore" ]; then | |
| output_info_message "Deleting 'ebignore' artifact config file '${APPLICATION_PATH}/.ebignore'." | |
| rm -f ".ebignore" &> ${OUTPUT} | |
| retval=$? | |
| if [[ ${retval} -eq 0 ]]; then | |
| output_success_message "'.ebignore' artifact config file deleted." | |
| else | |
| output_error_message "'.ebignore' artifact config file delete error." ${PROMPT_VERBOSE} | |
| fi | |
| else | |
| output_warning_message "'${APPLICATION_PATH}/.elasticbeanstalk/env_configs/${CONFIG}/.ebignore' file does not exist, file not deleted." | |
| fi | |
| cd "${WORKING_PATH}" | |
| } | |
| #-------------------------------------------- | |
| #------------------ MAIN -------------------- | |
| #-------------------------------------------- | |
| set_opts "$@" | |
| run_format_script ${OUTPUT_FLAG} | |
| activate_env | |
| output_header_message "----------------------------------------" | |
| output_header_message "[1/3] PREPARE" | |
| output_header_message "preparing config artifact files ..." | |
| output_header_message "----------------------------------------" | |
| if [ "${PERFORM_PREPARE}" == true ] ; then | |
| prepare | |
| else output_warning_message "Preparation skipped"; fi | |
| output_header_message "----------------------------------------" | |
| output_header_message "[2/3] DEPLOY" | |
| output_header_message "deploying application to ElasticBeanstalk ..." | |
| output_header_message "----------------------------------------" | |
| if [ "${PERFORM_DEPLOY}" == true ] ; then | |
| deploy | |
| else output_warning_message "Deploy skipped"; fi | |
| output_header_message "----------------------------------------" | |
| output_header_message "[3/3] CLEAN" | |
| output_header_message "cleaning config artifact files ..." | |
| output_header_message "----------------------------------------" | |
| if [ "${PERFORM_CLEAN}" == true ] ; then | |
| clean | |
| else output_warning_message "Clean skipped"; fi |
Raw URLs:
TODO:
-implement config file with STOML (see CloudFormation script)
Change Log:
v1: Initial
v2:
-feat: added CONDA_ENV variable: Conda environment is activated if defined (fixes v1 issue #1)
-fix: removed debug 'ls -a' command in ""deploy"" function (fixes v1 issue #2)
-fix: updated "Application Deployed" from warning message to success message (fixes v1 issue #3)
-fix: fixed incorrect directory in clean function (fixes v1 issue #4)
v3:
-update: "APPLICATION_PATH" can be null and include forward-slashes
-update: each function changes to required directory, in case only some functions are called
-fix: abort and clean trap on ctrl-c
-add: clean trap on exit
-update: check "PERFORM_*" variables inside functions in case they are called from other functions
-feat: add "activate_env" function
v4:
-feat: added create command if env does not exist
-reorganized config file prep & clean to .elasticbeanstalk/env_configs/
-add env.yaml and requirements.txt to config file prep and clean
v5:
-feat: ebignore file
Known Issues:
v1:
-(#1) Breaking: Deployment not working due to EB error "zsh: /usr/local/bin/eb: bad interpreter: /usr/local/opt/python/bin/python3.7: no such file or directory" -- temporary solution: skip deploy step, run with only prepare step, manually deploy, then run again with only clean step (fixed in v2)
-(#2) Bug: Deploy step runs debug
ls -a(fixed in v2)-(#3) Bug: "Application Deployed" prints as warning message (fixed in v2)
-(#4) Breaking: If deployment does work, clean step does not, due to
cdcommand