206 lines
7.0 KiB
Bash
206 lines
7.0 KiB
Bash
#!/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 <<EOF >> /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_<parameter> 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 <<EOF > /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 <<EOF >> /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
|
|
}
|