20
#!/bin/bash without loosing your sleep

#!/bin/bash without loosing your sleep

Embed Size (px)

DESCRIPTION

Operators are used to scripting, but learning bash can be confusing for developers. It starts out by putting a few commands in sequence and running it. Immediately you experience the power of the shell, and you want to do more. That's when you may experience that your scripts become a nightmare, and I am sure operators have experienced that to. When developers get more experienced in scripting they miss the set of tools and techniques they are used to from the developers world, and they start finding ways to incorporate them in bash scripting. And when they do, their scripts become a lot more manageable and less error-prone. Things like functions, unit tests, organising of code files, version control and logging frameworks enhances the quality, maintainability, readability, reusability, cleanliness, changeability, testability, security and ease of use. In this talk I will explain problems you may encounter when scripting bash and how to cope with them. I will show how techniques and tools from the programming world may be interesting to people coming from the operators world, and what developers need to learn from the operators world to become effective bash programmers.

Citation preview

Page 1: #!/bin/bash without loosing your sleep

#!/bin/bash without loosing your sleep

Page 2: #!/bin/bash without loosing your sleep
Page 3: #!/bin/bash without loosing your sleep
Page 4: #!/bin/bash without loosing your sleep
Page 5: #!/bin/bash without loosing your sleep

#!/bin/bash# Usage: deploy.sh <artifact> <version>

artifact=$1version=$2

wget https://nexus.bekk.no/${artifact}/${version}/${artifact}-${version}.zip

unzip ${artifact}.zip

/etc/init.d/${artifact} stop

rm ${artifact} # softlink

ln -s ${artifact}-${version} ${artifact}

/etc/init.d/${artifact} start

Page 6: #!/bin/bash without loosing your sleep

#!/bin/bash# Usage: rollback.sh <artifact> <version>

artifact=$1version=$2

/etc/init.d/${artifact} stop

rm ${artifact} # softlink

ln -s ${artifact}-${version} ${artifact}

/etc/init.d/${artifact} start

Page 7: #!/bin/bash without loosing your sleep

includes=( "../include/common_functions.sh" "../include/common_config.sh" )

for include in ${includes[@]}do if [ -f ${include} ]; then . ${include} else _fatal "File ${include} not found. Quitting! :-(" fidone

Page 8: #!/bin/bash without loosing your sleep

_is_snapshot() { [[ "${1}" =~ ^[0-9]+(\.[0-9]+)+-SNAPSHOT$ ]] && return 0 || return 1}

_run() { local cmd=${1} _info "Running: ${cmd}" if [ $? -ne 0 ]; then _fatal "${cmd} failed! Please retry the command manually." fi}

_mwget() { local wget="wget -nd -nH -r -l1 --no-parent -A \"${1}\" ${2}" _info "Running: ${wget}" eval $wget return $?}

Page 9: #!/bin/bash without loosing your sleep

_ms() { local S=${1} ((m=S%3600/60)) ((s=S%60)) printf "%dm:%ds" $m $s}

expected=10m:15s

val=$(_ms 615) && retval=$? || retval=$? && [[ "${val}" == "${expected}" ]] \&& _info "test passed! (retval=${retval})" \|| _fatal "test failed! Expected \"${expected}\" but was \"$val\" (retval=${retval})"exit 0

Page 10: #!/bin/bash without loosing your sleep

_assertEquals() { local arguments=( "$@" ) local expected="${1}" local actual="${2}" local msg="${arguments[@]:2}" [[ "${expected}" == "${actual}" ]] \ && _info "test passed! (value=${actual}) ${msg}" \ || _fatal "test failed! Expected value ${expected} but was ${actual} ${msg}" [[ $1 =~ "^[0-9]+$" ]] && return ${actual} || return 0}

HOSTNAME="node1"expected="8080"actual=$( _getWebServerPort )_assertEquals 0 $?_assertEquals ${expected} ${actual} "Test: _getWebServerPort when server is ${HOSTNAME}"

HOSTNAME="no_such_server"expected="UNDEFINED"actual=$( _getWebServerPort )_assertEquals 1 $?_assertEquals ${expected} ${actual} "Test: _getWebServerPort when server is nonexistent"

Page 11: #!/bin/bash without loosing your sleep

# Syntax check echo "Running syntax check ..."scripts=$(ls -1 *.sh ../include/*.sh ../scripts/*.sh)

for script in ${scripts[@]}do echo "checking ${script}" bash -n ${script} || exit 1doneecho "Syntax check finished ..."

# Testsecho "Running tests ..."tests=$(ls -1 ../tests/test*.sh )

for test in ${tests[@]}do echo "running ${test}" ${test} || exit 1doneecho "Tests finished ..."exit 0

