Build a Simple Inventory Script

Learn how to build a simple inventory script in Python for DevOps automation. Step-by-step hands-on tutorial with practical examples, troubleshooting tips, and what to study next.

Focus: build a simple inventory script

Sponsored

You're staring at a spreadsheet of 400 EC2 instances, a requirements.txt that's drifted from production, or a list of Kubernetes nodes that no one quite remembers provisioning. Manual inventory tracking is the silent killer of DevOps velocity — it's slow, error-prone, and by the time you notice a discrepancy, a security patch has already been missed. This lesson shows you how to build a simple inventory script in Python that turns scattered infrastructure data into a single, trustworthy source of truth — the kind of script that saves hours every week and makes you the person who always knows what's running where.

The problem this lesson solves

Infrastructure sprawl is real. Cloud resources get launched, containers get deployed, and servers get decommissioned — but the documentation rarely keeps up. The painful symptoms are familiar:

  • You SSH into a box that was supposed to be terminated months ago.
  • A critical service runs on an instance type that's no longer cost-efficient.
  • Auditors ask for a list of all production instances, and you scramble through AWS Console screenshots and stale spreadsheets.

A simple inventory script automates the discovery and reporting of your infrastructure. It pulls live data from APIs, normalizes it into a consistent format, and outputs a clean report. This isn't just a nice-to-have; it's the foundation for capacity planning, cost optimization, and security compliance.

Pro tip: The difference between a good DevOps engineer and a great one is often just the ability to answer "what do we have running right now?" in under a minute. An inventory script gives you that superpower.

Core concept / mental model

Think of your infrastructure as a library. The books (servers, databases, containers) are scattered across different floors (cloud providers, regions, on-prem racks). A manual inventory is like asking a librarian to check every shelf by hand every morning. An automated script is the card catalog — it queries a central index, sorts the books, and gives you a map in seconds.

In technical terms, an inventory script follows a simple collect → normalize → report pattern:

  1. Collect: Query infrastructure providers (AWS, Azure, GCP, Kubernetes, or even local files) using their APIs.
  2. Normalize: Convert the raw responses into a consistent Python data structure — typically a list of dictionaries.
  3. Report: Output the data in a human-friendly format (CSV, JSON, or plain text) or feed it into another system.

The key abstraction is the inventory entry — one dictionary that represents a single resource. Every resource type (EC2 instance, S3 bucket, Docker container) can be reduced to a few essential fields: name, ID, type, status, region, and tags.

Here's the mental model in pseudo-code:

for each provider in providers:
    resources = provider.fetch_all_resources()
    for resource in resources:
        inventory.append(normalize(resource))
print_inventory(inventory)

The elegance is that the provider-specific logic is isolated in one function, while the reporting stays generic. Add a new provider later? Just write another fetch function.

How it works step by step

Let's break down the process of building a simple inventory script from scratch. We'll use a mix of Python standard library and a couple of popular DevOps libraries to make it realistic.

1. Define the inventory schema

First, decide what fields every entry should have, regardless of the source. A minimal but useful schema:

  • name: Human-readable identifier
  • id: Unique provider-assigned ID
  • type: EC2, S3, Pod, etc.
  • status: running, stopped, active, etc.
  • region: cloud region or on-prem zone
  • tags: dictionary of key-value labels

2. Fetch resources from a provider

For AWS, that's the boto3 library. For Kubernetes, kubernetes client. For this example, we'll simulate a simple provider to keep the focus on the pattern.

3. Normalize the data

Write a function per provider that maps raw API responses to your schema.

4. Aggregate and report

Use Python's csv module for a CSV report, or json for machine-readable output.

5. Make it reusable

Wrap everything in functions, use argparse for CLI flags, and handle errors gracefully.

The cause-and-effect chain is clear: better data collection → normalized rows → reliable reports → informed decisions.

Hands-on walkthrough

Let's build a working inventory script step by step. We'll start with a simulated provider, then show how to plug in real AWS data.

Example 1: The basic pattern (no external dependencies)

This version uses a hardcoded list to demonstrate the normalize-and-report flow.

# inventory_basic.py
import json

# Simulated raw data from a provider API
raw_servers = [
    {"InstanceId": "i-12345", "InstanceType": "t3.micro", "State": {"Name": "running"}, "Tags": [{"Key": "env", "Value": "prod"}]},
    {"InstanceId": "i-67890", "InstanceType": "m5.large", "State": {"Name": "stopped"}, "Tags": [{"Key": "env", "Value": "dev"}]},
]

def normalize_aws_instance(raw):
    """Convert AWS raw dict to our inventory schema."""
    tags = {t["Key"]: t["Value"] for t in raw.get("Tags", [])}
    return {
        "name": tags.get("Name", raw["InstanceId"]),
        "id": raw["InstanceId"],
        "type": "EC2",
        "status": raw["State"]["Name"],
        "region": "us-east-1",  # would come from client
        "tags": tags,
    }

def build_inventory(raw_list, normalizer):
    return [normalizer(item) for item in raw_list]

inventory = build_inventory(raw_servers, normalize_aws_instance)
print(json.dumps(inventory, indent=2))

Expected output:

[
  {
    "name": "prod-server",
    "id": "i-12345",
    "type": "EC2",
    "status": "running",
    "region": "us-east-1",
    "tags": {"env": "prod"}
  },
  {
    "name": "dev-server",
    "id": "i-67890",
    "type": "EC2",
    "status": "stopped",
    "region": "us-east-1",
    "tags": {"env": "dev"}
  }
]

Example 2: Pulling real AWS data with boto3

Now replace the simulation with a live AWS query. Install boto3 first: pip install boto3.

# inventory_aws.py
import boto3
import json
from datetime import datetime

def fetch_ec2_instances():
    ec2 = boto3.client("ec2", region_name="us-east-1")
    response = ec2.describe_instances()
    instances = []
    for reservation in response["Reservations"]:
        for instance in reservation["Instances"]:
            tags = {t["Key"]: t["Value"] for t in instance.get("Tags", [])}
            instances.append({
                "name": tags.get("Name", "(untagged)"),
                "id": instance["InstanceId"],
                "type": instance["InstanceType"],
                "status": instance["State"]["Name"],
                "region": ec2.meta.region_name,
                "tags": tags,
                "launch_time": instance["LaunchTime"].isoformat()
            })
    return instances

def save_inventory(instances, filename="inventory.json"):
    with open(filename, "w") as f:
        json.dump(instances, f, indent=2)
    print(f"Saved {len(instances)} instances to {filename}")

if __name__ == "__main__":
    inventory = fetch_ec2_instances()
    save_inventory(inventory)

Run it: python inventory_aws.py. You'll get a JSON file listing every instance in your default region.

Example 3: Adding a CSV report

CSV is the lingua franca for spreadsheets. Add a function to export to CSV using csv.DictWriter.

# report_csv.py
import csv
from inventory_aws import fetch_ec2_instances

def write_csv(instances, filename="inventory.csv"):
    if not instances:
        print("No instances to write.")
        return
    fieldnames = ["name", "id", "type", "status", "region", "tags"]
    with open(filename, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        for inst in instances:
            inst["tags"] = ";".join(f"{k}={v}" for k, v in inst["tags"].items())
            writer.writerow(inst)
    print(f"Wrote {len(instances)} rows to {filename}")

if __name__ == "__main__":
    write_csv(fetch_ec2_instances())

The output file will open directly in Excel or Google Sheets.

Example 4: Adding a simple filter

Filter to only running instances or a specific tag — common for operational queries.

# filter_inventory.py
def filter_by_tag(instances, key, value):
    return [inst for inst in instances if inst["tags"].get(key) == value]

# Demo with simulated data
simulated = [
    {"id": "i-1", "status": "running", "tags": {"env": "prod"}},
    {"id": "i-2", "status": "stopped", "tags": {"env": "dev"}},
]
print(filter_by_tag(simulated, "env", "prod"))
# Output: [{'id': 'i-1', 'status': 'running', 'tags': {'env': 'prod'}}]

Compare options / when to choose what

You have several ways to store and report inventory data. Here's a comparison:

Approach Pros Cons Best for
JSON file Simple, machine-readable, easy to parse with Python Not human-friendly for large datasets API responses, configuration files
CSV file Universal spreadsheet support, version-control friendly Loses nested data structure (tags flattened) Reporting to business teams, audits
SQLite database Queryable with SQL, scalable to thousands of rows Requires schema design, extra dependency Long-term inventory tracking with history
Cloud provider console Real-time, no code Not scriptable, manual, limited filtering Quick spot checks, one-off inspection

When to choose what?

  • For a simple one-off audit, use a JSON or CSV file.
  • If you need recurring reports and history, move to SQLite.
  • If you need to automate actions (e.g., stop untagged instances), your script should output JSON so other tools can consume it.

As variations, you could use pandas for heavy data manipulation, or click for a nicer CLI interface.

Troubleshooting & edge cases

1. boto3 raises NoCredentialsError

Symptom: botocore.exceptions.NoCredentialsError: Unable to locate credentials. Fix: Configure AWS credentials via environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) or run aws configure.

