In the previous lesson we toured the console and created our first bucket with the mouse. It worked, but we left one question open: how do you repeat that exact thing a hundred times, or how do you prove six months from now what you actually did? The answer is to stop clicking and start writing commands and code.
This lesson is the gateway to all serious work on AWS. The AWS CLI is the tool you will use every day to query, create and diagnose; the SDKs are the libraries your application uses to talk to AWS from the inside. Both call the same APIs as the console, so everything you learn here applies to any service in the rest of the course.
Contents
- Why automate: the problem with the console
- Installing AWS CLI v2 on Linux, macOS and Windows
- Anatomy of a command
- Configuring credentials:
aws configure, files and profiles - Credential precedence
- Access key security
- Output formats and filtering with
--query - Pagination,
--dry-runand other useful options - Essential exploration commands
- SDKs: what they are and which languages have one
- boto3: the Python SDK step by step
- An equivalent look in JavaScript
- CloudShell as a no-install alternative
Why automate: the problem with the console
Marta created the test bucket in the console. Now Luis needs to create three identical buckets (one per environment) with the same tags and the same configuration. With the console he would have to repeat about fifteen clicks three times over, and any slip would produce a silent difference between environments.
The four structural problems with the console:
| Problem | Real consequence at MercadoFresco |
|---|---|
| It is not reproducible | The development environment never ends up identical to production, and bugs "only happen in production" |
| It is not documentable | Nobody can review in Git what was done with the mouse, or find out why |
| It is not composable | You cannot chain "list every stopped instance and tag them" |
| It does not scale | Tagging 200 resources by hand is unworkable |
The CLI solves all four: a command is text, and text can be saved, versioned, reviewed, repeated and chained. And it is the natural stepping stone towards the infrastructure as code of module 9, where we will not even write commands any more, but declarations of the desired state.
Installing AWS CLI v2 on Linux, macOS and Windows
Always use version 2. v1 is in maintenance mode, it used to be installed with pip (which caused
dependency conflicts) and it lacks important features such as signing in with IAM
Identity Center.
Linux (x86_64)
# 1. Download the official installer as a zip file
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
# 2. Unpack the archive into the current directory
unzip awscliv2.zip
# 3. Run the installer with administrator privileges
sudo ./aws/install
# 4. Clean up the temporary files
rm -rf awscliv2.zip aws/What each line does:
curl ... -o "awscliv2.zip": downloads the package and saves it under that name. The URL is the official AWS one; be wary of third-party installers.unzip: extracts anaws/folder containing the installer.sudo ./aws/install: copies the binaries to/usr/local/aws-cliand creates the/usr/local/bin/awslink. It needssudobecause it writes outside your home directory.rm -rf: deletes what you downloaded, which is no longer needed.
If your machine is ARM (a Graviton instance, for example), swap x86_64 for aarch64.
To upgrade an existing installation, add the update parameters:
macOS
# Download the official .pkg package
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
# Install for every user on the machine
sudo installer -pkg AWSCLIV2.pkg -target /An alternative with Homebrew, if you already use it: brew install awscli.
Windows
Download and run the official MSI installer:
Or from PowerShell as administrator:
Afterwards close and reopen the terminal so that the PATH variable is reloaded.
Verification
On any system:
Expected output (the numbers will vary):
Read it like this: CLI version, embedded Python version (it does not use your Python, it ships with
its own), operating system and build type. If you see aws-cli/1.x, you have v1 and you should
uninstall it before carrying on.
Anatomy of a command
Every command follows the same structure:
A real example, line by line:
aws ec2 describe-instances \
--filters "Name=instance-state-name,Values=running" \
--region eu-west-1 \
--output table| Part | What it is | Detail |
|---|---|---|
aws |
The program | — |
ec2 |
Service | Usually matches the service name: s3, iam, lambda, rds |
describe-instances |
Operation | The API is called DescribeInstances; the CLI uses lower case and hyphens |
--filters "..." |
Operation-specific parameter | Filters on the server: running instances only |
--region |
Global option | Overrides the profile's region for this command |
--output table |
Global option | Output format |
The verbs follow very predictable conventions, and recognising them speeds up learning enormously:
| Prefix | What it does | Examples |
|---|---|---|
describe- |
Returns detailed information | describe-instances, describe-vpcs |
list- |
Returns a list of identifiers | list-buckets, list-functions |
get- |
Fetches one specific item | get-caller-identity, get-bucket-tagging |
create- |
Creates a resource | create-bucket, create-tags |
delete- / terminate- |
Removes something | delete-bucket, terminate-instances |
put- |
Writes or overwrites a configuration | put-bucket-tagging, put-metric-alarm |
The built-in help
It is the best documentation you have and it works offline:
aws help # lists every available service
aws s3api help # lists every s3api operation
aws s3api create-bucket help # every parameter of that operation, with examplesIt opens in a pager: navigate with the arrow keys and quit with q.
Configuring credentials: aws configure, files and profiles
The CLI needs to know who you are. We are going to create access keys for the
mercadofresco-admin user we created in lesson 01-02.
Step 1: create the access keys
In the console: IAM → Users → mercadofresco-admin → Security credentials → Create access key. Choose the Command Line Interface (CLI) use case, confirm the warning and create.
You will get two values:
- Access key ID: something like
AKIAIOSFODNN7EXAMPLE. It is public-ish; it identifies the key. - Secret access key: something like
wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY. It is shown only once. If you lose it, you have to create a new key.
Warning: never create access keys for the root user (lesson 01-02). And if you would rather not handle permanent keys at all, the modern alternative is
aws configure ssowith IAM Identity Center, which issues temporary credentials.
Step 2: aws configure
It will ask you four questions:
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY Default region name [None]: eu-west-1 Default output format [None]: json
- Default region:
eu-west-1, the one we settled on in lesson 01-03. It saves you writing--regionon every command. - Output format:
jsonis a good default value because it is the one consumed by other tools.
The configuration files
aws configure performs no magic: it writes two text files in your home directory
(~/.aws/ on Linux and macOS, %USERPROFILE%\.aws\ on Windows).
~/.aws/credentials — holds the secrets:
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY~/.aws/config — holds the non-secret settings:
You can edit them by hand perfectly well. Check as well that the permissions are restrictive:
This leaves the file readable and writable by your user only. On a shared machine it is essential.
Named profiles
You will almost never work with a single identity. MercadoFresco will soon have a development environment and a production one, and it is important that the two cannot be confused.
Create a new profile:
The files then look like this:
# ~/.aws/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
[mercadofresco-dev]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = je7MtGbClwBF/2Zp9Utk/h3yCoEXAMPLEKEY# ~/.aws/config
[default]
region = eu-west-1
output = json
[profile mercadofresco-dev]
region = eu-west-1
output = jsonMind the detail: in credentials the section is [mercadofresco-dev], but in config it is
[profile mercadofresco-dev], with the word profile in front. It is a historical inconsistency
that causes constant errors. If a profile "cannot be found", check this first.
To use a profile:
# Option 1: as a parameter, on one specific command
aws s3 ls --profile mercadofresco-dev
# Option 2: as an environment variable, for the whole terminal session
export AWS_PROFILE=mercadofresco-dev
aws s3 lsAnd to always know who you are acting as:
Get into the habit of running this command before any destructive operation. It is the difference between deleting the development database and deleting the production one.
Credential precedence
When you run a command, the CLI looks for credentials in a fixed order and stops at the first source it finds. Knowing this order explains 90 % of all "but I did configure that" moments:
| # | Source | How it is given | When it is used |
|---|---|---|---|
| 1 | Command line options | --profile, --region |
Overrides everything else |
| 2 | Environment variables | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_PROFILE, AWS_DEFAULT_REGION |
CI/CD, containers, temporary sessions |
| 3 | The ~/.aws/credentials file |
The [default] profile or the one you name |
Day-to-day work on your laptop |
| 4 | The ~/.aws/config file |
The [profile ...] section |
Configuration and roles |
| 5 | Container credentials | A variable injected by ECS/EKS | Containers inside AWS (module 10) |
| 6 | Instance role (IMDS) | The EC2 instance's own metadata | The correct way inside AWS |
The last two are the conceptually important ones. When your code runs inside AWS —on an EC2 instance, in a Lambda function, in a container— it needs no keys at all: it is assigned an IAM role and the SDK automatically obtains temporary credentials that rotate themselves every few hours.
flowchart TB
A["Command or SDK needs credentials"] --> B{"--profile on the line?"}
B -->|yes| USA1["Use that profile"]
B -->|no| C{"Environment variables?"}
C -->|yes| USA2["Use the variables"]
C -->|no| D{"~/.aws/credentials file?"}
D -->|yes| USA3["Use the default profile"]
D -->|no| E{"Running inside AWS?"}
E -->|yes| USA4["Instance role - temporary credentials - THE IDEAL"]
E -->|no| F["Error: Unable to locate credentials"]
IAM roles are studied in depth in lesson 04-01. Hold on to the idea: permanent keys on your laptop only; inside AWS, always roles.
Access key security
An access key for mercadofresco-admin is, quite literally, full control of the account. These
rules are not optional.
Never in the code and never in Git
This is the mistake that ruins accounts every single day. There are bots continuously scanning
GitHub for strings starting with AKIA; a published key is exploited within minutes, typically
by launching dozens of large instances to mine cryptocurrency. The resulting bill can run into
thousands of euros.
Minimum protection in any repository:
And an automatic barrier, which is highly recommended:
# git-secrets scans every commit for AWS credential patterns
git secrets --install
git secrets --register-awsIf you ever publish a key by mistake: deactivate and delete it in IAM immediately, check CloudTrail (lesson 05-03) to see what was done with it, and create a new one. Deleting the commit achieves nothing: it has already been indexed.
Other rules
| Rule | Why |
|---|---|
| Rotate every 90 days | Shrinks the exploitation window for a leaked key |
| One key per person and use | Lets you revoke one without affecting the rest |
| Delete inactive keys | IAM shows the last-used date; whatever is not used gets deleted |
| Never keys on root | Already covered in 01-02 |
| Inside AWS, roles instead of keys | Temporary credentials, rotated automatically |
| MFA for sensitive operations | It can be required in IAM policies (lesson 04-01) |
The procedure for rotating without downtime is simple: create a second key, update wherever it is used, check that everything works, deactivate the old one, wait a few days and delete it. IAM allows two active keys per user precisely for this.
Output formats and filtering with --query
AWS commands return a great deal of information. Learning to trim it is what turns the CLI into a comfortable tool.
Output formats
| Format | When to use it |
|---|---|
json |
The default. Ideal for piping into jq or processing in scripts |
table |
For reading with your eyes: ASCII borders and columns |
text |
For processing with grep, awk or cut; tab separated |
yaml |
More readable than JSON for long configurations |
--query with JMESPath
--query applies a JMESPath expression to the response, on the client. Let us build up step
by step.
Level 1 — extract a list:
Level 2 — one field from each element:
# From each region, just its name. The [] brackets walk the list
aws ec2 describe-regions --query 'Regions[].RegionName' --output textOutput:
Level 3 — rename fields into an object:
aws ec2 describe-regions \
--query 'Regions[].{Region:RegionName, Endpoint:Endpoint}' \
--output tableThe braces {...} build a new object; to the left of the : goes the name you want to see, to the
right the original field.
Level 4 — filter with a condition:
# European regions only: ? introduces the filter, contains() is a JMESPath function
aws ec2 describe-regions \
--query 'Regions[?contains(RegionName, `eu-`)].RegionName' \
--output tableNote the backticks around eu-: in JMESPath, literals go inside backticks, not inside ordinary
quotes. It is a frequent source of errors.
Level 5 — a real MercadoFresco case:
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{
Id:InstanceId,
Type:InstanceType,
State:State.Name,
IP:PrivateIpAddress,
Zone:Placement.AvailabilityZone,
Name:Tags[?Key==`Name`]|[0].Value
}' \
--output tableBreakdown:
Reservations[].Instances[]: flattens EC2's nested structure (instances come grouped by reservation) into a single list.State.Name: navigates inside a nested object with the dot.Tags[?Key==\Name`]|[0].Value: filters the tags down to the one with keyName, and the|[0]` takes the first result of that list in order to extract its value.
--query versus --filters
--filters |
--query |
|
|---|---|---|
| Where it is applied | On the AWS server | On your machine, after receiving the response |
| Effect | Reduces what is transferred | Only reduces what is displayed |
| Availability | Only on some services and fields | On any command |
| Recommendation | Use it whenever it exists | To shape the result |
The ideal is to combine them: --filters to bring back little, --query to present it well.
Pagination, --dry-run and other useful options
Pagination
When there are many results, the CLI v2 paginates them automatically and opens a pager. Options to control it:
# Returns at most 5 items
aws s3api list-buckets --max-items 5
# Disables the interactive pager (useful in scripts)
aws ec2 describe-instances --no-cli-pager
# Disable it permanently
export AWS_PAGER=""If you limit with --max-items and there are more results, the response includes a NextToken that
you can pass with --starting-token to request the next page.
--dry-run
Many EC2 operations accept --dry-run: they check whether you would have permission to perform
the operation, but they do not run it.
If you have permission, you will see:
An error occurred (DryRunOperation) when calling the RunInstances operation: Request would have succeeded, but DryRun flag is set
That message, even though it says "error", is the confirmation of success. If you did not have
permission, the error would be UnauthorizedOperation. It is a safe way of verifying permissions
without creating anything and without spending anything.
Other options that get a lot of use
| Option | What for |
|---|---|
--no-cli-pager |
Direct output with no pager |
--debug |
Full trace of the HTTP request; indispensable for diagnosing |
--cli-input-json file://params.json |
Pass every parameter from a file |
--generate-cli-skeleton |
Generate the JSON parameter template for an operation |
--endpoint-url |
Point at an alternative endpoint (a local emulator, for instance) |
Essential exploration commands
These are the commands you will run hundreds of times. Keep them somewhere safe.
# 2. Which regions exist and are enabled in my account?
aws ec2 describe-regions --query 'Regions[].RegionName' --output text# 3. Which Availability Zones does my region have, with their physical ID?
aws ec2 describe-availability-zones \
--query 'AvailabilityZones[].{AZ:ZoneName, Id:ZoneId, State:State}' \
--output table# 5. Which EC2 instances do I have, and in what state?
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{Id:InstanceId, State:State.Name}' \
--output tableThe output of the last one is especially useful because it indicates the origin of each value:
Name Value Type Location
---- ----- ---- --------
profile mercadofresco-dev env AWS_PROFILE
access_key ****************MPLE shared-credentials-file
region eu-west-1 config-file ~/.aws/configWhen something does not work the way you expect, this command tells you exactly which credential and which region the CLI is using, and why.
SDKs: what they are and which languages have one
An SDK (Software Development Kit) is a library that lets your application call the AWS APIs from the language it is written in, without building signed HTTP requests by hand.
The difference from the CLI:
| CLI | SDK | |
|---|---|---|
| Who uses it | A person at a terminal, or a script | Your application, at runtime |
| Typical case | "List the buckets", "stop that instance" | "When the customer uploads a photo, store it in S3" |
| Format | Text commands | Code in your language |
Languages with an official SDK:
| Language | SDK name | Note |
|---|---|---|
| Python | boto3 | The most widely used for scripting and automation |
| JavaScript / TypeScript | AWS SDK for JavaScript v3 | Modular: you install only the client you use |
| Java | AWS SDK for Java 2.x | Very common in the enterprise |
| .NET (C#) | AWS SDK for .NET | — |
| Go | AWS SDK for Go v2 | Common in infrastructure tooling |
| PHP | AWS SDK for PHP | Relevant to MercadoFresco's current monolith |
| Rust, Ruby, C++, Kotlin, Swift | — | Available |
They all share the same logic: a credential provider chain (the same order of precedence we saw), automatic retries with exponential backoff, and operation names equivalent to those of the API.
boto3: the Python SDK step by step
Luis is going to use Python for MercadoFresco's scripts, so let us look at boto3 in detail.
Installation
# Create an isolated virtual environment (good practice: do not install into the system Python)
python3 -m venv venv
# Activate it (on Windows: venv\Scripts\activate)
source venv/bin/activate
# Install boto3
pip install boto3Client versus resource
boto3 offers two different interfaces to the same thing:
| Client | Resource | |
|---|---|---|
| Level | Low: mirrors the API 1:1 | High: object-oriented |
| Coverage | Every service and operation | Only some services; in maintenance |
| Style | s3.list_buckets() returns a dictionary |
for b in s3.buckets.all() |
| Recommendation | Use it by default | Only if you run into it in legacy code |
The same goal with both:
import boto3
# --- Client interface (recommended) ---
s3_client = boto3.client("s3")
response = s3_client.list_buckets() # returns a dictionary exactly as the API gives it
for bucket in response["Buckets"]:
print(bucket["Name"], bucket["CreationDate"])
# --- Resource interface (older) ---
s3_resource = boto3.resource("s3")
for bucket in s3_resource.buckets.all(): # objects with attributes
print(bucket.name, bucket.creation_date)Credentials and sessions
boto3 uses exactly the same precedence order as the CLI, so if aws s3 ls works, your Python
code will work with nothing to configure.
To choose a profile or a region explicitly you use a session:
import boto3
# A session wraps credentials + region
session = boto3.Session(profile_name="mercadofresco-dev", region_name="eu-west-1")
s3 = session.client("s3")
print(session.client("sts").get_caller_identity()["Arn"])Error handling with ClientError
Every error returned by an AWS service arrives as botocore.exceptions.ClientError. Ignoring it is
the most common mistake in a beginner's scripts.
import boto3
from botocore.exceptions import ClientError
s3 = boto3.client("s3", region_name="eu-west-1")
def bucket_exists(name: str) -> bool:
"""Checks whether a bucket exists and is reachable with our credentials."""
try:
s3.head_bucket(Bucket=name) # lightweight request: headers only
return True
except ClientError as error:
# The specific code is always in error.response["Error"]["Code"]
code = error.response["Error"]["Code"]
if code == "404":
print(f"Bucket '{name}' does not exist.")
elif code == "403":
print(f"Bucket '{name}' exists but you have no permission to see it.")
else:
raise # any other error, let it propagate
return False
print(bucket_exists("mercadofresco-fotos-producto"))Key points in the snippet:
head_bucketis the cheap way to check existence: it downloads no content.error.response["Error"]["Code"]is the standard path for reading the error code. Memorise it.- Telling 404 apart from 403 matters: "it does not exist" and "it exists but you cannot see it" demand different actions.
- The final
raiseavoids the antipattern of swallowing unknown errors, which hides real problems.
Pagination in boto3
APIs return at most a few hundred items per call. If you iterate only the first response, you will lose data silently. Paginators solve it:
import boto3
s3 = boto3.client("s3", region_name="eu-west-1")
paginator = s3.get_paginator("list_objects_v2")
pages = paginator.paginate(Bucket="mercadofresco-fotos-producto", Prefix="2026/")
total = 0
for page in pages: # each iteration is one API call
for obj in page.get("Contents", []): # .get() avoids KeyError if the page comes back empty
total += 1
print(f"Objects found: {total}")An equivalent look in JavaScript
So that you can see the concepts carry over, here is the same bucket listing with the AWS SDK for JavaScript v3:
# The v3 SDK is modular: you install only the client for the service you need
npm install @aws-sdk/client-s3// Import only the S3 client and the specific command we are going to use
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
// The client reads credentials with the same precedence order as the CLI
const client = new S3Client({ region: "eu-west-1" });
async function listBuckets() {
try {
// In v3, each operation is a Command object that is sent with send()
const response = await client.send(new ListBucketsCommand({}));
response.Buckets.forEach((b) => console.log(b.Name, b.CreationDate));
} catch (error) {
// The equivalent of ClientError: the code is in error.name
console.error("AWS error:", error.name, "-", error.message);
}
}
listBuckets();Differences from boto3: v3 is modular (less weight in Lambda) and uses the command pattern:
you create an XxxCommand object and send it with send(). The credential, retry and error logic
is equivalent.
CloudShell as a no-install alternative
As we saw in lesson 01-04, AWS CloudShell gives you a browser terminal with the CLI v2, Python and boto3 already installed, and automatically authenticated as the console user.
| Local CLI | CloudShell | |
|---|---|---|
| Installation | Yes | No |
| Credentials | You configure keys | Inherited, temporary |
| Performance and local files | Full | Limited (1 GB persistent) |
| Session | Permanent | Expires on inactivity |
| Ideal for | Day-to-day work, scripts, CI | Quick tests, emergencies, training |
CloudShell is especially useful for the "I am on a computer that is not mine and I need to diagnose something right now" scenario. It does not replace the local CLI for day-to-day work, but it is an excellent safety net.
Common Mistakes and Tips
Unable to locate credentials. You have not configured the profile, or you have used a profile name that does not exist. Diagnose it withaws configure list.ProfileNotFoundwith the profile spelt correctly. It is almost always the~/.aws/configsection without theprofileprefix: it must be[profile mercadofresco-dev], not[mercadofresco-dev].You must specify a region. Neither the profile nor the environment variable nor the command states a region. Add--regionor set it in the profile.- An unexpected
AccessDenied. Check which identity you are acting as first, withaws sts get-caller-identity; very often it is the wrong profile, not a permissions problem. - Pushing keys to Git. The most expensive and most frequent disaster.
.gitignore,git secretsand, better still, temporary credentials. - Using permanent keys inside an EC2 instance. Never. You use an instance role (lesson 04-01), which rotates the credentials for you as a bonus.
- Forgetting pagination. Your script says "there are 1,000 objects" when there are 50,000. Use
paginators in boto3 and
--max-items/NextTokenin the CLI. - Confusing
aws s3withaws s3api. The first is high level (ls,cp,sync); the second exposes the full API (get-bucket-tagging,put-bucket-policy). If an option does not exist ins3, look for it ins3api. - Ignoring
ClientError. A script that fails silently is worse than one that breaks. - Tip: define aliases in your terminal for whatever you repeat a lot, for example
alias whoami-aws='aws sts get-caller-identity'. - Tip: when a command does not work and you cannot see why, add
--debug. You will see the full signed request and the service's response.
Exercises
Exercise 1: installation and exploration
- Install AWS CLI v2 on your system and check the version.
- Configure a profile called
mercadofresco-adminwith regioneu-west-1andjsonoutput. - Run
aws sts get-caller-identityand note down your full ARN. - Write a single command that shows, in table format, every region whose name starts with
eu-, with a single column calledRegion.
Exercise 2: bash inventory script
Write a script inventario-mercadofresco.sh that:
- Checks which identity and which account it is running under, and displays it.
- Lists every S3 bucket in the account.
- Lists the Availability Zones of
eu-west-1with their physical ID. - Lists the EC2 instances with their ID, type, state and zone (if there are none, it must say so without failing).
- All of it with table output and without opening the interactive pager.
Exercise 3: boto3 script that creates and tags a bucket
Write a Python script crear_bucket_mercadofresco.py that:
- Takes the bucket name as a command line argument.
- Checks whether it already exists (telling "does not exist" apart from "no permissions").
- If it does not exist, creates it in
eu-west-1with public access block enabled. - Applies the tags
Proyecto=mercadofresco,Entorno=pruebas,Componente=formacion. - Displays the resulting tags.
- Handles errors with
ClientErrorand returns a non-zero exit code if it fails.
Cost: an empty bucket generates no appreciable cost and the Free Tier covers 5 GB. Even so, delete it when you are done with
aws s3 rb s3://<bucket-name>.
Solutions
Solution 1
# 1. Installation on Linux and verification
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install
aws --version
# 2. Profile
aws configure --profile mercadofresco-admin
# Region: eu-west-1 ; Output: json
# 3. Identity
aws sts get-caller-identity --profile mercadofresco-admin
# 4. European regions in a table, single column called Region
aws ec2 describe-regions \
--query 'Regions[?starts_with(RegionName, `eu-`)].{Region:RegionName}' \
--output tableYou can use either starts_with() or contains(); starts_with is more precise because contains
would also accept a name carrying eu- in the middle.
Solution 2
#!/usr/bin/env bash
# inventario-mercadofresco.sh - quick inventory of the AWS account
set -euo pipefail # abort on error, undefined variable or failure in a pipeline
PROFILE="${1:-mercadofresco-admin}" # first argument, or the default value
REGION="eu-west-1"
export AWS_PAGER="" # disables the interactive pager
echo "===== IDENTITY ====="
aws sts get-caller-identity \
--profile "$PROFILE" \
--query '{Account:Account, Identity:Arn}' \
--output table
echo "===== S3 BUCKETS ====="
aws s3api list-buckets \
--profile "$PROFILE" \
--query 'Buckets[].{Name:Name, Created:CreationDate}' \
--output table
echo "===== AVAILABILITY ZONES IN $REGION ====="
aws ec2 describe-availability-zones \
--profile "$PROFILE" --region "$REGION" \
--query 'AvailabilityZones[].{AZ:ZoneName, PhysicalId:ZoneId, State:State}' \
--output table
echo "===== EC2 INSTANCES ====="
INSTANCES=$(aws ec2 describe-instances \
--profile "$PROFILE" --region "$REGION" \
--query 'Reservations[].Instances[].{Id:InstanceId, Type:InstanceType, State:State.Name, Zone:Placement.AvailabilityZone}' \
--output table)
if [ -z "$INSTANCES" ]; then
echo "(no EC2 instances in $REGION)"
else
echo "$INSTANCES"
fi
echo "===== END OF INVENTORY ====="Important details:
set -euo pipefailstops the script from merrily carrying on after a failure.export AWS_PAGER=""is essential in scripts: without it, the CLI would open a pager and the script would hang waiting for somebody to pressq.- Storing the output in a variable lets you check whether it is empty and give a clear message instead of printing an empty table.
You run it with:
Solution 3
#!/usr/bin/env python3
"""crear_bucket_mercadofresco.py - creates and tags an S3 bucket for MercadoFresco."""
import sys
import boto3
from botocore.exceptions import ClientError
REGION = "eu-west-1"
TAGS = [
{"Key": "Proyecto", "Value": "mercadofresco"},
{"Key": "Entorno", "Value": "pruebas"},
{"Key": "Componente", "Value": "formacion"},
]
def bucket_exists(s3, name: str) -> bool:
"""True if the bucket exists and is reachable; False if it does not exist."""
try:
s3.head_bucket(Bucket=name)
return True
except ClientError as error:
code = error.response["Error"]["Code"]
if code in ("404", "NoSuchBucket"):
return False
if code == "403":
print(f"Bucket '{name}' exists but belongs to another account.")
sys.exit(1)
raise
def create_bucket(s3, name: str) -> None:
"""Creates the bucket in REGION with public access blocked."""
# Outside us-east-1 you have to state LocationConstraint explicitly
s3.create_bucket(
Bucket=name,
CreateBucketConfiguration={"LocationConstraint": REGION},
)
print(f"Bucket '{name}' created in {REGION}.")
# Total public access block: all four options set to True
s3.put_public_access_block(
Bucket=name,
PublicAccessBlockConfiguration={
"BlockPublicAcls": True,
"IgnorePublicAcls": True,
"BlockPublicPolicy": True,
"RestrictPublicBuckets": True,
},
)
print("Public access blocked.")
def apply_tags(s3, name: str) -> None:
"""Applies the tag set (overwriting any existing tags)."""
s3.put_bucket_tagging(Bucket=name, Tagging={"TagSet": TAGS})
print("Tags applied.")
def main() -> int:
if len(sys.argv) != 2:
print("Usage: python crear_bucket_mercadofresco.py <bucket-name>")
return 1
name = sys.argv[1]
s3 = boto3.client("s3", region_name=REGION)
try:
if bucket_exists(s3, name):
print(f"Bucket '{name}' already exists in your account; it will not be created again.")
else:
create_bucket(s3, name)
apply_tags(s3, name)
current = s3.get_bucket_tagging(Bucket=name)["TagSet"]
print("\nCurrent tags on the bucket:")
for tag in current:
print(f" {tag['Key']} = {tag['Value']}")
except ClientError as error:
print(f"AWS error: {error.response['Error']['Code']} - "
f"{error.response['Error']['Message']}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())Keys to the solution:
LocationConstraint: if you create a bucket in any region other thanus-east-1and omit this parameter, S3 creates it inus-east-1. It is one of the historical oddities of the API.put_public_access_blockwith all four options set toTrueis the protection that prevents the most common data leak on AWS.put_bucket_taggingoverwrites the complete tag set: if you wanted to add one without deleting the others, you would have to read them first withget_bucket_taggingand merge them.- Exit codes:
0if all goes well,1if it fails, so that the script can be chained into a pipeline.
Running it and cleaning up:
python crear_bucket_mercadofresco.py mercadofresco-pruebas-cli-jc-2741
# Mandatory clean-up when you are done (rb = remove bucket; it must be empty)
aws s3 rb s3://mercadofresco-pruebas-cli-jc-2741Conclusion
With this lesson you close the introductory module and, above all, make the leap from "AWS user" to
"AWS operator". You have installed the AWS CLI v2, you have understood the anatomy of a command
(aws <service> <operation> --parameters) and the naming conventions that let you guess commands you
do not know yet. You have configured credentials with aws configure, you have seen what is really
inside ~/.aws/credentials and ~/.aws/config, and you know how to work with named profiles so
as never to confuse development with production. You know credential precedence, which explains
almost every strange behaviour, and the non-negotiable security rules: never keys in Git, periodic
rotation and, inside AWS, roles instead of keys.
You have learned to tame command output with --output and with --query and JMESPath, to avoid the
silent trap of pagination, to verify permissions without spending anything using --dry-run, and you
have in hand the handful of exploration commands you will use every day. And you have taken your
first steps with the SDKs: boto3 in Python, with the distinction between client and resource,
correct error handling through ClientError and paginators, plus the JavaScript equivalent to show
that the concepts are universal.
MercadoFresco now has everything it needs to start in earnest: a secure, monitored account, a region chosen with judgement, a console you know how to drive, and command line and programming tools that make the work repeatable.
In module 2, "Core AWS services", starting with lesson 02-01 "Amazon EC2", we will stand up MercadoFresco's first real server in the cloud and begin the migration that will solve, one by one, the four problems we uncovered in the very first lesson.
AWS Course
Module 1: Introduction to AWS
- What Is AWS?
- Setting Up Your AWS Account
- AWS Global Infrastructure
- The AWS Management Console
- AWS CLI and SDKs
Module 2: Core AWS Services
Module 3: Networking and Content Delivery
Module 4: Security and Identity
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager and Parameter Store
- AWS Shield
- AWS WAF
Module 5: Monitoring and Management
Module 6: Databases
Module 7: Application Integration
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Integration Patterns: Idempotency, Retries and Dead-Letter Queues
