vantage-aws 0.4.6

AWS API persistence backend for Vantage framework — incubating
Documentation
# vantage-aws

AWS API backend for the [Vantage](https://github.com/romaninsh/vantage)
data framework — incubating.

Treats AWS API endpoints as Vantage `TableSource`s. `AwsAccount` *is*
the source — there's no per-operation wrapper. The operation you want
to run lives in the table name.

Two wire protocols ship today: **JSON-1.1** (CloudWatch, ECS, KMS,
DynamoDB control plane, …) and **Query** (IAM, STS, EC2, ELBv1, SES, …).
The protocol is picked from the table name's prefix, so the same
`AwsAccount` covers everything and relations span services freely.

## Authentication

Three ways to construct an `AwsAccount`, plus a chain helper:

```rust
use vantage_aws::AwsAccount;

let aws = AwsAccount::new(access_key, secret_key, region);   // explicit
let aws = AwsAccount::from_env()?;                            // standard env vars
let aws = AwsAccount::from_credentials_file()?;               // ~/.aws/credentials [default] only
let aws = AwsAccount::from_default()?;                        // env first, file fallback
```

`from_credentials_file` reads only the `[default]` profile. Region falls
through `AWS_REGION` → `AWS_DEFAULT_REGION` → `~/.aws/config`
`[default]` `region`. `AWS_PROFILE`, SSO, assume-role, IMDS — out of
v0; set the env vars yourself if you need anything fancier.

## Quick start

```rust
use vantage_aws::{AwsAccount, eq};
use vantage_table::table::Table;
use vantage_types::EmptyEntity;
use vantage_dataset::prelude::ReadableValueSet;

let aws = AwsAccount::from_default()?;

let mut groups: Table<AwsAccount, EmptyEntity> = Table::new(
    "json1/logGroups:logs/Logs_20140328.DescribeLogGroups",
    aws,
);
groups.add_condition(eq("logGroupNamePrefix", "/aws/lambda/"));

let rows = groups.list_values().await?;
```

That's a CloudWatch `DescribeLogGroups` call, filtered by prefix. The
condition folds into the JSON request body; the response array gets
parsed into `Record<CborValue>` rows.

## Anatomy of a table name

```text
"json1/logGroups:logs/Logs_20140328.DescribeLogGroups"
   │       │      │       └── X-Amz-Target header value
   │       │      └────────── service code (also URL hostname segment)
   │       └───────────────── response field that holds the row array
   └───────────────────────── wire protocol (json1 or query)

"query/Users:iam/2010-05-08.ListUsers"
   │     │    │     │
   │     │    │     └── "VERSION.Action" — both go into the form body
   │     │    └──────── service code (also URL hostname segment)
   │     └───────────── response element name (Query lists wrap in <member>)
   └─────────────────── wire protocol
```

You only have to write this once per resource — usually you wrap it in
a model factory and forget about the encoding (see below).

## Built-in models

`vantage_aws::models` ships ready-made tables so you don't have to
memorise the table-name format:

- CloudWatch Logs (under `models::logs`): `groups_table`, `streams_table`, `events_table`.
- ECS (under `models::ecs`): `clusters_table`, `services_table`, `tasks_table`, `task_definitions_table`.
- IAM (under `models::iam`): `users_table`, `groups_table`, `roles_table`, `policies_table`, `access_keys_table`, `instance_profiles_table`.

```rust
use vantage_aws::models::logs::{groups_table, events_table};
use vantage_aws::eq;

let mut groups = groups_table(aws.clone());
groups.add_condition(eq("logGroupNamePrefix", "/aws/lambda/"));
let rows = groups.list_values().await?;

let mut events = events_table(aws);
events.add_condition(eq("logGroupName", "/aws/lambda/foo"));
let logs = events.list_values().await?;
```

`logs::groups_table` registers two `with_many` relations — `events` and
`streams` — that traverse to the group's log events / streams. AWS
doesn't accept multi-value filters, so the source has to narrow to a
single group before traversal — otherwise the call errors at execute
time.

ECS APIs split into a `List*` step (returns ARNs only) and a `Describe*`
step (full objects). v0 exposes the `List*` side; rows hold the ARN
plus parsed-out short-name helpers (`Cluster::name()`,
`Task::task_id()`, etc.).

## Conditions

`eq` folds straight into the JSON request body. AWS APIs only accept
exact-match filters, so that's all you really get:

```rust
use vantage_aws::eq;

table.add_condition(eq("logGroupNamePrefix", "/aws/lambda/"));
```

Bring `AwsOperation` into scope to write `column.eq(...)` instead:

```rust
use vantage_aws::AwsOperation;

table.add_condition(table["logGroupName"].clone().eq("/ecs/ba-nginx"));
```

`In` and `Deferred` are here to make `with_one` / `with_many`
traversal work — they must collapse to a single value at execute
time, otherwise the call errors loudly.

## Demo CLI

`examples/aws-cli.rs` exercises the end-to-end machinery:

```sh
cargo run --example aws-cli -- list-groups
cargo run --example aws-cli -- list-groups --prefix /aws/lambda/
cargo run --example aws-cli -- list-events /ecs/ba-nginx
cargo run --example aws-cli -- traverse                        # with_many walk
cargo run --example aws-cli -- --region eu-west-2 list-groups  # override
```

Output goes through `vantage_cli_util::print_table` so it exercises
the same `Table` / `TableSource` machinery the rest of the framework
uses.

## SigV4

No `aws-sdk-*`, no `aws-sigv4` — signing is hand-rolled in `src/sign.rs`
with `hmac` + `sha2` + `hex` and pinned to AWS's canonical-example
fixture. Non-streaming, non-presigned. The same signer powers both the
JSON-1.1 transport and the form-encoded Query transport.

## Status

v0 covers: `AwsAccount` + JSON-1.1 *and* Query transports, hand-rolled
SigV4, `Eq` / `In` / `Deferred` conditions, `with_one` / `with_many`
traversal, CloudWatch (`LogGroup`, `LogStream`, `LogEvent`), ECS
(`Cluster`, `Service`, `Task`, `TaskDefinition`), and IAM (`User`,
`Role`) models, env-var and `~/.aws/credentials` `[default]` credential
loading.

Out of scope for v0:

- **Writes.** `insert_table_value` and friends return errors. Read-only
  end-to-end.
- **Pagination.** First page only. Most list operations cap at 50–100
  items per call.
- **Aggregations.** `sum` / `min` / `max` error out — would need a full
  scan.
- **REST-JSON / S3.** Lambda invoke and S3 are different protocols;
  they'll arrive as their own crates.
- **`AWS_PROFILE` / SSO / assume-role / IMDS.** Static credentials and
  the `[default]` profile only.

## License

MIT OR Apache-2.0