2. Pagination — you only see the first 100 instances

Symptom: You get fewer results than expected. Fix: The AWS API returns results in pages. Use pagination with paginator = ec2.get_paginator("describe_instances") and iterate pages.

paginator = ec2.get_paginator("describe_instances")
for page in paginator.paginate():
    for reservation in page["Reservations"]:
        for instance in reservation["Instances"]:
            # process instance
            pass

3. Instances without tags

Symptom: KeyError: 'Name' when accessing tags. Fix: Always use .get() with a default value, as shown in the examples.

4. Timezone-aware timestamps in JSON

Symptom: TypeError: Object of type datetime is not JSON serializable. Fix: Call .isoformat() on datetime objects before storing them, as done in the boto3 example.

5. Empty inventory

Symptom: No data output. Fix: Check that your filters are correct and that you have permission to describe instances (IAM policy).

What you learned & what's next

You've built a simple inventory script that collects, normalizes, and reports infrastructure data. You can now:

  • Explain the core idea behind building an inventory script — the collect → normalize → report pattern.
  • Apply this pattern in a hands-on exercise, using both simulated data and real AWS APIs.
  • Handle common edge cases like missing tags, pagination, and timezone serialization.
  • Decide between JSON, CSV, and SQLite based on your use case.

You've also practiced key DevOps automation patterns: API integration, data normalization, and reporting. These skills are the foundation for more advanced topics like configuration drift detection and auto-remediation.

Next up in the Python for DevOps automation track: you'll learn how to schedule and deploy this inventory script — turning a one-off script into a scheduled cron job that runs every morning and sends you a Slack summary. That's where your script really starts paying dividends.

Practice recap

Take your inventory_aws.py script and add a --tag Key=Value filter that only lists instances with a specific tag. Then output the result to a CSV file. Run it against your real AWS account (or a local mock) and confirm you can filter by env=prod to see only production instances.

Common mistakes

  • Forgetting to handle pagination in cloud APIs — you only get the first page of results, missing most of your inventory.
  • Assuming every resource has a 'Name' tag — always use .get('Name', 'unknown') to avoid KeyError crashes.
  • Storing datetime objects directly in JSON — convert to .isoformat() first or you'll hit serialization errors.
  • Hardcoding region names instead of making them configurable — your script breaks when you move to another region.
  • Not checking AWS credentials before running — you get cryptic NoCredentialsError instead of a friendly message.

Variations

  1. Use pandas instead of csv for data manipulation and analysis — supports grouping, filtering, and Excel export.
  2. Use click or argparse to add CLI flags (e.g., --region, --tag, --format) — makes the script reusable across teams.
  3. Store inventory in a SQLite database to keep historical snapshots — enables 'what changed since yesterday' reports.

Real-world use cases

  • Generate a weekly CSV report of all AWS EC2 instances with tags, region, and status for finance to audit cloud costs.
  • Track Kubernetes node inventory across clusters for capacity planning, checking for unused nodes or version drift.
  • Produce a JSON inventory of all S3 buckets and their encryption status for security compliance checks before an audit.

Key takeaways

  • An inventory script follows a collect → normalize → report pattern — isolate provider logic from reporting.
  • Always define a consistent schema (name, id, type, status, region, tags) for every resource type.
  • Use pagination and handle missing tags to avoid incomplete or crashing scripts.
  • Output format depends on your audience: JSON for machines, CSV for humans, SQLite for history.
  • A simple script that runs reliably beats a complex solution that no one maintains.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.