# AWS Find AMIs | Boto3 Script

## Overview

Capture a list of available AWS AMIs for a specified region and OS version with my latest boto3 script. This Python script will output a color-coded table listing AMIs that have been released within the last 30 days with the owner alias set to Amazon. The table includes columns for AMI ID, Name, Architecture type and Virtualization type. Output is sorted by Architecture type (x86\_64, arm64, etc), then sorted by AMI names which include release dates.

Sample output against `us-west-2` region for a list of `debian-11*` AMI's:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685493555879/b45c934e-5ef8-431a-abed-2f9a86319cec.png align="center")

## Purpose

For my particular use case, I have a dev environment where AMIs for various OS versions and instance types need to be built for testing purposes. I also have a fleet of production instances that all run the same OS version and machine type using a custom AWS Terraform module. I'm still able to use this same Terraform module for my dev infrastructure however, I've created a unique variable to be able to assign a specific AMI to certain dev resources.  The `user_specified_ami` var is what I'm using to assign my AMIs.  I've applied the coalesce function to the "ami" resource which forces this value to return as precedent over the next sequential [data.aws](http://data.aws)\_ami source as long as there's a string assigned to this variable. It looks like this in the aws\_instance resource block of the module:

`ami = coalesce(var.user_specified_ami,` [`data.aws`](http://data.aws)`_`[`ami.prod`](http://ami.prod)`_`[`os.id`](http://os.id)`)`

Then in my configuration module, I can simply assign the user\_specified\_ami an ID from the script output.

## Script Functionality

[![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685539900686/5b5466cd-6987-4c00-a6bc-4cabbf7bfd7d.png align="center")](https://github.com/jksprattler/aws-tools/blob/main/scripts/aws_ec2_find_amis.py)

*Link to the script:* [https://github.com/jksprattler/aws-tools/blob/main/scripts/aws\_ec2\_find\_amis.py](https://github.com/jksprattler/aws-tools/blob/main/scripts/aws_ec2_find_amis.py)

### Usage

Here's a look at the options available from the `-h/--help` output:

```bash
❯ python aws_ec2_find_amis.py --help
usage: aws_ec2_find_amis.py [-h] region os_version

Find AMIs based on region and OS

positional arguments:
  region      AWS region
  os_version  OS version

options:
  -h, --help  show this help message and exit
```

Here's an example of entering an OS version not set in the `valid_os_versions` list of elements:

```bash
❯ python aws_ec2_find_amis.py us-west-2 debian
Invalid OS version 'debian'. Available OS versions:
amzn2
debian-11
ubuntu-jammy
Windows_Server-2019-English
```

Here's an example of entering an invalid AWS region:

```bash
❯ python aws_ec2_find_amis.py us-west debian-11
Invalid region 'us-west'. Available regions:
af-south-1
ap-south-1
eu-north-1
eu-west-3
eu-south-1
eu-west-2
eu-west-1
ap-northeast-3
ap-northeast-2
me-south-1
ap-northeast-1
ca-central-1
sa-east-1
ap-east-1
ap-southeast-1
ap-southeast-2
eu-central-1
ap-southeast-3
us-east-1
us-east-2
us-west-1
us-west-2
```

### Code Explanation

Inside the main function, it calls the `parse_arguments()` function to parse the user input arguments and assigns them to the `region` and `os_version` variables. See the above help output for example.

```python
def main():
    """
    Parse user arguments, assign region and os values
    Lookup latest available AMI's and output them to color-coded table
    """
    region, os_version = parse_arguments()
    validate_inputs(region, os_version)
    find_amis(region, os_version)
```

Here's the parse\_arguments() function :

```python
def parse_arguments():
    """ Parse args """
    parser = argparse.ArgumentParser(description='Find AMIs based on region and OS')
    parser.add_argument('region', type=str, help='AWS region')
    parser.add_argument('os_version', type=str, help='OS version')
    args = parser.parse_args()
    return args.region, args.os_version
```

Next, it checks if the provided inputs for the region and os\_version arguments are valid with the `validate_inputs(region, os_version)` function.

```python
def validate_inputs(region, os_version):
    """ Validate region and OS version if provided"""
    if region:
        available_regions = get_available_regions()
        if region not in available_regions:
            print(f"Invalid region '{region}'. Available regions:")
            for regions in available_regions:
                print(regions)
            sys.exit()

    if os_version:
        validate_os_version(os_version)
```

This will perform a conditional check for the region input against the `get_available_regions()` function which performs an API call using the EC2 client to describe the AWS regions:

```python
def get_available_regions():
    """
    Get a list of available AWS regions
    """
    ec2_client = boto3.client('ec2')
    response = ec2_client.describe_regions()
    regions = [region['RegionName'] for region in response['Regions']]
    return regions
```

Then it performs a conditional check for the os\_version input against the `validate_os_version(os_version)` function which checks if the provided versions starts with any of the elements in the `valid_os_versions` list:

```python
def validate_os_version(os_version):
    """ Check if the provided OS version is valid """
    valid_os_versions = ['amzn2', 'debian-11', 'ubuntu-jammy', 'Windows_Server-2019-English']
    for version in valid_os_versions:
        if os_version.startswith(version):
            return
    print(f"Invalid OS version '{os_version}'. Available OS versions:")
    for version in valid_os_versions:
        print(version)
    sys.exit()
```

Finally, the `find_amis(region, os_version)` function is executed. The API of the EC2 client is called to describe images hosted in the specified region and filtered based on the provided OS version and AMI alias owner, Amazon:

```python
def find_amis(region, os_version):
    """
    Output a list of the latest Amazon owned AMI's for the specified
    region and OS version in color-coded Table format
    """

    ec2_client = boto3.client('ec2', region_name=region)

    response = ec2_client.describe_images(
        Filters=[
            {'Name': 'name', 'Values': ['*'+os_version+'*']},
            {'Name': 'owner-alias', 'Values': ['amazon']}
        ]
    )

    amis = response['Images']

    # Sort the AMIs by architecture type and name
    sorted_amis = sorted(amis, key=lambda x: (x['Architecture'], x['Name']))
    latest_amis = []
    for ami in sorted_amis:
        creation_date = datetime.datetime.strptime(ami['CreationDate'], "%Y-%m-%dT%H:%M:%S.%fZ")
        if creation_date >= DATE_THRESHOLD:
            latest_amis.append(ami)

    table = []
    table_headers = ["AMI ID", "Name", "Architecture", "VirtualizationType"]
    for ami in latest_amis:
        table.append([
            f"{Fore.CYAN}{ami['ImageId']}{Style.RESET_ALL}",
            ami['Name'],
            f"{Fore.GREEN}{ami['Architecture']}{Style.RESET_ALL}",
            ami['VirtualizationType']
        ])
    print(tabulate(table, headers=table_headers, tablefmt="grid"))
```

The response contains a list of AMIS matching these filters and is sorted by the Architecture type. Further sorting is done by AMI name which contains release dates. Only AMIs with release dates in the last 30 days are provided.

Next, a table is generated by creating an empty list called `table` to store the AMI information for tabulation. As the script iterates over the sorted AMIs, the requested data for the AMI ID, Name, Architecture and Virtualization Type are appended to the `table` list. The ImageID and the Architecture values have color formatting applied using the Fore class from the colorama library. I added this feature to help with the readability of the table output.

## Conclusion

Now I'm able to get these AMI values much more quickly with this handy script rather than crafting an `aws ec2 describe-images` command and parsing through the output. Hope you found this useful! What kind of use cases do you have for capturing and assigning AMIs?
