Tools: Week 9: Fish Shell Functions for Managing AWS EC2 Instances -- Save Time and Billing

Tools: Week 9: Fish Shell Functions for Managing AWS EC2 Instances -- Save Time and Billing

Introduction

Install Fish Shell

Create the Functions Directory

Set the Instance ID Universal Variable

The Functions

lab-login — Authenticate via IAM Identity Center

lab-status — Check Instance State

lab-start — Start a Stopped Instance

lab-stop — Stop a Running Instance

lab-connect — Open a Shell on the Instance

lab-terminate — Permanently Delete the Instance

lab-create — Launch a Fresh Instance from a Launch Template

Snapshot Functions

lab-snapshot-list — List All Saved Snapshots

lab-snapshot — Save the Current Instance State

lab-restore — Launch an Instance from a Snapshot

lab-snapshot-delete — Delete an Old Snapshot

Complete Reference

Complete Near Zero-Cost Session Workflow

One More Thing — Quick Poll Prerequisites: This post assumes you have a working AWS lab with IAM Identity Center and SSM Session Manager configured. If you have not done that yet, start here first: 👉 Secure AWS Lab Setup for Security Engineers: IAM Identity Center + SSM + Zero Open Ports If you find this useful, I'd really appreciate a ⭐ on my open source secure coding exercise repo — it helps a lot: 🌟 Star the SecEng-Exercises Repo on GitHub Also — why do you read security engineering blog posts? One click helps me write better content: 👉 Take the poll (takes 10 seconds) Running a personal AWS security lab involves the same set of CLI operations over and over: launch an instance, connect to it, do some work, save your progress, and shut it down to avoid unnecessary billing. Without automation, each of these steps requires remembering long aws ec2 commands with multiple flags. This post provides a complete set of fish shell functions that reduce all of that to single commands. They are designed specifically for the AWS lab exercises in this blog series and give you three practical benefits: Save progress — lab-snapshot creates a full AMI snapshot of your running instance so you can terminate it and restore exactly where you left off next session. No reinstalling tools from scratch. Save time — lab-create and lab-restore handle the full instance launch sequence automatically: create, wait for running state, wait for SSM Agent, and tell you when it is ready. One command instead of five. Save billing — lab-terminate permanently deletes the instance so you pay nothing between sessions. Combined with snapshots, you get zero ongoing compute cost with full state preservation. To put the cost difference in perspective: A snapshot of a Debian 13 instance with Suricata installed uses roughly 2-3GB of actual data — AWS only charges for used data, not the full volume size. At $0.05/GB/month that comes to about $0.10-0.15/month. Compared to leaving the instance running at $8.35/month, snapshots are roughly 60x cheaper while preserving your full environment between sessions. All functions are available to download from GitHub: 👉 github.com/fosres/SecEng-Exercises/tree/main/aws/functions Fish loads functions automatically from this directory. Each function lives in its own .fish file named after the function. No source command needed — drop the file in and it is immediately available. All functions that operate on a running instance use $INSTANCE_ID — a fish universal variable that persists across all sessions permanently: set -U means universal — it survives terminal restarts, new tabs, and reboots. lab-create and lab-restore update it automatically when launching a new instance so you rarely need to set it manually. Run this at the start of every session. Opens a browser where you tap your YubiKey to issue 8-hour temporary credentials. Token expires after 8 hours. If any function returns Token has expired and refresh failed — run lab-login again. Returns: running, stopped, terminated, or None. None means the instance was terminated. Run lab-create or lab-restore to launch a new one. Use this only if you chose lab-stop to pause billing rather than lab-terminate. A stopped instance still incurs EBS storage charges (~$0.08/GB/month). Pauses compute billing. The EBS volume persists with all data intact. Resume with lab-start. Opens a terminal session via SSM Session Manager — no SSH, no open ports, no key pair file required. Before running lab-terminate, always verify it is pointing at the right instance: Both should agree before proceeding. ⚠️ Fish syntax bug: Use --prompt-str not --prompt for the read command. Using --prompt causes a syntax error that silently skips the confirmation and immediately terminates the instance. Launches a clean Debian 13 instance from the Launch Template. Automatically updates $INSTANCE_ID. The 3-minute wait allows the User Data script to install the SSM Agent on first boot. These functions let you save and restore instance state so you never lose progress between sessions. --no-reboot — takes the snapshot while the instance keeps running. You stay connected the whole time. ⏳ Expect to wait 3-5 minutes for the snapshot to complete. AWS is copying the entire EBS volume to S3. A heavily loaded instance may take up to 10-15 minutes. Do not interrupt it. Lists your saved AMIs first so you never need to look up the AMI ID separately. The security group ID is resolved automatically by name. sleep 60 instead of sleep 180 — the SSM Agent is already baked into the snapshot so it initializes much faster than a fresh install. Two steps are required to fully delete a snapshot — this is an important AWS quirk: Step 1 — Deregister the AMI (aws ec2 deregister-image) — removes the AMI entry. But the underlying EBS snapshot is still there and still billing you until Step 2. Step 2 — Delete the EBS snapshot (aws ec2 delete-snapshot) — this is what actually frees the storage and stops charges. lab-snapshot-delete handles both steps automatically. These functions are part of an open source repository of LeetCode-style secure coding exercises designed to: Every exercise in the repo has 60+ test cases and a companion Dev.to post. If this post was useful, the best thing you can do is star the repo: 🌟 Star the SecEng-Exercises Repo on GitHub Why do you read security engineering blog posts? Your answer helps me write better content: Series: Security Engineering Interview Prep | Week 9 | March 2026

