Skip to content

Commit 20ee044

Browse files
committed
Add storage tutorials (batch 7)
1 parent 49f07d9 commit 20ee044

7 files changed

Lines changed: 409 additions & 0 deletions

File tree

tuts/107-amazon-efs-gs/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# EFS: Create a file system
2+
3+
## Source
4+
5+
https://docs.aws.amazon.com/efs/latest/ug/getting-started.html
6+
7+
## Use case
8+
9+
- **ID**: efs/getting-started
10+
- **Level**: intermediate
11+
- **Core actions**: `elasticfilesystem:CreateFileSystem`, `elasticfilesystem:CreateMountTarget`, `elasticfilesystem:PutLifecycleConfiguration`
12+
13+
## Steps
14+
15+
1. Create an encrypted file system
16+
2. Wait for the file system to be available
17+
3. Describe the file system
18+
4. Create a mount target in the default VPC
19+
5. Wait for the mount target
20+
6. Describe mount targets
21+
7. Set a lifecycle policy
22+
23+
## Resources created
24+
25+
| Resource | Type |
26+
|----------|------|
27+
| `tutorial-efs-<random>` | EFS file system |
28+
| Mount target in default VPC subnet | Mount target |
29+
30+
## Cost
31+
32+
Per-GB pricing. $0.30/GB-month (Standard), $0.025/GB-month (Infrequent Access). An empty file system has no storage cost. Clean up promptly to avoid charges.
33+
34+
## Duration
35+
36+
~114 seconds (mount target creation accounts for most of the wait)
37+
38+
## Related docs
39+
40+
- [Getting started with Amazon EFS](https://docs.aws.amazon.com/efs/latest/ug/getting-started.html)
41+
- [Creating file systems](https://docs.aws.amazon.com/efs/latest/ug/creating-using-create-fs.html)
42+
- [Creating mount targets](https://docs.aws.amazon.com/efs/latest/ug/accessing-fs.html)
43+
- [EFS lifecycle management](https://docs.aws.amazon.com/efs/latest/ug/lifecycle-management-efs.html)
44+
45+
---
46+
47+
## Appendix: Generation details
48+
49+
| Field | Value |
50+
|-------|-------|
51+
| Generation date | 2026-04-14 |
52+
| Source script | New, 117 lines |
53+
| Script test result | EXIT 0, 114s, 7 steps, no issues |
54+
| Issues encountered | Mount target deletion wait increased to 120s |
55+
| Iterations | v1 (direct to publish) |
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Create a file system with Amazon EFS
2+
3+
This tutorial shows you how to create an encrypted Amazon EFS file system, create a mount target in your default VPC, configure a lifecycle policy, and clean up all resources.
4+
5+
## Prerequisites
6+
7+
- AWS CLI configured with credentials and a default region
8+
- A default VPC with at least one subnet in the configured region
9+
- Permissions for `elasticfilesystem:CreateFileSystem`, `elasticfilesystem:DescribeFileSystems`, `elasticfilesystem:CreateMountTarget`, `elasticfilesystem:DescribeMountTargets`, `elasticfilesystem:PutLifecycleConfiguration`, `elasticfilesystem:DescribeLifecycleConfiguration`, `elasticfilesystem:DeleteMountTarget`, `elasticfilesystem:DeleteFileSystem`, `ec2:DescribeVpcs`, `ec2:DescribeSubnets`
10+
11+
## Step 1: Create an encrypted file system
12+
13+
```bash
14+
RANDOM_ID=$(openssl rand -hex 4)
15+
FS_TOKEN="tut-efs-${RANDOM_ID}"
16+
17+
FS_ID=$(aws efs create-file-system --creation-token "$FS_TOKEN" \
18+
--performance-mode generalPurpose \
19+
--throughput-mode bursting \
20+
--encrypted \
21+
--tags Key=Name,Value="tutorial-efs-${RANDOM_ID}" \
22+
--query 'FileSystemId' --output text)
23+
echo "File system ID: $FS_ID"
24+
```
25+
26+
The creation token is an idempotency key — calling `create-file-system` again with the same token returns the existing file system instead of creating a duplicate.
27+
28+
## Step 2: Wait for the file system to be available
29+
30+
```bash
31+
for i in $(seq 1 15); do
32+
STATE=$(aws efs describe-file-systems --file-system-id "$FS_ID" \
33+
--query 'FileSystems[0].LifeCycleState' --output text)
34+
echo "State: $STATE"
35+
[ "$STATE" = "available" ] && break
36+
sleep 5
37+
done
38+
```
39+
40+
## Step 3: Describe the file system
41+
42+
```bash
43+
aws efs describe-file-systems --file-system-id "$FS_ID" \
44+
--query 'FileSystems[0].{Id:FileSystemId,State:LifeCycleState,Encrypted:Encrypted,Performance:PerformanceMode,Size:SizeInBytes.Value}' \
45+
--output table
46+
```
47+
48+
## Step 4: Create a mount target
49+
50+
A mount target provides a network endpoint for mounting the file system. Create one in a subnet of your default VPC.
51+
52+
```bash
53+
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" \
54+
--query 'Vpcs[0].VpcId' --output text)
55+
SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \
56+
--query 'Subnets[0].SubnetId' --output text)
57+
58+
MT_ID=$(aws efs create-mount-target --file-system-id "$FS_ID" \
59+
--subnet-id "$SUBNET_ID" \
60+
--query 'MountTargetId' --output text)
61+
echo "Mount target: $MT_ID"
62+
```
63+
64+
You need one mount target per Availability Zone. This tutorial creates one for demonstration.
65+
66+
## Step 5: Wait for the mount target
67+
68+
Mount target creation typically takes 60–90 seconds.
69+
70+
```bash
71+
for i in $(seq 1 15); do
72+
MT_STATE=$(aws efs describe-mount-targets --mount-target-id "$MT_ID" \
73+
--query 'MountTargets[0].LifeCycleState' --output text)
74+
echo "State: $MT_STATE"
75+
[ "$MT_STATE" = "available" ] && break
76+
sleep 10
77+
done
78+
```
79+
80+
## Step 6: Describe mount targets
81+
82+
```bash
83+
aws efs describe-mount-targets --file-system-id "$FS_ID" \
84+
--query 'MountTargets[].{Id:MountTargetId,Subnet:SubnetId,State:LifeCycleState,IP:IpAddress}' \
85+
--output table
86+
```
87+
88+
## Step 7: Set a lifecycle policy
89+
90+
Move files not accessed for 30 days to the Infrequent Access (IA) storage class to reduce costs.
91+
92+
```bash
93+
aws efs put-lifecycle-configuration --file-system-id "$FS_ID" \
94+
--lifecycle-policies '[{"TransitionToIA":"AFTER_30_DAYS"}]'
95+
96+
aws efs describe-lifecycle-configuration --file-system-id "$FS_ID" \
97+
--query 'LifecyclePolicies' --output table
98+
```
99+
100+
## Cleanup
101+
102+
Delete mount targets first, then the file system. You must wait for mount targets to finish deleting before the file system can be removed.
103+
104+
```bash
105+
aws efs delete-mount-target --mount-target-id "$MT_ID"
106+
107+
# Wait for mount target deletion
108+
for i in $(seq 1 12); do
109+
MT_COUNT=$(aws efs describe-mount-targets --file-system-id "$FS_ID" \
110+
--query 'MountTargets | length(@)' --output text 2>/dev/null || echo "0")
111+
[ "$MT_COUNT" = "0" ] && break
112+
sleep 10
113+
done
114+
115+
aws efs delete-file-system --file-system-id "$FS_ID"
116+
```
117+
118+
EFS charges per GB stored ($0.30/GB-month for Standard, $0.025/GB-month for IA). An empty file system has no storage cost. The script automates all steps including cleanup:
119+
120+
```bash
121+
bash amazon-efs-gs.sh
122+
```
123+
124+
## Related resources
125+
126+
- [Getting started with Amazon EFS](https://docs.aws.amazon.com/efs/latest/ug/getting-started.html)
127+
- [Creating file systems](https://docs.aws.amazon.com/efs/latest/ug/creating-using-create-fs.html)
128+
- [Creating mount targets](https://docs.aws.amazon.com/efs/latest/ug/accessing-fs.html)
129+
- [EFS lifecycle management](https://docs.aws.amazon.com/efs/latest/ug/lifecycle-management-efs.html)
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/bin/bash
2+
# Tutorial: Create an Amazon EFS file system
3+
# Source: https://docs.aws.amazon.com/efs/latest/ug/getting-started.html
4+
5+
WORK_DIR=$(mktemp -d)
6+
LOG_FILE="$WORK_DIR/efs-$(date +%Y%m%d-%H%M%S).log"
7+
exec > >(tee -a "$LOG_FILE") 2>&1
8+
9+
REGION=${AWS_DEFAULT_REGION:-${AWS_REGION:-$(aws configure get region 2>/dev/null)}}
10+
if [ -z "$REGION" ]; then
11+
echo "ERROR: No AWS region configured. Set one with: export AWS_DEFAULT_REGION=us-east-1"
12+
exit 1
13+
fi
14+
export AWS_DEFAULT_REGION="$REGION"
15+
echo "Region: $REGION"
16+
17+
RANDOM_ID=$(openssl rand -hex 4)
18+
FS_TOKEN="tut-efs-${RANDOM_ID}"
19+
20+
handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; }
21+
trap 'handle_error $LINENO' ERR
22+
23+
cleanup() {
24+
echo ""
25+
echo "Cleaning up resources..."
26+
if [ -n "$FS_ID" ]; then
27+
# Delete mount targets first
28+
for MT_ID in $(aws efs describe-mount-targets --file-system-id "$FS_ID" \
29+
--query 'MountTargets[].MountTargetId' --output text 2>/dev/null); do
30+
aws efs delete-mount-target --mount-target-id "$MT_ID" 2>/dev/null
31+
echo " Deleted mount target $MT_ID"
32+
done
33+
# Wait for mount targets to be deleted
34+
for i in $(seq 1 12); do
35+
MT_COUNT=$(aws efs describe-mount-targets --file-system-id "$FS_ID" \
36+
--query 'MountTargets | length(@)' --output text 2>/dev/null || echo "0")
37+
[ "$MT_COUNT" = "0" ] && break
38+
sleep 10
39+
done
40+
aws efs delete-file-system --file-system-id "$FS_ID" 2>/dev/null && \
41+
echo " Deleted file system $FS_ID"
42+
fi
43+
rm -rf "$WORK_DIR"
44+
echo "Cleanup complete."
45+
}
46+
47+
# Step 1: Create a file system
48+
echo "Step 1: Creating EFS file system"
49+
FS_ID=$(aws efs create-file-system --creation-token "$FS_TOKEN" \
50+
--performance-mode generalPurpose \
51+
--throughput-mode bursting \
52+
--encrypted \
53+
--tags Key=Name,Value="tutorial-efs-${RANDOM_ID}" \
54+
--query 'FileSystemId' --output text)
55+
echo " File system ID: $FS_ID"
56+
57+
# Step 2: Wait for file system to be available
58+
echo "Step 2: Waiting for file system to be available..."
59+
for i in $(seq 1 15); do
60+
STATE=$(aws efs describe-file-systems --file-system-id "$FS_ID" \
61+
--query 'FileSystems[0].LifeCycleState' --output text)
62+
echo " State: $STATE"
63+
[ "$STATE" = "available" ] && break
64+
sleep 5
65+
done
66+
67+
# Step 3: Describe the file system
68+
echo "Step 3: File system details"
69+
aws efs describe-file-systems --file-system-id "$FS_ID" \
70+
--query 'FileSystems[0].{Id:FileSystemId,State:LifeCycleState,Encrypted:Encrypted,Performance:PerformanceMode,Size:SizeInBytes.Value}' --output table
71+
72+
# Step 4: Create a mount target
73+
echo "Step 4: Creating mount target"
74+
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --query 'Vpcs[0].VpcId' --output text)
75+
SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" --query 'Subnets[0].SubnetId' --output text)
76+
echo " VPC: $VPC_ID, Subnet: $SUBNET_ID"
77+
78+
MT_ID=$(aws efs create-mount-target --file-system-id "$FS_ID" --subnet-id "$SUBNET_ID" \
79+
--query 'MountTargetId' --output text)
80+
echo " Mount target: $MT_ID"
81+
82+
# Step 5: Wait for mount target
83+
echo "Step 5: Waiting for mount target to be available..."
84+
for i in $(seq 1 15); do
85+
MT_STATE=$(aws efs describe-mount-targets --mount-target-id "$MT_ID" \
86+
--query 'MountTargets[0].LifeCycleState' --output text)
87+
echo " State: $MT_STATE"
88+
[ "$MT_STATE" = "available" ] && break
89+
sleep 10
90+
done
91+
92+
# Step 6: Describe mount targets
93+
echo "Step 6: Mount target details"
94+
aws efs describe-mount-targets --file-system-id "$FS_ID" \
95+
--query 'MountTargets[].{Id:MountTargetId,Subnet:SubnetId,State:LifeCycleState,IP:IpAddress}' --output table
96+
97+
# Step 7: Set lifecycle policy
98+
echo "Step 7: Setting lifecycle policy (move to IA after 30 days)"
99+
aws efs put-lifecycle-configuration --file-system-id "$FS_ID" \
100+
--lifecycle-policies "[{\"TransitionToIA\":\"AFTER_30_DAYS\"}]" > /dev/null
101+
aws efs describe-lifecycle-configuration --file-system-id "$FS_ID" \
102+
--query 'LifecyclePolicies' --output table
103+
104+
echo ""
105+
echo "Tutorial complete."
106+
echo "To mount: sudo mount -t nfs4 $FS_ID.efs.$REGION.amazonaws.com:/ /mnt/efs"
107+
echo ""
108+
echo "Do you want to clean up all resources? (y/n): "
109+
read -r CHOICE
110+
if [[ "$CHOICE" =~ ^[Yy]$ ]]; then
111+
cleanup
112+
else
113+
echo "Resources left running. EFS charges per GB stored."
114+
echo "Manual cleanup:"
115+
echo " aws efs delete-mount-target --mount-target-id $MT_ID"
116+
echo " sleep 60"
117+
echo " aws efs delete-file-system --file-system-id $FS_ID"
118+
fi
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
WORK_DIR=$(mktemp -d)
3+
exec > >(tee -a "$WORK_DIR/glacier-$(date +%Y%m%d-%H%M%S).log") 2>&1
4+
REGION=${AWS_DEFAULT_REGION:-$(aws configure get region 2>/dev/null)}
5+
[ -z "$REGION" ] && echo "ERROR: No region" && exit 1
6+
export AWS_DEFAULT_REGION="$REGION"
7+
echo "Region: $REGION"
8+
RANDOM_ID=$(openssl rand -hex 4)
9+
VAULT_NAME="tut-vault-${RANDOM_ID}"
10+
handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; }
11+
trap 'handle_error $LINENO' ERR
12+
cleanup() { echo ""; echo "Cleaning up..."; aws glacier delete-vault --vault-name "$VAULT_NAME" 2>/dev/null && echo " Deleted vault $VAULT_NAME"; rm -rf "$WORK_DIR"; echo "Done."; }
13+
echo "Step 1: Creating vault: $VAULT_NAME"
14+
aws glacier create-vault --vault-name "$VAULT_NAME" --account-id -
15+
echo " Vault created"
16+
echo "Step 2: Describing vault"
17+
aws glacier describe-vault --vault-name "$VAULT_NAME" --account-id - --query '{Name:VaultName,ARN:VaultARN,Created:CreationDate}' --output table
18+
echo "Step 3: Uploading an archive"
19+
echo "Hello from Glacier tutorial" > "$WORK_DIR/archive.txt"
20+
ARCHIVE_ID=$(aws glacier upload-archive --vault-name "$VAULT_NAME" --account-id - --body "$WORK_DIR/archive.txt" --query 'archiveId' --output text)
21+
echo " Archive ID: ${ARCHIVE_ID:0:40}..."
22+
echo "Step 4: Listing vaults"
23+
aws glacier list-vaults --account-id - --query 'VaultList[?starts_with(VaultName, `tut-`)].{Name:VaultName,Archives:NumberOfArchives,Size:SizeInBytes}' --output table
24+
echo "Step 5: Initiating inventory retrieval"
25+
JOB_ID=$(aws glacier initiate-job --vault-name "$VAULT_NAME" --account-id - --job-parameters '{"Type":"inventory-retrieval"}' --query 'jobId' --output text)
26+
echo " Job ID: ${JOB_ID:0:40}..."
27+
echo " (Inventory retrieval takes 3-5 hours — not waiting)"
28+
echo ""
29+
echo "Tutorial complete."
30+
echo "Note: The vault contains an archive and cannot be deleted until the archive is removed."
31+
echo "Archive deletion takes 24 hours to process. The vault will need manual cleanup later."
32+
echo "Do you want to attempt cleanup? (y/n): "
33+
read -r CHOICE
34+
[[ "$CHOICE" =~ ^[Yy]$ ]] && { echo "Deleting archive (takes 24h to process)..."; aws glacier delete-archive --vault-name "$VAULT_NAME" --account-id - --archive-id "$ARCHIVE_ID" 2>/dev/null; echo " Archive deletion initiated. Delete vault after 24h:"; echo " aws glacier delete-vault --vault-name $VAULT_NAME --account-id -"; rm -rf "$WORK_DIR"; } || echo "Manual: aws glacier delete-archive then delete-vault"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
WORK_DIR=$(mktemp -d); exec > >(tee -a "$WORK_DIR/s3-events.log") 2>&1
3+
REGION=${AWS_DEFAULT_REGION:-$(aws configure get region 2>/dev/null)}; [ -z "$REGION" ] && echo "ERROR: No region" && exit 1; export AWS_DEFAULT_REGION="$REGION"; ACCOUNT=$(aws sts get-caller-identity --query 'Account' --output text); echo "Region: $REGION"
4+
RANDOM_ID=$(openssl rand -hex 4); BUCKET="s3-events-tut-${RANDOM_ID}-${ACCOUNT}"; QUEUE="s3-events-tut-${RANDOM_ID}"
5+
handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; }; trap 'handle_error $LINENO' ERR
6+
cleanup() { echo ""; echo "Cleaning up..."; aws s3 rm "s3://$BUCKET" --recursive --quiet 2>/dev/null; aws s3 rb "s3://$BUCKET" 2>/dev/null && echo " Deleted bucket"; [ -n "$QUEUE_URL" ] && aws sqs delete-queue --queue-url "$QUEUE_URL" 2>/dev/null && echo " Deleted queue"; rm -rf "$WORK_DIR"; echo "Done."; }
7+
echo "Step 1: Creating SQS queue for notifications"
8+
QUEUE_URL=$(aws sqs create-queue --queue-name "$QUEUE" --query 'QueueUrl' --output text)
9+
QUEUE_ARN=$(aws sqs get-queue-attributes --queue-url "$QUEUE_URL" --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
10+
aws sqs set-queue-attributes --queue-url "$QUEUE_URL" --attributes "{\"Policy\":\"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"Service\\\":\\\"s3.amazonaws.com\\\"},\\\"Action\\\":\\\"sqs:SendMessage\\\",\\\"Resource\\\":\\\"$QUEUE_ARN\\\"}]}\"}"
11+
echo " Queue: $QUEUE_URL"
12+
echo "Step 2: Creating bucket with event notification"
13+
if [ "$REGION" = "us-east-1" ]; then aws s3api create-bucket --bucket "$BUCKET" > /dev/null; else aws s3api create-bucket --bucket "$BUCKET" --create-bucket-configuration LocationConstraint="$REGION" > /dev/null; fi
14+
aws s3api put-bucket-notification-configuration --bucket "$BUCKET" --notification-configuration "{\"QueueConfigurations\":[{\"QueueArn\":\"$QUEUE_ARN\",\"Events\":[\"s3:ObjectCreated:*\"]}]}"
15+
echo " Notifications configured"
16+
echo "Step 3: Uploading a file to trigger notification"
17+
echo "test data" > "$WORK_DIR/test.txt"
18+
aws s3 cp "$WORK_DIR/test.txt" "s3://$BUCKET/test.txt" --quiet
19+
sleep 5
20+
echo "Step 4: Reading notification from SQS"
21+
aws sqs receive-message --queue-url "$QUEUE_URL" --max-number-of-messages 1 --wait-time-seconds 10 --query 'Messages[0].Body' --output text 2>/dev/null | python3 -c "import sys,json;d=json.loads(sys.stdin.read());r=d.get('Records',[{}])[0];print(f\" Event: {r.get('eventName','?')}\\n Bucket: {r.get('s3',{}).get('bucket',{}).get('name','?')}\\n Key: {r.get('s3',{}).get('object',{}).get('key','?')}\")" 2>/dev/null || echo " No notification yet"
22+
echo ""; echo "Tutorial complete."
23+
echo "Do you want to clean up? (y/n): "; read -r CHOICE; [[ "$CHOICE" =~ ^[Yy]$ ]] && cleanup
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
WORK_DIR=$(mktemp -d); exec > >(tee -a "$WORK_DIR/presign.log") 2>&1
3+
REGION=${AWS_DEFAULT_REGION:-$(aws configure get region 2>/dev/null)}; [ -z "$REGION" ] && echo "ERROR: No region" && exit 1; export AWS_DEFAULT_REGION="$REGION"; ACCOUNT=$(aws sts get-caller-identity --query 'Account' --output text); echo "Region: $REGION"
4+
RANDOM_ID=$(openssl rand -hex 4); BUCKET="presign-tut-${RANDOM_ID}-${ACCOUNT}"
5+
handle_error() { echo "ERROR on line $1"; trap - ERR; cleanup; exit 1; }; trap 'handle_error $LINENO' ERR
6+
cleanup() { echo ""; echo "Cleaning up..."; aws s3 rm "s3://$BUCKET" --recursive --quiet 2>/dev/null; aws s3 rb "s3://$BUCKET" 2>/dev/null && echo " Deleted bucket"; rm -rf "$WORK_DIR"; echo "Done."; }
7+
echo "Step 1: Creating bucket"
8+
if [ "$REGION" = "us-east-1" ]; then aws s3api create-bucket --bucket "$BUCKET" > /dev/null; else aws s3api create-bucket --bucket "$BUCKET" --create-bucket-configuration LocationConstraint="$REGION" > /dev/null; fi
9+
echo "Step 2: Uploading a file"
10+
echo "Hello from presigned URL tutorial" > "$WORK_DIR/data.txt"
11+
aws s3 cp "$WORK_DIR/data.txt" "s3://$BUCKET/data.txt" --quiet
12+
echo "Step 3: Generating presigned download URL (expires in 5 min)"
13+
DOWNLOAD_URL=$(aws s3 presign "s3://$BUCKET/data.txt" --expires-in 300)
14+
echo " URL: ${DOWNLOAD_URL:0:80}..."
15+
echo "Step 4: Testing presigned download"
16+
curl -s "$DOWNLOAD_URL"
17+
echo ""
18+
echo "Step 5: Generating presigned upload URL"
19+
UPLOAD_URL=$(aws s3 presign "s3://$BUCKET/uploaded.txt" --expires-in 300)
20+
echo " Upload URL generated (expires in 5 min)"
21+
echo "Step 6: Listing objects"
22+
aws s3api list-objects-v2 --bucket "$BUCKET" --query 'Contents[].{Key:Key,Size:Size}' --output table
23+
echo ""; echo "Tutorial complete."
24+
echo "Do you want to clean up? (y/n): "; read -r CHOICE; [[ "$CHOICE" =~ ^[Yy]$ ]] && cleanup

0 commit comments

Comments
 (0)