============
Architecture
============

Module Overview
===============

InfraHouse Core is organized into two main areas:

- **aws** -- AWS service wrappers with consistent session, credential, and role-assumption handling.
- **github** -- GitHub Actions runner management via the GitHub API.

Supporting modules provide logging, timeouts, filesystem helpers, and input validation.

::

    infrahouse_core/
    ├── aws/
    │   ├── __init__.py          # get_session, get_client, get_resource, SSO login
    │   ├── base.py              # AWSResource base class
    │   ├── ec2_instance.py      # EC2 + SSM command execution
    │   ├── dynamodb.py          # DynamoDB table + distributed locking
    │   ├── secretsmanager.py    # Secrets Manager wrapper
    │   ├── route53/zone.py      # DNS record management
    │   ├── config.py            # ~/.aws/config parser
    │   └── ...                  # ACM, CloudFront, ECS, IAM, S3, etc.
    ├── orchestrator/            # RAFT cluster coordination
    ├── github.py                # GitHub Actions runner management
    ├── logging.py               # stdout/stderr log routing
    ├── timeout.py               # SIGALRM-based timeout context manager
    └── validation.py            # Input validation helpers


Session, Client, and Resource Functions
=======================================

The ``aws`` module provides three functions for obtaining boto3 objects.
All three accept optional ``role_arn`` and ``region`` parameters.

``get_session(role_arn=None, region=None)``
    Returns a ``boto3.Session``, optionally with assumed-role credentials.
    Use this when you need to create multiple clients or resources from
    the same set of credentials.

``get_client(service_name, role_arn=None, region=None)``
    Convenience wrapper -- creates a session and returns a single low-level
    client (e.g. ``get_client("ec2")``).

``get_resource(service_name, role_arn=None, region=None)``
    Convenience wrapper -- creates a session and returns a single high-level
    resource (e.g. ``get_resource("dynamodb")``).

**When to use which:**

- Use ``get_client`` (the most common case) when you need one service client.
- Use ``get_resource`` when you want the higher-level boto3 resource API
  (DynamoDB Table, S3 Bucket, etc.).
- Use ``get_session`` when you need to create multiple clients/resources
  sharing the same assumed-role credentials.

Example::

    from infrahouse_core.aws import get_client, get_resource, get_session

    # Single client
    ec2 = get_client("ec2", region="us-east-1")

    # Single resource
    table = get_resource("dynamodb").Table("my-table")

    # Shared session for cross-account access
    session = get_session(role_arn="arn:aws:iam::123456789012:role/MyRole")
    ec2 = session.client("ec2")
    ssm = session.client("ssm")


Role Assumption
===============

Cross-account (or cross-role) access is supported throughout the library
via an optional ``role_arn`` parameter.

**At the function level:**

::

    client = get_client("s3", role_arn="arn:aws:iam::123456789012:role/S3Reader")

**At the class level:**

Most resource classes (``EC2Instance``, ``DynamoDBTable``, ``Secret``, ``Zone``, etc.)
accept ``role_arn`` in their constructors.  The role is assumed lazily when the
first API call is made::

    from infrahouse_core.aws.secretsmanager import Secret

    # Read a secret from another account
    secret = Secret("my-secret", role_arn="arn:aws:iam::123456789012:role/SecretsReader")
    print(secret.value)

**Session names** are auto-generated from the caller's module and function name
for CloudTrail auditing.  You can override this with the ``session_name`` parameter.


Caching Behaviour
=================

Several classes use ``cached_property_with_ttl`` to avoid redundant API calls.
The default TTL is **10 seconds** -- after that the property is re-fetched on
next access.

Properties that use TTL caching include:

- ``EC2Instance._describe_instance`` (instance metadata)
- ``GitHubActionsRunner._runner_data`` (runner status)

This means rapid successive reads (e.g. checking ``instance.state`` twice in a
tight loop) hit the cache, but reads separated by more than 10 seconds always
get fresh data.


Client Lifecycle
================

Resource classes create their boto3 clients lazily on first use and reuse
them for the lifetime of the object.  This means:

1. Constructing a class is cheap -- no API calls are made.
2. The first property access or method call creates the client.
3. Subsequent calls reuse the same client.

If you need to refresh credentials (e.g. after a long-running process),
create a new instance of the class.


AWSResource Base Class
======================

All AWS resource wrapper classes follow the contract defined by ``AWSResource``:

- ``exists`` property -- returns ``True`` if the resource currently exists.
- ``delete()`` method -- idempotent deletion (no error if already absent).
- ``delete()`` uses EAFP (try/except), not check-then-act, to avoid TOCTOU
  race conditions.