Skip to content

Instantly share code, notes, and snippets.

@jcfr
Created April 1, 2025 06:25
Show Gist options
  • Save jcfr/5e8e7ee93ceb736324aa4685740ac235 to your computer and use it in GitHub Desktop.
Save jcfr/5e8e7ee93ceb736324aa4685740ac235 to your computer and use it in GitHub Desktop.
Scripts to create, shelve or unshelve a MorphoCloud instance called `instance-9999`

The following scripts are expected to be executed after both SSH'ing into the GitHub self-hosted runner associated with allocation BIO180006_IU and activating the associated python environment.

Scripts were created based of MorphoCloud/MorphoCloudWorkflow@624549e64

For example:

ssh [email protected]
. venv/bin/activate
#!/usr/bin/env bash
export OS_CLOUD=BIO180006_IU
cd /tmp
git clone https://github.com/MorphoCloud/MorphoCloudWorkflow.git
cd MorphoCloudWorkflow
INSTANCE_NAME="instance-9999"
VOLUME_NAME="My-Data-9999"
INSTANCE_FLAVOR="g3.xl"
# ----------------------------------------------------------------------------
# Check if instance has a floating IP
# Get instance floating IP
instance_ip=$(
openstack server show $INSTANCE_NAME -c addresses -f json | \
jq -r '.addresses.auto_allocated_network | if length > 1 then .[1] else "" end'
)
echo "instance_ip [$instance_ip]"
# Get instance internal IP (also called fixed IP)
instance_internal_ip=$(
openstack server show $INSTANCE_NAME -c addresses -f json | \
jq -r '.addresses.auto_allocated_network | if length > 0 then .[0] else "" end'
)
echo "instance_internal_ip [$instance_internal_ip]"
# Determine if instance already has a floating IP assigned
if [[ -n "$instance_ip" ]]; then
has_ip="true"
else
has_ip="false"
fi
echo "has_ip [$has_ip]"
# Check if an unassigned floating IP matches instance IP
has_ip_assigned="false"
if [[ "$has_ip" == "true" ]]; then
json_output=$(openstack floating ip list -f json | jq \
--arg fixed_ip "$instance_internal_ip" \
--arg ip "$instance_ip" \
'map(select(.Port != null and ."Fixed IP Address" == $fixed_ip and ."Floating IP Address" == $ip))')
floating_ip_address=$(echo "$json_output" | jq -r 'if length > 0 then .[0]["Floating IP Address"] else "" end')
if [[ -n "$floating_ip_address" ]]; then
has_ip_assigned="true"
fi
fi
# If previously assigned IP is not available anymore, discard it.
if [[ "$has_ip_assigned" == "false" ]]; then
instance_ip=""
fi
echo "instance_ip [$instance_ip]"
echo "has_ip_assigned [$has_ip_assigned]"
# ----------------------------------------------------------------------------
# Retrieve or create floating IP
floating_ip_address=""
floating_ip_uuid=""
if [[ $has_ip_assigned == "false" ]]; then
preferred_ip_address=$instance_ip
echo "preferred_ip_address [$preferred_ip_address]"
# Retrieve unassigned floating IP
json_output=$(openstack floating ip list -f json | jq 'map(select(.Port == null))')
echo "Filtered JSON Output:"
echo "$json_output" | jq
if [[ -n "$preferred_ip_address" ]]; then
floating_ip=$(echo "$json_output" | jq -r --arg ip "$preferred_ip_address" \
'map(select(."Floating IP Address" == $ip)) | if length > 0 then .[0] else null end')
else
floating_ip=$(echo "$json_output" | jq -r 'if length > 0 then .[0] else null end')
fi
floating_ip_address=$(echo "$floating_ip" | jq -r '."Floating IP Address" // empty')
floating_ip_uuid=$(echo "$floating_ip" | jq -r '."ID" // empty')
echo "floating_ip_address [$floating_ip_address]"
echo "floating_ip_uuid [$floating_ip_uuid]"
fi
if [[ -z "$floating_ip_address" ]]; then
json_output=$(openstack floating ip create public -f json)
echo "Floating IP Creation Output:"
echo "$json_output" | jq
floating_ip_uuid=$(
echo $json_output |
jq -r ".id"
)
echo "floating_ip_uuid [$floating_ip_uuid]"
floating_ip_address=$(
echo $json_output |
jq -r ".floating_ip_address"
)
echo "floating_ip_address [$floating_ip_address]"
fi
# ----------------------------------------------------------------------------
openstack volume create --size 100 "$VOLUME_NAME"
# ----------------------------------------------------------------------------
# The MorphCloud workflow uses a unique UUID generated with:
# python3 -c "import uuid; print(uuid.uuid4())"
exoClientUuid=b7b75870-ecb5-4aca-ae73-49474a7f531e
# See "currentExoServerVersion" in exosphere/src/Types/Server.elm
exoServerVersion=5
FLOATING_IP_UUID=$floating_ip_uuid
echo "INSTANCE_FLAVOR [$INSTANCE_FLAVOR]"
echo "FLOATING_IP_UUID [$FLOATING_IP_UUID]"
echo "exoServerVersion [$exoServerVersion]"
echo "exoClientUuid [$exoClientUuid]"
openstack server create "$INSTANCE_NAME" \
--nic net-id="auto_allocated_network" \
--security-group "exosphere" \
--flavor $INSTANCE_FLAVOR \
--image "Featured-Ubuntu22" \
--key-name "jcfr" \
--property "exoGuac={\"v\":1,\"ssh\":true,\"vnc\":true}" \
--property "exoClientUuid=$exoClientUuid" \
--property "exoServerVersion=$exoServerVersion" \
--property "[email protected]" \
--property "exoFloatingIpOption=useFloatingIp" \
--property "exoFloatingIpReuseOption=$FLOATING_IP_UUID" \
--property "exoSetup={\"status\":\"waiting\",\"epoch\":null}" \
--user-data ./cloud-config \
--wait \
--column created \
--column flavor \
--column image \
--column name \
--column status
# Associate floating IP with created instance
has_ip=$(
openstack server show $INSTANCE_NAME -c addresses -f json | \
jq -r '.addresses.auto_allocated_network[1] != null'
)
echo "has_ip [$has_ip]"
if [[ $has_ip != "true" ]]; then
openstack server add floating ip "$INSTANCE_NAME" $FLOATING_IP_ADDRESS
fi
# ----------------------------------------------------------------------------
# Poll instance setup status
echo Polling "$INSTANCE_NAME" setup status
max_wait_time=1200 # Maximum wait time in seconds (1200s -> 20mins)
wait_interval=5 # Interval between status checks in seconds
total_wait_time=0
while [ $total_wait_time -lt $max_wait_time ]; do
status=$(openstack console log show $INSTANCE_NAME | \
grep "^\{\"status\":\"" | \
tail -1 | \
jq -r '.status // "pending"')
echo -n "setup status [$status]. "
if [[ "$status" == "complete" ]]; then
echo "Exiting loop."
break
else
echo "Waiting for completion..."
sleep $wait_interval
total_wait_time=$((total_wait_time + wait_interval))
fi
done
if [ $total_wait_time -ge $max_wait_time ]; then
echo "::error ::Maximum wait time ($max_wait_time seconds) exceeded."
fi
# Explicitly set status to "complete"
exoSetup='{"status":"complete", "epoch": '$(date '+%s')'000}'
openstack server set \
--property "exoSetup=$exoSetup" \
$INSTANCE_NAME
echo "status [$status]"
# ----------------------------------------------------------------------------
# Rename MyData directory to MyData-tmp
INSTANCE_IP=$FLOATING_IP_ADDRESS
ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
exouser@$INSTANCE_IP \
'mv /media/volume/MyData /media/volume/MyData-tmp'
# ----------------------------------------------------------------------------
# Attach volume
instance_id=$(openstack server list -f json | \
jq \
--arg instance_name "$INSTANCE_NAME" \
-c '.[] | select(.Name == $instance_name)' | \
jq -r '.ID' | tail -1)
echo "instance_id [$instance_id]"
volume_id=$(openstack volume list -f json | \
jq \
--arg volume_name "$VOLUME_NAME" \
-c '.[] | select(.Name == $volume_name)' | \
jq -r '.ID' | tail -1)
echo "volume_id [$volume_id]"
openstack server set \
--property "exoVolumes::$volume_id={\"name\":\"MyData\"}" \
$instance_id
openstack server add volume \
$instance_id \
$volume_id
# ----------------------------------------------------------------------------
# Copy installed files into attached volume
function check_mountpoint_ready {
ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
exouser@$INSTANCE_IP \
'mountpoint -q /media/volume/MyData'
}
set +e
max_attempts=5
for attempt in $(seq 1 $max_attempts); do
echo "Checking if mount point is ready ($attempt/$max_attempts)"
check_mountpoint_ready
if check_mountpoint_ready; then
echo "Mount point /media/volume/MyData is ready."
break
else
echo "Mount point not ready. Retrying in 5 seconds..."
sleep 5
fi
done
if ! check_mountpoint_ready; then
echo "::error ::Mount point /media/volume/MyData not ready after $((max_attempts * 5)) seconds."
exit 1
fi
set -e
ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
exouser@$INSTANCE_IP \
'[ ! -d /media/volume/MyData/Slicer ] && mv /media/volume/MyData-tmp/Slicer /media/volume/MyData/Slicer && rmdir /media/volume/MyData-tmp || rm -rf /media/volume/MyData-tmp'
# ----------------------------------------------------------------------------
# Create Renviron file
ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
exouser@$INSTANCE_IP \
'echo -e "R_LIBS_SITE=/media/share/MorphoCloudCephShare/R/x86_64-pc-linux-gnu-library/4.4/\nR_LIBS_USER=/media/volume/MyData/R/x86_64-pc-linux-gnu-library/4.4/" > /home/exouser/.Renviron && mkdir -p /media/volume/MyData/R/x86_64-pc-linux-gnu-library/4.4/'
# ----------------------------------------------------------------------------
# Reboot instance
set +e
ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
exouser@$INSTANCE_IP \
'sudo shutdown -r now'
sleep 10
function check_instance_ready {
ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
exouser@$INSTANCE_IP \
'true'
}
max_attempts=3
instance_ready=false
for attempt in $(seq 1 $max_attempts); do
echo "Checking if instance is ready ($attempt/$max_attempts)"
if check_instance_ready; then
instance_ready=true
echo "Instance '$INSTANCE_NAME' is ready."
break
else
echo "Instance '$INSTANCE_NAME' is not ready. Retrying in 5 seconds..."
sleep 10
fi
done
if ! $instance_ready; then
echo "::error ::Instance '$INSTANCE_NAME' is not ready after $max_attempts attempts to connect."
fi
set -e
# ----------------------------------------------------------------------------
# Explicitly set status to complete
exoSetup='{"status":"complete", "epoch": '$(date '+%s')'000}'
openstack server set \
--property "exoSetup=$exoSetup" \
$INSTANCE_NAME
#!/usr/bin/env bash
export OS_CLOUD=BIO180006_IU
INSTANCE_NAME="instance-9999"
COMMAND_NAME="shelve"
EXPECTED_STATUS="SHELVED_OFFLOADED"
# ----------------------------------------------------------------------------
# Check if instance has a floating IP
# Get instance floating IP
instance_ip=$(
openstack server show $INSTANCE_NAME -c addresses -f json | \
jq -r '.addresses.auto_allocated_network | if length > 1 then .[1] else "" end'
)
echo "instance_ip [$instance_ip]"
# Get instance internal IP (also called fixed IP)
instance_internal_ip=$(
openstack server show $INSTANCE_NAME -c addresses -f json | \
jq -r '.addresses.auto_allocated_network | if length > 0 then .[0] else "" end'
)
echo "instance_internal_ip [$instance_internal_ip]"
# Determine if instance already has a floating IP assigned
if [[ -n "$instance_ip" ]]; then
has_ip="true"
else
has_ip="false"
fi
echo "has_ip [$has_ip]"
# Check if an unassigned floating IP matches instance IP
has_ip_assigned="false"
if [[ "$has_ip" == "true" ]]; then
json_output=$(openstack floating ip list -f json | jq \
--arg fixed_ip "$instance_internal_ip" \
--arg ip "$instance_ip" \
'map(select(.Port != null and ."Fixed IP Address" == $fixed_ip and ."Floating IP Address" == $ip))')
floating_ip_address=$(echo "$json_output" | jq -r 'if length > 0 then .[0]["Floating IP Address"] else "" end')
if [[ -n "$floating_ip_address" ]]; then
has_ip_assigned="true"
fi
fi
# If previously assigned IP is not available anymore, discard it.
if [[ "$has_ip_assigned" == "false" ]]; then
instance_ip=""
fi
echo "instance_ip [$instance_ip]"
echo "has_ip_assigned [$has_ip_assigned]"
FLOATING_IP_ADDRESS=$instance_ip
# ----------------------------------------------------------------------------
openstack server $COMMAND_NAME "$INSTANCE_NAME"
# ----------------------------------------------------------------------------
max_wait_time=300 # Maximum wait time in seconds (300s -> 5mins)
if [[ "$EXPECTED_STATUS" == "SHELVED_OFFLOADED" ]]; then
max_wait_time=600 # Maximum wait time in seconds (600s -> 10mins)
fi
echo "Polling '$INSTANCE_NAME' status [max_wait_time=${max_wait_time}s]"
wait_interval=5 # Interval between status checks in seconds
total_wait_time=0
while [ $total_wait_time -lt $max_wait_time ]; do
status=$(openstack server show $INSTANCE_NAME -f json -c status | \
jq -r '.status // "PENDING"')
echo -n "status [$status]. "
if [[ "$status" == "$EXPECTED_STATUS" ]]; then
echo "Exiting loop."
break
else
echo "Waiting for completion..."
sleep $wait_interval
total_wait_time=$((total_wait_time + wait_interval))
fi
done
if [ $total_wait_time -ge $max_wait_time ]; then
echo "::error ::Maximum wait time ($max_wait_time seconds) exceeded."
fi
echo "status [$status]"
# ----------------------------------------------------------------------------
openstack server remove floating ip "$INSTANCE_NAME" "$FLOATING_IP_ADDRESS"
#!/usr/bin/env bash
export OS_CLOUD=BIO180006_IU
INSTANCE_NAME="instance-9999"
COMMAND_NAME="unshelve"
EXPECTED_STATUS="ACTIVE"
# ----------------------------------------------------------------------------
# Check if instance has a floating IP
# Get instance floating IP
instance_ip=$(
openstack server show $INSTANCE_NAME -c addresses -f json | \
jq -r '.addresses.auto_allocated_network | if length > 1 then .[1] else "" end'
)
echo "instance_ip [$instance_ip]"
# Get instance internal IP (also called fixed IP)
instance_internal_ip=$(
openstack server show $INSTANCE_NAME -c addresses -f json | \
jq -r '.addresses.auto_allocated_network | if length > 0 then .[0] else "" end'
)
echo "instance_internal_ip [$instance_internal_ip]"
# Determine if instance already has a floating IP assigned
if [[ -n "$instance_ip" ]]; then
has_ip="true"
else
has_ip="false"
fi
echo "has_ip [$has_ip]"
# Check if an unassigned floating IP matches instance IP
has_ip_assigned="false"
if [[ "$has_ip" == "true" ]]; then
json_output=$(openstack floating ip list -f json | jq \
--arg fixed_ip "$instance_internal_ip" \
--arg ip "$instance_ip" \
'map(select(.Port != null and ."Fixed IP Address" == $fixed_ip and ."Floating IP Address" == $ip))')
floating_ip_address=$(echo "$json_output" | jq -r 'if length > 0 then .[0]["Floating IP Address"] else "" end')
if [[ -n "$floating_ip_address" ]]; then
has_ip_assigned="true"
fi
fi
# If previously assigned IP is not available anymore, discard it.
if [[ "$has_ip" == "true" && "$has_ip_assigned" == "false" ]]; then
json_output=$(openstack floating ip list -f json | jq \
--arg fixed_ip "$instance_internal_ip" \
--arg ip "$instance_ip" \
'map(select(.Port != null and ."Fixed IP Address" != $fixed_ip and ."Floating IP Address" == $ip))')
has_ip_reassigned=$(echo "$json_output" | jq -r 'if length > 0 then "true" else "false" end')
echo "has_ip_reassigned [$has_ip_reassigned]"
if [[ $has_ip_reassigned == "true" ]]; then
instance_ip=""
fi
fi
echo "instance_ip [$instance_ip]"
echo "has_ip_assigned [$has_ip_assigned]"
# ----------------------------------------------------------------------------
# Retrieve or create floating IP
floating_ip_address=""
floating_ip_uuid=""
if [[ $has_ip_assigned == "false" ]]; then
preferred_ip_address=$instance_ip
echo "preferred_ip_address [$preferred_ip_address]"
# Retrieve unassigned floating IP
json_output=$(openstack floating ip list -f json | jq 'map(select(.Port == null))')
echo "Filtered JSON Output:"
echo "$json_output" | jq
if [[ -n "$preferred_ip_address" ]]; then
floating_ip=$(echo "$json_output" | jq -r --arg ip "$preferred_ip_address" \
'map(select(."Floating IP Address" == $ip)) | if length > 0 then .[0] else null end')
else
floating_ip=$(echo "$json_output" | jq -r 'if length > 0 then .[0] else null end')
fi
floating_ip_address=$(echo "$floating_ip" | jq -r '."Floating IP Address" // empty')
floating_ip_uuid=$(echo "$floating_ip" | jq -r '."ID" // empty')
echo "floating_ip_address [$floating_ip_address]"
echo "floating_ip_uuid [$floating_ip_uuid]"
fi
if [[ -z "$floating_ip_address" ]]; then
json_output=$(openstack floating ip create public -f json)
echo "Floating IP Creation Output:"
echo "$json_output" | jq
floating_ip_uuid=$(
echo $json_output |
jq -r ".id"
)
echo "floating_ip_uuid [$floating_ip_uuid]"
floating_ip_address=$(
echo $json_output |
jq -r ".floating_ip_address"
)
echo "floating_ip_address [$floating_ip_address]"
fi
# ----------------------------------------------------------------------------
openstack server add floating ip "$INSTANCE_NAME" "$floating_ip_address"
# ----------------------------------------------------------------------------
openstack server $COMMAND_NAME "$INSTANCE_NAME"
max_wait_time=300 # Maximum wait time in seconds (300s -> 5mins)
if [[ "$EXPECTED_STATUS" == "SHELVED_OFFLOADED" ]]; then
max_wait_time=600 # Maximum wait time in seconds (600s -> 10mins)
fi
echo "Polling '$INSTANCE_NAME' status [max_wait_time=${max_wait_time}s]"
wait_interval=5 # Interval between status checks in seconds
total_wait_time=0
while [ $total_wait_time -lt $max_wait_time ]; do
status=$(openstack server show $INSTANCE_NAME -f json -c status | \
jq -r '.status // "PENDING"')
echo -n "status [$status]. "
if [[ "$status" == "$EXPECTED_STATUS" ]]; then
echo "Exiting loop."
break
else
echo "Waiting for completion..."
sleep $wait_interval
total_wait_time=$((total_wait_time + wait_interval))
fi
done
if [ $total_wait_time -ge $max_wait_time ]; then
echo "::error ::Maximum wait time ($max_wait_time seconds) exceeded."
fi
echo "status [$status]"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment