Drill 06: Infrastructure Patterns

Real-world patterns: HTTP clients, CLI tools, config files, logging.

Run This Drill

bash ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/examples/python-drills/06-infrastructure.sh

Drill Script

#!/bin/bash
# PYTHON DRILL 06: INFRASTRUCTURE PATTERNS
# Paste this entire script into your terminal
# Topics: API clients, config management, CLI tools

echo "=================================================================="
echo "             PYTHON DRILL 06: INFRASTRUCTURE PATTERNS            "
echo "=================================================================="
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 6.1: HTTP REQUESTS (urllib)"
echo "Built-in HTTP without dependencies"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
import urllib.request
import json

# GET request
url = "https://httpbin.org/get"
try:
    with urllib.request.urlopen(url, timeout=5) as response:
        data = json.loads(response.read().decode())
        print(f"Status: {response.status}")
        print(f"Origin: {data.get('origin', 'unknown')}")
except Exception as e:
    print(f"Request failed: {e}")

# POST with data
post_url = "https://httpbin.org/post"
post_data = json.dumps({"hostname": "ise-01", "status": "active"}).encode()
req = urllib.request.Request(
    post_url,
    data=post_data,
    headers={"Content-Type": "application/json"}
)
try:
    with urllib.request.urlopen(req, timeout=5) as response:
        result = json.loads(response.read().decode())
        print(f"\nPOST response data: {result.get('json', {})}")
