Skip to content

Instantly share code, notes, and snippets.

@Here-Be-Dragons
Created August 30, 2020 13:41
Show Gist options
  • Save Here-Be-Dragons/3eb87af52e0d99ca240eab71ef7bd5a5 to your computer and use it in GitHub Desktop.
Save Here-Be-Dragons/3eb87af52e0d99ca240eab71ef7bd5a5 to your computer and use it in GitHub Desktop.
Launch AWX job with survey input from Environment Variables
#!/bin/bash
# Kicks off a Job Template, and waits for it to finish.
function LogIt {
LEVEL=$1
MESSAGE=$2
if [ "$LEVEL" = "SUCCESS" ]; then
echo -e "[\033[32m✔\033[0m]\t\033[32m${LEVEL}:\033[0m ${MESSAGE}"
elif [ "$LEVEL" = "INFO" ]; then
echo -e "[•]\t${LEVEL}: ${MESSAGE}"
elif [ "$LEVEL" = "WARN" ]; then
echo -e "[\033[33;1m-\033[0m]\t\033[33;1m${LEVEL}:\033[0m ${MESSAGE}"
elif [ "$LEVEL" = "ERROR" ]; then
echo -e "[\033[31m✘\033[0m]\t\033[31m${LEVEL}:\033[0m ${MESSAGE}"
else
echo -e "[?]\t${LEVEL}: ${MESSAGE}"
fi
}
function GetAssemblyVersion {
if [ -z "${APP_VERSION}" ]; then
LogIt "INFO" "Parsing Build Number for Application Version."
if [[ "$BUILD_BUILDNUMBER" =~ ^(.*)-[0-9]+\.[0-9]+$ ]]; then
LogIt "SUCCESS" "Retrieved Build Number: ${BUILD_BUILDNUMBER}"
APP_VERSION=${BASH_REMATCH[1]}
LogIt "SUCCESS" "Parsed Application Version: ${APP_VERSION}"
elif [[ "$BUILD_BUILDNUMBER" =~ ^([0-9]+\.){2,3}[0-9]+$ ]]; then
LogIt "SUCCESS" "Build Number already in correct format, using as-is."
APP_VERSION=${BUILD_BUILDNUMBER}
LogIt "SUCCESS" "Parsed Application Version: ${APP_VERSION}"
else
LogIt "ERROR" "Unable to get Application Version from Build.BuildNumber variable."
LogIt "ERROR" "Verify \$Build.BuildNumber is of the format ##.##.##.##-##.##, where"
LogIt "ERROR" "## is any length number. The portion before the dash is used as App Version."
exit 1
fi
else
LogIt "INFO" "Application Version provided by APP_VERSION variable: ${APP_VERSION}"
fi
}
function TestConnectivity {
LogIt "INFO" "Verifying connectivity to the AWX Server at ${ANSIBLEHOST}"
QRETURN=$(curl --silent --show-error -i -X GET \
https://${ANSIBLEHOST}/api/v2/ping/)
if [ $? -ne 0 ]; then
LogIt "ERROR" "Connecting to Ansible Failed. Check Server Availability."
LogIt "INFO" "$QRETURN"
exit 1
else
AWX_VERSION=$(grep '{"' <<< "${QRETURN}" | jq '.version')
LogIt "SUCCESS" "AWX connection succeeded."
LogIt "INFO" "AWX Version ${AWX_VERSION} running."
fi
}
function GetAnsibleJobNumber {
if [[ "$TEMPLATE_ID" =~ [0-9]+ ]]; then
LogIt "WARN" "Ansible AWX Job Template ID manually provided as \"${TEMPLATE_ID}\". Skipping Template ID discovery."
LogIt "WARN" "Template IDs usually vary between instances of Ansible AWX. It is much safer to specify"
LogIt "WARN" "\$ANSIBLEJOBNAME and let the script discover the Template ID dynamically."
else
if [ -z "${ANSIBLEJOBNAME}" ]; then
LogIt "ERROR" "Variable \"ANSIBLEJOBNAME\" is not set, cannot continue. Verify you pass the variable to this script and re-run."
exit 1
fi
LogIt "INFO" "Locating Ansible Job Number using Job Name: \"${ANSIBLEJOBNAME}\""
QRETURN=$(curl --silent --show-error -G \
https://${ANSIBLEHOST}/api/v2/job_templates/ --data-urlencode "name=${ANSIBLEJOBNAME}" \
-H "Authorization: Bearer ${ANSIBLEPAT}" \
-H 'Content-Type: application/json' \
-s)
echo "##[debug]${QRETURN}"
TEMPLATE_ID=$(jq '.results[0].id' <<< "${QRETURN}")
TEMPLATE_NAME=$(jq -r '.results[0].name' <<< "${QRETURN}")
if [ "$TEMPLATE_NAME" = "$ANSIBLEJOBNAME" ]; then
LogIt "INFO" "\"${ANSIBLEJOBNAME}\" matches return from Ansible: \"$TEMPLATE_NAME\""
LogIt "SUCCESS" "Ansible Job Template name sanity check successful."
else
LogIt "INFO" "\"${ANSIBLEJOBNAME}\" DOES NOT match return from Ansible: \"$TEMPLATE_NAME\""
LogIt "ERROR" "Ansible Job Template name sanity check FAILED. HALTING DEPLOYMENT."
LogIt "INFO" "Job ID returned: ${TEMPLATE_ID}"
LogIt "INFO" "${QRETURN}"
exit 1
fi
if [[ "$TEMPLATE_ID" =~ [0-9]+ ]]; then
LogIt "SUCCESS" "Ansible AWX Job Template ID found: ${TEMPLATE_ID}"
else
LogIt "ERROR" "Unable to discover Ansible AWX Job Template ID. This is a fatal error, investigation required."
LogIt "INFO" "Template ID returned:"
echo ${QRETURN}
exit 1
fi
fi
}
function GeneratePostData {
postData="{}"
LogIt "INFO" "Gathering Variable Requirements from Ansible AWX"
ansiblevars=$(curl --silent -X GET \
https://${ANSIBLEHOST}/api/v2/job_templates/${TEMPLATE_ID}/survey_spec/ \
-H "Authorization: Bearer ${ANSIBLEPAT}" \
-H 'Content-Type: application/json' \
-s)
if [[ "$ansiblevars" =~ "could not be found" ]]; then
LogIt "ERROR" "Gathering Variable Requirements from Ansible AWX Failed. This is a fatal error, investigation required."
LogIt "INFO" "Return from Ansible AWX:"
echo -e "${ansiblevars}"
exit 1
fi
# If there's no survey, skip populating the JSON Payload. Else, get the values
# we need to continue.
if [ "$ansiblevars" = "{}" ]; then
LogIt "SUCCESS" "No variables needed, continuing without action."
else
ansiblevars=$(echo ${ansiblevars} | jq -r '.spec[].variable')
for i in ${ansiblevars}; do
if [ -z "$(eval echo \$${i})" ]; then
LogIt "ERROR" "Required variable \"$i\" is not set, cannot continue. Exiting, no job created."
exit 1
fi
replacementval=$(eval echo \$${i})
postData=$(echo ${postData} | jq --arg inp1 "$i" --arg inp2 "$replacementval" '.extra_vars += {( $inp1 ) : ( $inp2 )}')
done
LogIt "SUCCESS" "JSON Payload being sent to Ansible AWX:"
echo -e "${postData}"
fi
}
function ExecuteAnsibleJob {
LogIt "INFO" "Handing off execution of deploy to Ansible AWX"
QRETURN=$(curl --silent --show-error -X POST \
https://${ANSIBLEHOST}/api/v2/job_templates/${TEMPLATE_ID}/launch/ \
-H "Authorization: Bearer ${ANSIBLEPAT}" \
-H 'Content-Type: application/json' \
-d "${postData}" \
-s)
if [ $? -ne 0 ]; then
LogIt "ERROR" "Attempt to start job failed. Failing request, investigation requred."
LogIt "INFO" "${QRETURN}"
exit 1
fi
JOB_ID=$(jq '.id' <<< "${QRETURN}" )
if [[ "$JOB_ID" =~ [0-9]+ ]]; then
LogIt "SUCCESS" "Ansible job created: ${JOB_ID}"
else
LogIt "ERROR" "Ansible job not created. Exiting, failed."
echo -e "\tJSON Payload:"
echo -e "\t${postData}"
echo -e "\tReturn:"
echo -e "\t${QRETURN}"
exit 1
fi
}
function GetStdOut {
LogIt "INFO" "STDOUT:"
ANSIBLESTDOUT=$(curl --silent -X GET \
"https://${ANSIBLEHOST}/api/v2/jobs/${JOB_ID}/stdout/?format=ansi" \
-H "Authorization: Bearer ${ANSIBLEPAT}" \
-H 'Content-Type: application/json')
echo -e "${ANSIBLESTDOUT}"
if [[ "$ANSIBLESTDOUT" =~ "no hosts matched" ]]; then
LogIt "ERROR" "Job execution did not match any hosts, invalid job execution."
LogIt "ERROR" "Validate \"hosts:\" line matches inventory values"
exit 1
fi
}
function AwaitJobCompletion {
LogIt "INFO" "Waiting for job to complete."
FINISHED=null
STATUS='"pending"'
while [ "$FINISHED" = 'null' ]; do
LogIt "INFO" "Current Status: ${STATUS}. Sleeping for 5 seconds. 💤"
sleep 5
QRESULT=$(curl --silent -X GET \
https://${ANSIBLEHOST}/api/v2/jobs/${JOB_ID}/ \
-H "Authorization: Bearer ${ANSIBLEPAT}" \
-H 'Content-Type: application/json')
STATUS=$(jq '.status' <<< ${QRESULT})
FINISHED=$(jq '.finished' <<< ${QRESULT})
FAILED=$(jq '.failed' <<< ${QRESULT})
done
if [ "$FAILED" = 'false' ]; then
LogIt "SUCCESS" "Ansible Job ${JOB_ID} reports success. Job URL:"
echo -e "\thttps://${ANSIBLEHOST}/#/jobs/playbook/${JOB_ID}"
GetStdOut
elif [ "$FAILED" = 'true' ]; then
LogIt "ERROR" "Ansible Job ${JOB_ID} reports ${STATUS}. Check the following URL for more details."
echo -e "\thttps://${ANSIBLEHOST}/#/jobs/playbook/${JOB_ID}"
GetStdOut
exit 1
else
LogIt "ERROR" "An unexpected return value was recieved. Please investigate."
echo -e "\thttps://${ANSIBLEHOST}/#/jobs/playbook/${JOB_ID}"
echo -e "${QRESULT}"
fi
}
# Release starts here
GetAssemblyVersion
LogIt "INFO" "Configured settings:"
LogIt "INFO" "ANSIBLEHOST: ${ANSIBLEHOST}"
LogIt "INFO" "TEMPLATE_ID (Discovered if missing): ${TEMPLATE_ID}"
LogIt "INFO" "ANSIBLEJOBNAME: ${ANSIBLEJOBNAME}"
TestConnectivity
GetAnsibleJobNumber
GeneratePostData
ExecuteAnsibleJob
AwaitJobCompletion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment