I'm using a bash script to create an AWS instance via CLI and a cloudformation template. I want my script to wait until the instance creation is complete before I move on in my script. Right now, I'm using a while loop to "describe-stacks" every 5 seconds, and breaking out of the loop when the status = "CREATE_COMPLETE" or some failure status. Does anyone know of a more elegant way to do this?
stackStatus="CREATE_IN_PROGRESS"
while [[ 1 ]]; do
echo "${AWS_CLI_PATH}" cloudformation describe-stacks --region "${CfnStackRegion}" --stack-name "${CfnStackName}"
response=$("${AWS_CLI_PATH}" cloudformation describe-stacks --region "${CfnStackRegion}" --stack-name "${CfnStackName}" 2>&1)
responseOrig="$response"
response=$(echo "$response" | tr '\n' ' ' | tr -s " " | sed -e 's/^ *//' -e 's/ *$//')
if [[ "$response" != *"StackStatus"* ]]
then
echo "Error occurred creating AWS CloudFormation stack. Error:"
echo " $responseOrig"
exit -1
fi
stackStatus=$(echo $response | sed -e 's/^.*"StackStatus"[ ]*:[ ]*"//' -e 's/".*//')
echo " StackStatus: $stackStatus"
if [[ "$stackStatus" == "ROLLBACK_IN_PROGRESS" ]] || [[ "$stackStatus" == "ROLLBACK_COMPLETE" ]] || [[ "$stackStatus" == "DELETE_IN_PROGRESS" ]] || [[ "$stackStatus" == "DELETE_COMPLETE" ]]; then
echo "Error occurred creating AWS CloudFormation stack and returned status code ROLLBACK_IN_PROGRESS. Details:"
echo "$responseOrig"
exit -1
elif [[ "$stackStatus" == "CREATE_COMPLETE" ]]; then
break
fi
# Sleep for 5 seconds, if stack creation in progress
sleep 5
done
The aws cli provides a wait subcommand for most of the commands that create resources. For your scenario, you can use the wait subcommand to wait for the stack-create-complete event:
aws cloudformation wait stack-create-complete --stack-name myStackName
That is how I did it. After start of instance I wait for public IP:
INSTANCE_ID="$(aws ec2 run-instances --cli-input-json "${LAUNCH_SPEC}" | jq -r '.Instances[0].InstanceId')"
echo "Instance id ${INSTANCE_ID}"
while true; do
PUBLIC_IP="$(aws ec2 describe-instances --instance-ids ${INSTANCE_ID} | jq -r '.Reservations[0].Instances[0].PublicIpAddress')"
if [[ "${PUBLIC_IP}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then break; fi
sleep 1
echo -n '.'
done
LAUNCH_SPEC defined previously
Ec2-wait-instance-exists seems like what you need:
http://docs.aws.amazon.com/cli/latest/reference/ec2/wait/instance-exists.html
The below is a general "check_status" function. Useful for multiple action such as checking whether stack has been deployed or an EKS cluster is up and whether nodes were added to it.
check_status() {
max_tries="$1"
test_command="$2"
required_value="$3"
error="$4"
ok="false"
for i in `seq 1 $max_tries`; do
return_value=$(eval ${test_command})
if [ "$return_value" == "$required_value" ]; then
ok="true"
break
else
echo -n "."
fi
sleep 5
done
if [ "$ok" == "false" ]; then
printf "\n\e[31mERROR:\e[0m $error\n"
exit 1
fi
}
check_vpc() {
echo "Waiting for stack status..."
vpc_stack_status="aws cloudformation describe-stacks --stack-
name=${vpc_stack_name} --query 'Stacks[0].StackStatus' --output text"
msg="Stack creation failed - giving up"
check_status "100" "$vpc_stack_status" "CREATE_COMPLETE" "$msg"
[[ $? == "0" ]] && echo "VPC stack deployed successfully"
}
Related
I am new to bash scripting, I am writing a script that will deploy a new artifact to AWS Elasticbeanstalk, rather than going to AWS UI, and developers taking a long time. please see below and let me know if I am doing anything wrong. I am worried about this part:
if [ "$1" = "help" ] HELP <<EOF
then
read -r -d ''
Usage:
\t$(basename $0) list - list all applications
\t$(basename $0) deploy - deploy artifact to an environment
EOF
die "$HELP"
exit 0
fi
Running this command to run the script:
AWS_PROFILE=default ARTIFACT_BUCKET=myawsstudybucket ARTIFACT_NAME=artifact1.zip ./deploy.sh deploy demo-app Demoapp-env artifact.zip
#!/bin/bash
PROFILE=$(aws sts get-caller-identity --query Account)
RED='\033[0;31m'
COLOR_OFF='\033[0m'
if [ -z "$PROFILE" ]
then
echo "Credentials missing"
else
region=$(aws configure get region)
fi
if [ "$1" = "list" ]
then
echo $(aws elasticbeanstalk describe-applications --query "Applications[].ApplicationName")
exit 0
fi
if [ "$1" = "help" ] HELP <<EOF
then
read -r -d ''
Usage:
\t$(basename $0) list - list all applications
\t$(basename $0) deploy <app-name> <environment-name> <local-artifact-path> - deploy artifact to an environment
EOF
die "$HELP"
exit 0
fi
die() { echo -e "$*" >&2; exit 1; }
[[ -z $EB_APP ]] && die "ERROR: Missing application name"
[[ -z $EB_ENV ]] && die "ERROR: Missing application environment"
[[ -z $EB_ARTIFACT ]] && die "ERROR: Missing application artifact location"
s3path="s3://$ARTIFACT_LOCATION/$ARTIFACT_NAME"
aws s3 cp $artifactpath $s3path
versionlabel=$(date +%s%N)
aws elasticbeanstalk create-application-version --application-name "$ebapp" --version-label $versionlabel --source-bundle S3Bucket=$ARTIFACT_LOCATION,S3Key=$ARTIFACT_NAME
aws elasticbeanstalk update-environment --environment-name $ebenv --version-label $versionlabel
echo "Deployment in progress"
while [[ "$STATUS" != OK ]] && [[ "$STATUS" != Severe ]];
do
echo "Checking environment status"
status=$(aws elasticbeanstalk describe-environment-health --environment-name $ebenv --attribute-names HealthStatus --query "HealthStatus"| tr -d '"')
echo "Current status: $status."
sleep 5
done
echo "Deployed successfully"
That part is definitely strange. What should the word HELP do after the ]?
You probably wanted something like
if [ "$1" = "help" ]
then
echo Press Enter to display the help...
read
cat <<-EOF
Usage:
$(basename $0) list - list all applications
$(basename $0) deploy <app-name> <environment-name> <local-artifact-path> - deploy artifact to an environment
EOF
fi
Note that you need the real Tab before the closing EOF.
Or maybe you wanted this?
die() { echo -e "$*" >&2; exit 1; }
if [ "$1" = "help" ]
then
HELP="
Usage:
$(basename $0) list - list all applications
$(basename $0) deploy <app-name> <environment-name> <local-artifact-path> - deploy artifact to an environment
"
die "$HELP"
fi
Strings in quotes can be multiline.
Note that die must be declared before you can call it.
I am trying to start/shut down VMs in parallel while also checking the powerstate to make sure it is either running/deallocated. Currently, my script waits on each VM to complete its operation before moving to the next VM. I want to be able to start/stop, check powerstate, move to the next one, check powerstate again and keep looping till all conditions are met.
#!/usr/bin/env bash
VM_IDS="$(az resource list --tag Nonessential=Yes --query "[?type=='Microsoft.Compute/virtualMachines'].{VM:name, RG:resourceGroup}" -o tsv >> vm.txt)"
cat vm.txt
if [[ "${OPTIONS}" == "stop" ]]; then
while read VM RG; do
timeout 10s az vm deallocate -g $RG -n $VM --no-wait
#PS=$(az vm show -g $RG -n $VM --show-details --query powerState -o tsv | tr -d '"')
echo "$VM is shutting down & deallocating..."
while [[ `az vm show -g $RG -n $VM --show-details --query powerState` != "VM deallocated" ]]; do
sleep 5
PS=`az vm show -g $RG -n $VM --show-details --query powerState -o tsv | tr -d '"'`
if [[ "${PS}" == "VM deallocated" ]]; then
echo "$VM has deallocated successfully..."
echo "--------------------------------------------------"
echo
break
else
echo "$VM is still deallocating..."
fi
done
done <vm.txt
elif [[ "${OPTIONS}" == "start" ]]; then
while read VM RG; do
timeout 10s az vm start -g $RG -n $VM --no-wait
#PS=$(az vm show -g $RG -n $VM --show-details --query powerState -o tsv | tr -d '"')
echo "$VM is starting..."
while [[ `az vm show -g $RG -n $VM --show-details --query powerState` != "VM running" ]]; do
sleep 5
PS=`az vm show -g $RG -n $VM --show-details --query powerState -o tsv | tr -d '"'`
if [[ "${PS}" == "VM running" ]]; then
echo "$VM has started successfully..."
echo "--------------------------------------------------"
echo
break
else
echo "$VM is still starting..."
fi
done
done <vm.txt
else
echo "Try again..."
exit 1
fi
exit 0
I might be blind, but I can't find the errors my script got, maybe you guys got better eyes than me :). I use a busybox compiled linux on a embedded system, Kernel 4.18.0. I found the base script here: Gist-Template
the following error is at "start":
./daemon: line 195: arithmetic syntax error
when I try "stop" these messages appear, but i dont see a unknown operand at line 0:
sh: 0: unknown operand
* Stopping Monitoring
my script:
#!/bin/sh
daemonName="Monitoring"
pidDir="."
pidFile="$pidDir/$daemonName.pid"
pidFile="$daemonName.pid"
logDir="."
# To use a dated log file.
# logFile="$logDir/$daemonName-"`date +"%Y-%m-%d"`".log"
# To use a regular log file.
logFile="$logDir/$daemonName.log"
# Log maxsize in KB
logMaxSize=1024 # 1mb
runInterval=300 # In seconds
doCommands() {
# This is where you put all the commands for the daemon.
echo "Running commands."
}
############################################################
# Below are the command functions
############################################################
hw_temp() {
cpu_temp=$(sensors|grep CPU|awk '{print $3}'|awk '{print ($0-int($0)<0.499)?int($0):int($0)+1}')
env_temp=$(sensors|grep ENV|awk '{print $3}'|awk '{print ($0-int($0)<0.499)?int($0):int($0)+1}')
pcb_temp=$(sensors|grep PCB|awk '{print $3}'|awk '{print ($0-int($0)<0.499)?int($0):int($0)+1}')
echo "$cpu_temp $env_temp $pcb_temp" >> /opt/monitoring/bla
}
############################################################
# Below is the skeleton functionality of the daemon.
############################################################
myPid=`echo $$`
setupDaemon() {
# Make sure that the directories work.
if [ ! -d "$pidDir" ]; then
mkdir "$pidDir"
fi
if [ ! -d "$logDir" ]; then
mkdir "$logDir"
fi
if [ ! -f "$logFile" ]; then
touch "$logFile"
else
# Check to see if we need to rotate the logs.
size=$((`ls -l "$logFile" | cut -d " " -f 8`/1024))
if [[ $size -gt $logMaxSize ]]; then
mv $logFile "$logFile.old"
touch "$logFile"
fi
fi
}
startDaemon() {
# Start the daemon.
setupDaemon # Make sure the directories are there.
if [[ `checkDaemon` = 1 ]]; then
echo " * \033[31;5;148mError\033[39m: $daemonName is already running."
exit 1
fi
echo " * Starting $daemonName with PID: $myPid."
echo "$myPid" > "$pidFile"
log '*** '`date +"%Y-%m-%d"`": Starting up $daemonName."
# Start the loop.
loop
}
stopDaemon() {
# Stop the daemon.
if [[ `checkDaemon` -eq 0 ]]; then
echo " * \033[31;5;148mError\033[39m: $daemonName is not running."
exit 1
fi
echo " * Stopping $daemonName"
log '*** '`date +"%Y-%m-%d"`": $daemonName stopped."
if [[ ! -z `cat $pidFile` ]]; then
kill -9 `cat "$pidFile"` &> /dev/null
fi
}
statusDaemon() {
# Query and return whether the daemon is running.
if [[ `checkDaemon` -eq 1 ]]; then
echo " * $daemonName is running."
else
echo " * $daemonName isn't running."
fi
exit 0
}
restartDaemon() {
# Restart the daemon.
if [[ `checkDaemon` = 0 ]]; then
# Can't restart it if it isn't running.
echo "$daemonName isn't running."
exit 1
fi
stopDaemon
startDaemon
}
checkDaemon() {
# Check to see if the daemon is running.
# This is a different function than statusDaemon
# so that we can use it other functions.
if [ -z "$oldPid" ]; then
return 0
elif [[ `ps aux | grep "$oldPid" | grep "$daemonName" | grep -v grep` > /dev/null ]]; then
if [ -f "$pidFile" ]; then
if [[ `cat "$pidFile"` = "$oldPid" ]]; then
# Daemon is running.
# echo 1
return 1
else
# Daemon isn't running.
return 0
fi
fi
elif [[ `ps aux | grep "$daemonName" | grep -v grep | grep -v "$myPid" | grep -v "0:00.00"` > /dev/null ]]; then
# Daemon is running but without the correct PID. Restart it.
log '*** '`date +"%Y-%m-%d"`": $daemonName running with invalid PID; restarting."
restartDaemon
return 1
else
# Daemon not running.
return 0
fi
return 1
}
loop() {
# This is the loop.
now=`date +%s`
if [ -z $last ]; then
last=`date +%s`
fi
# Do everything you need the daemon to do.
doCommands
# Check to see how long we actually need to sleep for. If we want this to run
# once a minute and it's taken more than a minute, then we should just run it
# anyway.
last=`date +%s`
# Set the sleep interval
if [[ ! $((now-last+runInterval+1)) -lt $((runInterval)) ]]; then
sleep $((now-last+runInterval))
fi
# Startover
loop
}
log() {
# Generic log function.
echo "$1" >> "$logFile"
}
###############################################################
# Parse the command.
###############################################################
if [ -f "$pidFile" ]; then
oldPid=`cat "$pidFile"`
fi
checkDaemon
case "$1" in
start)
startDaemon
;;
stop)
stopDaemon
;;
status)
statusDaemon
;;
restart)
restartDaemon
;;
*)
echo "Error: usage $0 { start | stop | restart | status }"
exit 1
esac
exit 0
This question already has answers here:
Calling custom function in bash results in "Command not found" [duplicate]
(1 answer)
Having problems calling function within bash script
(1 answer)
Closed 4 years ago.
I'm trying to create a bash script that will allow you to specify an AWS account that you want to perform some actions in.
I have a variable I need to pass that refers to a file that the script will cycle through.
But the function that performs the actions isn't recognized by the script.
This is the error I get :
./delete_snapshots.sh
What AWS Account:
12345678910
Delete Snapshots in account: AWS Lab
./delete_snapshots.sh: line 14: delete_snapshots: command not found
This is the script :
#!/bin/bash
echo "What AWS Account: ";
read accountnumber
declare -a arr=("12345678910" "109876543212")
for i in "${arr[#]}"
do
if [ "$i" -eq 12345678910 ]; then
aws_account="AWS Lab"
aws_key="lab"
aws_file="aws_lab_snapshots.txt"
echo "Delete Snapshots in account: $aws_account"
delete_snapshots
break
elif [ "$i" -eq 109876543212 ]; then
aws_account="AWS Billing"
aws_key="bill"
aws_file="aws_bill_snapshots.txt"
echo "You are currently in account: $aws_account"
delete_snapshots
break
else
echo "Unkown account"
fi
done
delete_snapshots(){
for i in $(cat $aws_file)
do
echo "*****************************************************************"
echo "deleting snapshot: $i"
aws ec2 delete-snapshot --snapshot-id=$i --profile=lab 2>&1 | sed -e 's/^An error occurred.*when calling the DeleteSnapshot operation: //'
echo "*****************************************************************"
echo; echo; echo; echo; echo
sleep 5
echo "*****************************************************************"
echo "Verify that snapshot: $i is gone:"
aws ec2 describe-snapshots --snapshot-ids=$i --profile=lab 2>&1 | sed -e 's/^An error occurred.*when calling the DescribeSnapshots operation: //g'
echo "*****************************************************************"
echo; echo; echo; echo; echo
done
}
The script works now thanks for some helpful suggestions. Here's the working form of the script:
#!/bin/bash
delete_snapshots(){
for i in $(cat $aws_file)
do
echo "*****************************************************************"
echo "deleting snapshot: $i"
aws ec2 delete-snapshot --snapshot-id=$i --profile=$aws_key 2>&1 | sed -e 's/^An error occurred.*when calling the DeleteSnapshot operation: //'
echo "*****************************************************************"
echo; echo; echo; echo; echo
sleep 5
echo "*****************************************************************"
echo "Verify that snapshot: $i is gone:"
aws ec2 describe-snapshots --snapshot-ids=$i --profile=$aws_key 2>&1 | sed -e 's/^An error occurred.*when calling the DescribeSnapshots operation: //g'
echo "*****************************************************************"
echo; echo; echo; echo; echo
done
}
echo "What AWS Account: ";
read accountnumber
declare -a arr=("123456789101" "109876543212")
for i in "${arr[#]}"
do
if [ "$i" -eq 123456789101 ]; then
aws_account="AWS Lab"
aws_key="lab"
aws_file="source_files/aws_lab_snapshots.txt"
echo "Delete Snapshots in account: $aws_account"
delete_snapshots
break
elif [ "$i" -eq 109876543212 ]; then
aws_account="AWS Billing"
aws_key="bill"
aws_file="source_files/aws_bill_snapshots.txt"
echo "You are currently in account: $aws_account"
delete_snapshots
break
else
echo "Unkown account"
fi
done
I've been playing with bash scripting for 40'ish days with 0 experience so forgive me if my code looks like crap. I have a script that will take the configured NTP servers out of the /etc/ntp.conf file (/root/ntp.conf for testing)
NTPSRVCounter=1
echo "--- NTP Configuration ---"
echo " "
while read -r line; do
if [ $NTPSRVCounter == 1 ] ; then
echo "Primary NTP: $line"
SEDConfiguredNTP1="$(echo $line | sed 's/\./\\./g')"
((NTPSRVCounter++))
echo " "
else
SEDConfiguredNTP2="$(echo $line | sed 's/\./\\./g')"
echo "Secondary NTP: $line"
echo ""
fi
done < <(grep -o -P '(?<=server ).*(?= iburst)' /root/ntp.conf)
And asks you if you want to change it with a case statement:
echo "Do you wish to change it? [Y/n]"
NTPSRVCounter2=1
read opt
case $opt in
Y|y) read -p "Enter in your primary NTP server: " -e -i '0.debian.pool.ntp.org' UserInputNTP1
read -p "Enter in your secondary NTP serer: " -e -i '1.debian.pool.ntp.org' UserInputNTP2
for NTP in "$UserInputNTP1" "$UserInputNTP2" ; do
is_fqdn "$NTP"
if [[ $? == 0 && $NTPSRVCounter2 == 1 ]] ; then
SEDUserInput1=$(echo $UserInputNTP1 | sed 's/\./\\./g')
((NTPSRVCounter2++))
elif [[ $? == 0 && $NTPSRVCounter2 == 2 ]] ; then
SEDUserInput2=$(echo $UserInputNTP2 | sed 's/\./\\./g')
sudo sed -i "s/$SEDConfiguredNTP1/$SEDUserInput1/g" /root/ntp.conf
sudo sed -i "s/$SEDConfiguredNTP2/$SEDUserInput2/g" /root/ntp.conf
else
echo "Fail!!! :-( "
fi
done
;;
N|n) return 0
;;
*) echo "I don't know what happened, but, eh, you're not supposed to be here."
;;
esac
The problem is with the "elif" statement and the function "is_fqdn" on the second run of the function. If I put "bash -x" on the script and run it, I see "is_fqdn" returning 0 on both runs of the function, but the elif statement "$?" is coming up as 1 instead of 0.
The two functions used are below. Have to validate NTP addresses as either valid domain names or I.P. addresses, right? :)
is_fqdn() {
hostname=$1
if [[ "$hostname" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
valid_ip "$hostname"
elif [[ "$hostname" == *"."* && "$hostname" != "localhost." && "$hostname" != "localhost" ]] ; then
return 0
else
return 1
fi
host $hostname > /dev/null 2>&1 || return 1
}
valid_ip(){
local stat=1
local ip=$1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS="."
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
return "$stat"
}
The condition in your if sets the value of $?, and that is what's used by the condition in the elif part, not the return value of is_fqdn. You need to save the value if you want to use it in multiple places:
is_fqdn "$NTP"
is_fqdn_rv=$?
if [[ $is_fqdn_rv == 0 && $NTPSRVCounter2 == 1 ]] ; then
SEDUserInput1=$(echo $UserInputNTP1 | sed 's/\./\\./g')
((NTPSRVCounter2++))
elif [[ $is_fqdn_rv == 0 && $NTPSRVCounter2 == 2 ]] ; then
SEDUserInput2=$(echo $UserInputNTP2 | sed 's/\./\\./g')
sudo sed -i "s/$SEDConfiguredNTP1/$SEDUserInput1/g" /root/ntp.conf
sudo sed -i "s/$SEDConfiguredNTP2/$SEDUserInput2/g" /root/ntp.conf
else
echo "Fail!!! :-( "
fi