#!/bin/bash # Todo: This should go into a yaml file query_imds() { MAC=$(imds meta-data/mac) AVAILABILITY_ZONE=$(imds meta-data/placement/availability-zone) REGION=$(echo ${AVAILABILITY_ZONE} | sed "s/[a-z]$//") INSTANCE_ID=$(imds meta-data/instance-id) cat <> /var/lib/cloud/meta-data AVAILABILITY_ZONE=$AVAILABILITY_ZONE REGION=$REGION INSTANCE_ID=$INSTANCE_ID IP_ADDRESS=$(imds meta-data/local-ipv4) PUBLIC_IP_ADDRESS=$(imds meta-data/public-ipv4 || true) DEFAULT_GW_INTERFACE=$(ip -o route get 8.8.8.8 | awk '{print $5}') MAC=$MAC VPC_CIDR_RANGE=$(imds meta-data/network/interfaces/macs/${MAC}/vpc-ipv4-cidr-block) SUBNET=$(imds meta-data/network/interfaces/macs/${MAC}/subnet-ipv4-cidr-block) _META_HOSTNAME=$(imds meta-data/hostname) DOMAIN_NAME=\${_META_HOSTNAME#*.} AWS_ACCOUNT_ID=$(imds meta-data/network/interfaces/macs/${MAC}/owner-id) INSTANCE_LIFE_CYCLE=$(imds meta-data/instance-life-cycle) INSTANCE_TYPE=$(imds meta-data/instance-type) EOF } # Todo: This should go into a yaml file get_tags() { # via metadata AWS restricts tags to NOT have " " or "/" ;-( # Replace all /:.- with _ for valid variable names for key in $(imds meta-data/tags/instance); do value="$(imds meta-data/tags/instance/$key)" key=$(echo ${key//[\/:.-]/_} | tr '[:lower:]' '[:upper:]') echo "$key=\"$value\"" >> /var/lib/cloud/meta-data done #while read _key value; do # key=$(echo ${_key//[\/:.-]/_} | tr '[:lower:]' '[:upper:]') # echo "$key=\"$value\"" >> /var/lib/cloud/meta-data #done < <(aws ec2 describe-tags --filters "Name=resource-id,Values=${INSTANCE_ID}" --query 'Tags[*].[Key,Value]' --region $REGION --output text) } # extract user-data args and cloud meta-data into /var/lib/cloud/meta-data get_meta_data() { if [ ! -f /var/lib/cloud/meta-data ]; then echo '#!/bin/bash' > /var/lib/cloud/meta-data query_imds get_tags fi if [ ! -f /etc/cloudbender.conf ]; then bash /var/lib/cloud/user-data extract_parameters fi } import_meta_data() { . /etc/cloudbender.conf . /var/lib/cloud/meta-data export AWS_DEFAULT_REGION=$REGION export AWS_DEFAULT_OUTPUT=text # Enabled LaunchHooks if not DEBUG is_enabled $ZDT_CLOUDBENDER_DEBUG || LAUNCH_HOOK="CloudBenderLaunchHook" # Workaround for current CFN ASG_ hack _key=$(echo $AWS_CLOUDFORMATION_LOGICAL_ID | tr '[:lower:]' '[:upper:]') [ -n "$(eval echo \$${_key}_CUSTOMHOSTNAME)" ] && CUSTOMHOSTNAME="$(eval echo \$${_key}_CUSTOMHOSTNAME)" [ -n "$(eval echo \$${_key}_VOLUMES)" ] && VOLUMES="$(eval echo \$${_key}_VOLUMES)" return 0 } # various early volume functions attach_ebs() { local volId="$1" local device="$2" local tries=30 while true; do _json="$(aws ec2 describe-volumes --volume-ids $volId --region $REGION --output json)" rc=$?; [ $rc -ne 0 ] && return $rc vol_status=$(echo "$_json" | jq -r .Volumes[].State) attachedId=$(echo "$_json" | jq -r .Volumes[].Attachments[].InstanceId) [ "$attachedId" = "$INSTANCE_ID" ] && break if [ "$vol_status" = "available" ]; then aws ec2 attach-volume --volume-id "$volId" --instance-id "$INSTANCE_ID" --region "$REGION" --device "$device" > /dev/null rc=$?; [ $rc -ne 0 ] && return $rc break fi # if attached but not to us -> detach if [ "$vol_status" = "in-use" ]; then aws ec2 detach-volume --volume-id "$volId" --region "$REGION" --force rc=$?; [ $rc -ne 0 ] && return $rc fi ((tries=tries-1)) [ $tries -eq 0 ] && return 1 sleep 5 done } asg_heartbeat() { [ -n "$LAUNCH_HOOK" ] && aws autoscaling record-lifecycle-action-heartbeat --instance-id $INSTANCE_ID --lifecycle-hook-name $LAUNCH_HOOK --auto-scaling-group-name $AWS_AUTOSCALING_GROUPNAME || true } setup_sns_alarms() { # store SNS message json template cat < /var/lib/cloud/sns_alarm.json { "Source": "CloudBender", "AWSAccountId": "$AWS_ACCOUNT_ID", "Region": "$REGION", "Artifact": "$ARTIFACT", "Asg": "$AWS_AUTOSCALING_GROUPNAME", "Instance": "$INSTANCE_ID", "ip": "$IP_ADDRESS" } EOF cat <<'EOF' > /var/lib/cloud/sns_alarm.sh #!/bin/bash SUBJECT="$1" MSG="$2" LEVEL="${3:-Info}" ATTACHMENT="${4:-""}" EMOJI="${5:-""}" EOF if [ -n "$ALARMSNSARN" ]; then cat <> /var/lib/cloud/sns_alarm.sh jq -M --arg subject "\$SUBJECT" --arg level "\$LEVEL" --arg msg "\$MSG" --arg attachment "\$ATTACHMENT" --arg emoji "\$EMOJI" --arg hostname "\$HOSTNAME" '.Subject = \$subject | .Level = \$level | .Message = \$msg | .Attachment = \$attachment | .Emoji = \$emoji | .Hostname = \$hostname' < /var/lib/cloud/sns_alarm.json | sed -e 's/\\\\\\\\/\\\\/g' > /tmp/sns.json aws sns publish --region ${REGION} --target-arn $ALARMSNSARN --message file:///tmp/sns.json EOF fi chmod +x /var/lib/cloud/sns_alarm.sh } # associate EIP # return 0 if we attached an EIP # return 1 if we the public IP did NOT change or other error associate_eip() { local instance_id=$1 local eip=$(echo $2 | sed -e 's/\/32//' | grep -E -o "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)") || true local current_instance if [ -n "$eip" ]; then if [ "$eip" != "0.0.0.0" ]; then read eip_alloc_id eip_assoc_id current_instance < <(aws ec2 describe-addresses --public-ips $eip --query 'Addresses[*].[AllocationId,AssociationId,InstanceId]' || true) # If we already own and have the EIP attached -> done [ "$instance_id" == "$current_instance" ] && return if [ ! -z "$eip_alloc_id" ]; then if [[ "$eip_assoc_id" =~ ^eipassoc- ]]; then log -t user-data info "EIP $eip already associated via Association ID ${eip_assoc_id}. Disassociating." retry 3 10 aws ec2 disassociate-address --association-id $eip_assoc_id fi log -t user-data info "Associating Elastic IP $eip via Allocation ID $eip_alloc_id with Instance $instance_id" aws ec2 associate-address --no-allow-reassociation --instance-id $instance_id --allocation-id $eip_alloc_id return else log -t user-data warn "Elastic IP $eip address not found." fi else log -t user-data info "0.0.0.0 requested, keeping AWS assigned IP." fi else log -t user-data debug "Invalid or no ElasticIP defined. Skip" fi return 1 } # Accept incoming traffic for everything disable_source_dest_check() { aws ec2 modify-instance-attribute --instance-id ${INSTANCE_ID} --source-dest-check "{\"Value\": false}" } # Register ourself at route tables register_routes() { local rtb_id_list="$1" local route_cidr="$2" for cidr in ${route_cidr//,/ }; do if [ "$cidr" != "$VPC_CIDR_RANGE" ]; then for rt in ${rtb_id_list//,/ }; do if [[ "$rt" =~ ^rtb-[a-f0-9]*$ ]]; then aws ec2 create-route --route-table-id $rt --destination-cidr-block "${cidr}" --instance-id ${INSTANCE_ID} || \ aws ec2 replace-route --route-table-id $rt --destination-cidr-block "${cidr}" --instance-id ${INSTANCE_ID} else log -t user-data warn "Invalid Route Table ID: $rt" fi done fi done }