except Exception as e:
    print(f"POST failed: {e}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 6.2: ARGPARSE CLI"
echo "Build command-line tools"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
import argparse
import sys

def build_parser():
    parser = argparse.ArgumentParser(
        description="Infrastructure management tool"
    )
    parser.add_argument("hostname", help="Target hostname")
    parser.add_argument("-p", "--port", type=int, default=22, help="Port number")
    parser.add_argument("-u", "--user", default="admin", help="Username")
    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
    parser.add_argument("-f", "--format", choices=["json", "table"], default="table")
    return parser

# Simulate CLI args
test_args = ["ise-01", "-p", "443", "-u", "admin", "-v", "-f", "json"]
parser = build_parser()
args = parser.parse_args(test_args)

print(f"Hostname: {args.hostname}")
print(f"Port: {args.port}")
print(f"User: {args.user}")
print(f"Verbose: {args.verbose}")
print(f"Format: {args.format}")

# With subcommands
def build_subparser():
    parser = argparse.ArgumentParser(prog="netapi")
    subparsers = parser.add_subparsers(dest="command")

    # ise subcommand
    ise = subparsers.add_parser("ise", help="ISE operations")
    ise.add_argument("action", choices=["sessions", "endpoints"])
    ise.add_argument("-f", "--format", default="table")

    # vault subcommand
    vault = subparsers.add_parser("vault", help="Vault operations")
    vault.add_argument("action", choices=["status", "list"])

    return parser

print("\n=== Subcommands ===")
test_args = ["ise", "sessions", "-f", "json"]
parser = build_subparser()
args = parser.parse_args(test_args)
print(f"Command: {args.command}")
print(f"Action: {args.action}")
print(f"Format: {args.format}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 6.3: CONFIG FILE HANDLING"
echo "YAML, TOML, INI patterns"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
import configparser
import json
from pathlib import Path

# INI file (built-in)
ini_content = """
[server]
hostname = ise-01
ip = 10.50.1.20
port = 443

[auth]
username = admin
method = certificate
"""

# Parse INI
Path("/tmp/config.ini").write_text(ini_content)
config = configparser.ConfigParser()
config.read("/tmp/config.ini")

print("=== INI Config ===")
print(f"Hostname: {config['server']['hostname']}")
print(f"IP: {config['server']['ip']}")
print(f"Auth method: {config['auth']['method']}")

# Convert to dict
config_dict = {section: dict(config[section]) for section in config.sections()}
print(f"\nAs dict: {json.dumps(config_dict, indent=2)}")

# JSON config (already covered)
json_config = {
    "servers": {
        "ise-01": {"ip": "10.50.1.20", "port": 443},
        "bind-01": {"ip": "10.50.1.90", "port": 53}
    },
    "defaults": {
        "timeout": 30,
        "retries": 3
    }
}

Path("/tmp/config.json").write_text(json.dumps(json_config, indent=2))
print("\n=== JSON Config ===")
loaded = json.loads(Path("/tmp/config.json").read_text())
print(f"ISE IP: {loaded['servers']['ise-01']['ip']}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 6.4: LOGGING"
echo "Proper logging for scripts"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
import logging

# Basic config
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)

logger.debug("Debug message (hidden at INFO level)")
logger.info("Starting infrastructure check")
logger.warning("High CPU detected on ise-01")
logger.error("Connection failed to vault-01")

# Structured logging pattern
def log_operation(operation, target, status, **extra):
    msg = f"{operation} {target}: {status}"
    if extra:
        msg += f" - {extra}"
    logger.info(msg)

log_operation("CONNECT", "ise-01", "SUCCESS", latency=45)
log_operation("BACKUP", "vault-01", "FAILED", error="timeout")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 6.5: INVENTORY SCRIPT"
echo "Complete infrastructure tool pattern"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
import json
from dataclasses import dataclass, asdict
from typing import List, Optional
from pathlib import Path

@dataclass
class Server:
    hostname: str
    ip: str
    port: int
    roles: List[str]
    status: str = "unknown"

    def to_dict(self):
        return asdict(self)

class Inventory:
    def __init__(self):
        self.servers: List[Server] = []

    def add(self, server: Server):
        self.servers.append(server)

    def find_by_role(self, role: str) -> List[Server]:
        return [s for s in self.servers if role in s.roles]

    def find_by_status(self, status: str) -> List[Server]:
        return [s for s in self.servers if s.status == status]

    def to_json(self) -> str:
        return json.dumps([s.to_dict() for s in self.servers], indent=2)

    def summary(self) -> dict:
        return {
            "total": len(self.servers),
            "by_status": {
                status: len(self.find_by_status(status))
                for status in set(s.status for s in self.servers)
            }
        }

# Build inventory
inv = Inventory()
inv.add(Server("ise-01", "10.50.1.20", 443, ["pan", "mnt"], "active"))
inv.add(Server("ise-02", "10.50.1.21", 443, ["psn"], "active"))
inv.add(Server("bind-01", "10.50.1.90", 53, ["dns"], "active"))
inv.add(Server("vault-01", "10.50.1.132", 8200, ["secrets"], "standby"))

print("=== PSN servers ===")
for s in inv.find_by_role("psn"):
    print(f"  {s.hostname}: {s.ip}")

print("\n=== Summary ===")
print(json.dumps(inv.summary(), indent=2))

print("\n=== Full inventory (JSON) ===")
print(inv.to_json())
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "YOUR TURN - TRY THESE:"
echo "------------------------------------------------------------------"
echo ""
echo "1. Build a simple CLI:"
echo "   import argparse"
echo "   parser = argparse.ArgumentParser()"
echo "   parser.add_argument('host')"
echo "   parser.add_argument('-p', '--port', type=int, default=22)"
echo "   args = parser.parse_args(['ise-01', '-p', '443'])"
echo ""
echo "2. Add logging to a script:"
echo "   import logging"
echo "   logging.basicConfig(level=logging.INFO)"
echo "   logging.info('Starting backup')"
echo ""
echo "3. Read config and merge with defaults:"
echo "   defaults = {'timeout': 30, 'retries': 3}"
echo "   user_config = {'timeout': 60}"
echo "   config = {**defaults, **user_config}  # merge"
echo ""
echo "------------------------------------------------------------------"
echo "KEY TAKEAWAYS:"
echo "1. urllib.request for HTTP without requests"
echo "2. argparse for CLI tools with subcommands"
echo "3. configparser for INI files"
echo "4. logging module for proper output"
echo "5. dataclasses + typing for clean code"
echo "------------------------------------------------------------------"