Page 12: #!/bin/bash without loosing your sleep
Page 13: #!/bin/bash without loosing your sleep

...13 if [ "${version}" != "rollback" ] && [ ${#artifactsAndVersions[@]} -ne 0 ]; then14 if ( _contains ${targets[@]} ${host} ) || ( _is_snapshot ${deployVersion} ) ; 15 then16 deployFile="${M2_REPO}/no/posten/dpost/deploy/${deployVersion}/deploy-${deployVersion}.zip"17 if [ ! -s "${deployFile}" ]; then18 echo "mkdir -p ${M2_REPO}/no/posten/dpost/deploy/${deployVersion}/"19 if ( _is_snapshot ${deployVersion} ); then20 if [ -s ~/deploy-${deployVersion}.zip ]; then21 _debug "Found deploy-${deployVersion}.zip in homedir. Moving it to ${deployFile}."22 _run "echo ~/deploy-${deployVersion}.zip ${deployFile}"23 if [ ! -s ~/deploy-${deployVersion}.zip ]; then24 _fatal "You are trying to deploy a SNAPSHOT version, but it is not installed"25 fi26 else27 _fatal "Could not find deploy-${deployVersion}.zip at ${deployFile}."28 fi29 [[ -e ${deployFile} ]] || _fatal "${deployFile} not found locally or in Nexus! Exiting!"30 fi31 artifactFilesToUpload+=( "${deployFile}" )32 else33 installDeployCmds=( "wget -O deploy-${deployVersion}.zip ${NEXUS_URL}deploy-${deployVersion}.zip" )34 fi35 fi

/Users/stein/deploy.sh: line 35: syntax error: unexpected end of file

Page 14: #!/bin/bash without loosing your sleep

debug="true"

_run_ssh() { local -a servers=( "${!1}" ) local cmd=${2} for server in ${servers[@]} do local remote_cmd="ssh -tt ${server} \"${cmd}\"" _info "Running: ${remote_cmd}" test "${debug}" == "true" && debug_cmds+=( "${remote_cmd}" ) || eval ${remote_cmd} if [ $? -ne 0 ]; then _fatal "${remote_cmd} failed! Please retry the command(s) on the remote server(s)." fi done test "${debug}" == "true" && echo "${debug_cmds[@]}"}

Page 15: #!/bin/bash without loosing your sleep

_info() { echo -e 1>&2 "\033[32m-->" $@ "\033[0m" # green}

_debug() { test "${debug}" == "true" && echo -e 1>&2 "\033[34m-->" $@ "\033[0m" # purple}

_error() { echo -e 1>&2 "\033[31m-->" $@ "\033[0m" # red}

_fatal() { echo -e 1>&2 "\033[31m-->" $@ "\033[0m" # red exit 2}

_happyQuit() { echo -e 1>&2 "\033[36m-->" $@ "\033[0m" # blue exit 0}

Page 16: #!/bin/bash without loosing your sleep

_init_log() { local timestamp=`date +%Y%m%d` logs="./logs" log_filename="${1}" if [ ! -d ${logs} ]; then mkdir $logs fi find $logs -name ${log_filename} -type f -size +512k | while read logfile do echo $logfile local newlogfile=$logfile.$timestamp cp $logfile $newlogfile cat /dev/null > $logfile gzip -f -9 $newlogfile done find $logs -name "${log_filename}*.gz" -type f -mtime 30 |xargs rm -f}

Page 17: #!/bin/bash without loosing your sleep

_run_with_rollback_if_fail() { _info "Running ${1} ..." ${1} response=$? if [ $response -ne 0 ]; then _rollback "${previous_version_directory}" "${home}/${artifact}-${version}" fi}

Page 18: #!/bin/bash without loosing your sleep

_rollback() { local rollback_to="${1}" local rollback_from="${2}" cd ${home} _info "Rolling back from ${rollback_from} to ${rollback_to}" if [ -d "${rollback_to}" ]; then _stop if [ -h "${artifact}" ]; then _delete ${artifact} fi if [ -d "${rollback_from}" ]; then _delete ${rollback_from} fi mv ${rollback_to} ${home} ln -s ${artifact}-${version} ${artifact} if ( _start ); then _info "Rollback to ${rollback_to} successful." else _fatal "Could not start ${artifact}. Rollback to ${rollback_to} failed!" fi else _error "File ${rollback_to} not found. Rollback failed!" if [ -d "${rollback_from}" ] && [ -h "${artifact}" ]; then _start else _fatal "No executable versions of ${artifact} exists!" fi fi}

Page 19: #!/bin/bash without loosing your sleep
Page 20: #!/bin/bash without loosing your sleep

THANK YOU!

Stein Inge Morisbak

Practice lead Continuous Delivery and DevOps @ BEKK