GitHub: fosres/SecEng-Exercises Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

# Debian / Ubuntu -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install fish -y # Verify fish --version # Debian / Ubuntu -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install fish -y # Verify fish --version # Debian / Ubuntu -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install fish -y # Verify fish --version mkdir -p ~/.config/fish/functions mkdir -p ~/.config/fish/functions mkdir -p ~/.config/fish/functions set -U INSTANCE_ID "i-your_instance_id_here" set -U INSTANCE_ID "i-your_instance_id_here" set -U INSTANCE_ID "i-your_instance_id_here" function lab-login aws sso login --profile lab-sso end function lab-login aws sso login --profile lab-sso end function lab-login aws sso login --profile lab-sso end $ lab-login # Browser opens → tap YubiKey → "Your credentials have been shared successfully" $ lab-login # Browser opens → tap YubiKey → "Your credentials have been shared successfully" $ lab-login # Browser opens → tap YubiKey → "Your credentials have been shared successfully" function lab--weight: 500;">status aws ec2 describe-instances --instance-ids $INSTANCE_ID \ --profile lab-sso \ --query "Reservations[0].Instances[0].State.Name" \ --output text end function lab--weight: 500;">status aws ec2 describe-instances --instance-ids $INSTANCE_ID \ --profile lab-sso \ --query "Reservations[0].Instances[0].State.Name" \ --output text end function lab--weight: 500;">status aws ec2 describe-instances --instance-ids $INSTANCE_ID \ --profile lab-sso \ --query "Reservations[0].Instances[0].State.Name" \ --output text end function lab--weight: 500;">start aws ec2 -weight: 500;">start-instances --instance-ids $INSTANCE_ID --profile lab-sso echo "Starting... wait 60 seconds" end function lab--weight: 500;">start aws ec2 -weight: 500;">start-instances --instance-ids $INSTANCE_ID --profile lab-sso echo "Starting... wait 60 seconds" end function lab--weight: 500;">start aws ec2 -weight: 500;">start-instances --instance-ids $INSTANCE_ID --profile lab-sso echo "Starting... wait 60 seconds" end function lab--weight: 500;">stop aws ec2 -weight: 500;">stop-instances --instance-ids $INSTANCE_ID --profile lab-sso echo "Stopping..." end function lab--weight: 500;">stop aws ec2 -weight: 500;">stop-instances --instance-ids $INSTANCE_ID --profile lab-sso echo "Stopping..." end function lab--weight: 500;">stop aws ec2 -weight: 500;">stop-instances --instance-ids $INSTANCE_ID --profile lab-sso echo "Stopping..." end function lab-connect aws ssm -weight: 500;">start-session --target $INSTANCE_ID --profile lab-sso end function lab-connect aws ssm -weight: 500;">start-session --target $INSTANCE_ID --profile lab-sso end function lab-connect aws ssm -weight: 500;">start-session --target $INSTANCE_ID --profile lab-sso end $ lab-connect Starting session with SessionId: your_username_here-... $ whoami ssm-user $ lab-connect Starting session with SessionId: your_username_here-... $ whoami ssm-user $ lab-connect Starting session with SessionId: your_username_here-... $ whoami ssm-user echo $INSTANCE_ID # prints the instance ID it will terminate lab--weight: 500;">status # confirms the instance is running echo $INSTANCE_ID # prints the instance ID it will terminate lab--weight: 500;">status # confirms the instance is running echo $INSTANCE_ID # prints the instance ID it will terminate lab--weight: 500;">status # confirms the instance is running function lab-terminate echo "WARNING: This will permanently delete the instance and all data on it." echo "Instance: $INSTANCE_ID" read --prompt-str "Type YES to confirm: " confirm if test "$confirm" = "YES" aws ec2 terminate-instances \ --instance-ids $INSTANCE_ID --profile lab-sso echo "Instance terminated. Use lab-create to launch a fresh one." else echo "Cancelled." end end function lab-terminate echo "WARNING: This will permanently delete the instance and all data on it." echo "Instance: $INSTANCE_ID" read --prompt-str "Type YES to confirm: " confirm if test "$confirm" = "YES" aws ec2 terminate-instances \ --instance-ids $INSTANCE_ID --profile lab-sso echo "Instance terminated. Use lab-create to launch a fresh one." else echo "Cancelled." end end function lab-terminate echo "WARNING: This will permanently delete the instance and all data on it." echo "Instance: $INSTANCE_ID" read --prompt-str "Type YES to confirm: " confirm if test "$confirm" = "YES" aws ec2 terminate-instances \ --instance-ids $INSTANCE_ID --profile lab-sso echo "Instance terminated. Use lab-create to launch a fresh one." else echo "Cancelled." end end function lab-create echo "Launching new suricata-ids-lab instance..." set new_id (aws ec2 run-instances \ --launch-template LaunchTemplateName=suricata-lab-template \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=suricata-ids-lab}]" \ --profile lab-sso \ --query "Instances[0].InstanceId" \ --output text) if test -z "$new_id" echo "ERROR: Failed to launch instance." return 1 end echo "Instance launched: $new_id" echo "Updating INSTANCE_ID..." set -U INSTANCE_ID $new_id echo "Waiting for instance to reach running state..." aws ec2 wait instance-running \ --instance-ids $new_id \ --profile lab-sso echo "Instance is running." echo "Waiting 3 minutes for SSM Agent to initialize..." sleep 180 echo "Ready. Run lab-connect to -weight: 500;">start your session." end function lab-create echo "Launching new suricata-ids-lab instance..." set new_id (aws ec2 run-instances \ --launch-template LaunchTemplateName=suricata-lab-template \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=suricata-ids-lab}]" \ --profile lab-sso \ --query "Instances[0].InstanceId" \ --output text) if test -z "$new_id" echo "ERROR: Failed to launch instance." return 1 end echo "Instance launched: $new_id" echo "Updating INSTANCE_ID..." set -U INSTANCE_ID $new_id echo "Waiting for instance to reach running state..." aws ec2 wait instance-running \ --instance-ids $new_id \ --profile lab-sso echo "Instance is running." echo "Waiting 3 minutes for SSM Agent to initialize..." sleep 180 echo "Ready. Run lab-connect to -weight: 500;">start your session." end function lab-create echo "Launching new suricata-ids-lab instance..." set new_id (aws ec2 run-instances \ --launch-template LaunchTemplateName=suricata-lab-template \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=suricata-ids-lab}]" \ --profile lab-sso \ --query "Instances[0].InstanceId" \ --output text) if test -z "$new_id" echo "ERROR: Failed to launch instance." return 1 end echo "Instance launched: $new_id" echo "Updating INSTANCE_ID..." set -U INSTANCE_ID $new_id echo "Waiting for instance to reach running state..." aws ec2 wait instance-running \ --instance-ids $new_id \ --profile lab-sso echo "Instance is running." echo "Waiting 3 minutes for SSM Agent to initialize..." sleep 180 echo "Ready. Run lab-connect to -weight: 500;">start your session." end function lab-snapshot-list echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso end function lab-snapshot-list echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso end function lab-snapshot-list echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso end $ lab-snapshot-list Your saved AMIs: +------------------------+---------------------------+----------------------+ | ami-0xxxxxxxxxxxxxxxxx | suricata_setup-2026-03-15 | 2026-03-15T12:00:00Z | +------------------------+---------------------------+----------------------+ $ lab-snapshot-list Your saved AMIs: +------------------------+---------------------------+----------------------+ | ami-0xxxxxxxxxxxxxxxxx | suricata_setup-2026-03-15 | 2026-03-15T12:00:00Z | +------------------------+---------------------------+----------------------+ $ lab-snapshot-list Your saved AMIs: +------------------------+---------------------------+----------------------+ | ami-0xxxxxxxxxxxxxxxxx | suricata_setup-2026-03-15 | 2026-03-15T12:00:00Z | +------------------------+---------------------------+----------------------+ function lab-snapshot echo "Your running instances:" aws ec2 describe-instances \ --filters "Name=instance-state-name,Values=running" \ --query "Reservations[*].Instances[*].[InstanceId,State.Name]" \ --output table \ --profile lab-sso read --prompt-str "Enter Instance ID to snapshot (i-...): " instance_id if test -z "$instance_id" echo "ERROR: No Instance ID entered. Cancelled." return 1 end read --prompt-str "Enter a name for this snapshot: " snapshot_name if test -z "$snapshot_name" echo "ERROR: No snapshot name entered. Cancelled." return 1 end set today (date +%Y-%m-%d) echo "Creating snapshot of $instance_id..." set ami_id (aws ec2 create-image \ --instance-id $instance_id \ --name "$snapshot_name-$today" \ --description "Lab snapshot: $snapshot_name" \ --no-reboot \ --profile lab-sso \ --query "ImageId" \ --output text) if test -z "$ami_id" echo "ERROR: Failed to create snapshot." return 1 end echo "Snapshot created: $ami_id" echo "Waiting for snapshot to become available..." aws ec2 wait image-available \ --image-ids $ami_id \ --profile lab-sso echo "Snapshot is ready: $ami_id" echo "Use this ID with lab-restore to launch from this state." end function lab-snapshot echo "Your running instances:" aws ec2 describe-instances \ --filters "Name=instance-state-name,Values=running" \ --query "Reservations[*].Instances[*].[InstanceId,State.Name]" \ --output table \ --profile lab-sso read --prompt-str "Enter Instance ID to snapshot (i-...): " instance_id if test -z "$instance_id" echo "ERROR: No Instance ID entered. Cancelled." return 1 end read --prompt-str "Enter a name for this snapshot: " snapshot_name if test -z "$snapshot_name" echo "ERROR: No snapshot name entered. Cancelled." return 1 end set today (date +%Y-%m-%d) echo "Creating snapshot of $instance_id..." set ami_id (aws ec2 create-image \ --instance-id $instance_id \ --name "$snapshot_name-$today" \ --description "Lab snapshot: $snapshot_name" \ --no-reboot \ --profile lab-sso \ --query "ImageId" \ --output text) if test -z "$ami_id" echo "ERROR: Failed to create snapshot." return 1 end echo "Snapshot created: $ami_id" echo "Waiting for snapshot to become available..." aws ec2 wait image-available \ --image-ids $ami_id \ --profile lab-sso echo "Snapshot is ready: $ami_id" echo "Use this ID with lab-restore to launch from this state." end function lab-snapshot echo "Your running instances:" aws ec2 describe-instances \ --filters "Name=instance-state-name,Values=running" \ --query "Reservations[*].Instances[*].[InstanceId,State.Name]" \ --output table \ --profile lab-sso read --prompt-str "Enter Instance ID to snapshot (i-...): " instance_id if test -z "$instance_id" echo "ERROR: No Instance ID entered. Cancelled." return 1 end read --prompt-str "Enter a name for this snapshot: " snapshot_name if test -z "$snapshot_name" echo "ERROR: No snapshot name entered. Cancelled." return 1 end set today (date +%Y-%m-%d) echo "Creating snapshot of $instance_id..." set ami_id (aws ec2 create-image \ --instance-id $instance_id \ --name "$snapshot_name-$today" \ --description "Lab snapshot: $snapshot_name" \ --no-reboot \ --profile lab-sso \ --query "ImageId" \ --output text) if test -z "$ami_id" echo "ERROR: Failed to create snapshot." return 1 end echo "Snapshot created: $ami_id" echo "Waiting for snapshot to become available..." aws ec2 wait image-available \ --image-ids $ami_id \ --profile lab-sso echo "Snapshot is ready: $ami_id" echo "Use this ID with lab-restore to launch from this state." end function lab-restore echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso read --prompt-str "Enter AMI ID to restore from (ami-...): " ami_id if test -z "$ami_id" echo "ERROR: No AMI ID entered. Cancelled." return 1 end set sg_id (aws ec2 describe-security-groups \ --filters "Name=group-name,Values=suricata-lab-sg" \ --query "SecurityGroups[0].GroupId" \ --output text \ --profile lab-sso) if test -z "$sg_id" echo "ERROR: Could not find security group suricata-lab-sg." return 1 end echo "Launching from snapshot $ami_id..." set new_id (aws ec2 run-instances \ --image-id $ami_id \ --instance-type t2.micro \ --iam-instance-profile Name=suricata-lab-ssm-role \ --security-group-ids $sg_id \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=suricata-ids-lab}]" \ --profile lab-sso \ --query "Instances[0].InstanceId" \ --output text) if test -z "$new_id" echo "ERROR: Failed to launch instance. Check AMI ID and try again." return 1 end echo "Instance launched: $new_id" echo "Updating INSTANCE_ID..." set -U INSTANCE_ID $new_id echo "Waiting for instance to reach running state..." aws ec2 wait instance-running \ --instance-ids $new_id \ --profile lab-sso echo "Instance is running." echo "Waiting 60 seconds for SSM Agent to initialize..." sleep 60 echo "Ready. Run lab-connect to -weight: 500;">start your session." end function lab-restore echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso read --prompt-str "Enter AMI ID to restore from (ami-...): " ami_id if test -z "$ami_id" echo "ERROR: No AMI ID entered. Cancelled." return 1 end set sg_id (aws ec2 describe-security-groups \ --filters "Name=group-name,Values=suricata-lab-sg" \ --query "SecurityGroups[0].GroupId" \ --output text \ --profile lab-sso) if test -z "$sg_id" echo "ERROR: Could not find security group suricata-lab-sg." return 1 end echo "Launching from snapshot $ami_id..." set new_id (aws ec2 run-instances \ --image-id $ami_id \ --instance-type t2.micro \ --iam-instance-profile Name=suricata-lab-ssm-role \ --security-group-ids $sg_id \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=suricata-ids-lab}]" \ --profile lab-sso \ --query "Instances[0].InstanceId" \ --output text) if test -z "$new_id" echo "ERROR: Failed to launch instance. Check AMI ID and try again." return 1 end echo "Instance launched: $new_id" echo "Updating INSTANCE_ID..." set -U INSTANCE_ID $new_id echo "Waiting for instance to reach running state..." aws ec2 wait instance-running \ --instance-ids $new_id \ --profile lab-sso echo "Instance is running." echo "Waiting 60 seconds for SSM Agent to initialize..." sleep 60 echo "Ready. Run lab-connect to -weight: 500;">start your session." end function lab-restore echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso read --prompt-str "Enter AMI ID to restore from (ami-...): " ami_id if test -z "$ami_id" echo "ERROR: No AMI ID entered. Cancelled." return 1 end set sg_id (aws ec2 describe-security-groups \ --filters "Name=group-name,Values=suricata-lab-sg" \ --query "SecurityGroups[0].GroupId" \ --output text \ --profile lab-sso) if test -z "$sg_id" echo "ERROR: Could not find security group suricata-lab-sg." return 1 end echo "Launching from snapshot $ami_id..." set new_id (aws ec2 run-instances \ --image-id $ami_id \ --instance-type t2.micro \ --iam-instance-profile Name=suricata-lab-ssm-role \ --security-group-ids $sg_id \ --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=suricata-ids-lab}]" \ --profile lab-sso \ --query "Instances[0].InstanceId" \ --output text) if test -z "$new_id" echo "ERROR: Failed to launch instance. Check AMI ID and try again." return 1 end echo "Instance launched: $new_id" echo "Updating INSTANCE_ID..." set -U INSTANCE_ID $new_id echo "Waiting for instance to reach running state..." aws ec2 wait instance-running \ --instance-ids $new_id \ --profile lab-sso echo "Instance is running." echo "Waiting 60 seconds for SSM Agent to initialize..." sleep 60 echo "Ready. Run lab-connect to -weight: 500;">start your session." end function lab-snapshot-delete echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso read --prompt-str "Enter AMI ID to delete (ami-...): " ami_id if test -z "$ami_id" echo "ERROR: No AMI ID entered. Cancelled." return 1 end set snapshot_id (aws ec2 describe-images \ --image-ids $ami_id \ --query "Images[0].BlockDeviceMappings[0].Ebs.SnapshotId" \ --output text \ --profile lab-sso) read --prompt-str "Delete $ami_id ($snapshot_id)? Type YES to confirm: " confirm if test "$confirm" != "YES" echo "Cancelled." return 0 end aws ec2 deregister-image \ --image-id $ami_id \ --profile lab-sso echo "AMI deregistered: $ami_id" aws ec2 delete-snapshot \ --snapshot-id $snapshot_id \ --profile lab-sso echo "Snapshot deleted: $snapshot_id" echo "Done. Storage charges for this snapshot will -weight: 500;">stop within the hour." end function lab-snapshot-delete echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso read --prompt-str "Enter AMI ID to delete (ami-...): " ami_id if test -z "$ami_id" echo "ERROR: No AMI ID entered. Cancelled." return 1 end set snapshot_id (aws ec2 describe-images \ --image-ids $ami_id \ --query "Images[0].BlockDeviceMappings[0].Ebs.SnapshotId" \ --output text \ --profile lab-sso) read --prompt-str "Delete $ami_id ($snapshot_id)? Type YES to confirm: " confirm if test "$confirm" != "YES" echo "Cancelled." return 0 end aws ec2 deregister-image \ --image-id $ami_id \ --profile lab-sso echo "AMI deregistered: $ami_id" aws ec2 delete-snapshot \ --snapshot-id $snapshot_id \ --profile lab-sso echo "Snapshot deleted: $snapshot_id" echo "Done. Storage charges for this snapshot will -weight: 500;">stop within the hour." end function lab-snapshot-delete echo "Your saved AMIs:" aws ec2 describe-images \ --owners self \ --query "Images[*].[ImageId,Name,CreationDate]" \ --output table \ --profile lab-sso read --prompt-str "Enter AMI ID to delete (ami-...): " ami_id if test -z "$ami_id" echo "ERROR: No AMI ID entered. Cancelled." return 1 end set snapshot_id (aws ec2 describe-images \ --image-ids $ami_id \ --query "Images[0].BlockDeviceMappings[0].Ebs.SnapshotId" \ --output text \ --profile lab-sso) read --prompt-str "Delete $ami_id ($snapshot_id)? Type YES to confirm: " confirm if test "$confirm" != "YES" echo "Cancelled." return 0 end aws ec2 deregister-image \ --image-id $ami_id \ --profile lab-sso echo "AMI deregistered: $ami_id" aws ec2 delete-snapshot \ --snapshot-id $snapshot_id \ --profile lab-sso echo "Snapshot deleted: $snapshot_id" echo "Done. Storage charges for this snapshot will -weight: 500;">stop within the hour." end lab-login # tap YubiKey → 8hr credentials lab-restore # launch from snapshot → wait → "Ready" lab-connect # shell as ssm-user lab-snapshot # save progress before ending session lab-terminate # zero ongoing cost lab-login # tap YubiKey → 8hr credentials lab-restore # launch from snapshot → wait → "Ready" lab-connect # shell as ssm-user lab-snapshot # save progress before ending session lab-terminate # zero ongoing cost lab-login # tap YubiKey → 8hr credentials lab-restore # launch from snapshot → wait → "Ready" lab-connect # shell as ssm-user lab-snapshot # save progress before ending session lab-terminate # zero ongoing cost - Train developers to write secure code - Prepare security engineers for technical interviews