>_STRYKE-AWS
The cloud, one stryke pipe away. AWS client for stryke across 17 services — S3, DynamoDB, SQS, Lambda, STS, SNS, SSM Parameter Store, Secrets Manager, SES, CloudWatch, CloudWatch Logs, EC2, KMS, IAM, Kinesis, ECR, and Step Functions. Opt-in package, kept out of the stryke core binary so the daily-driver install stays slim. The 17 SDKs combined are too much to bake into stryke core, so they ship as a Rust cdylib that stryke dlopens in-process on first use AWS.
Contents
Install
# from a release (no rustc on the consumer machine)
s pkg install -g github.com/MenkeTechnologies/stryke-aws
# from a local checkout
cd ~/projects/stryke-aws
cargo build --release # produces target/release/libstryke_aws.{dylib,so}
s pkg install -g . # cdylib lands in ~/.stryke/store/aws@<version>/
# one-liner
make install
The cdylib is dlopened in-process on first use AWS — no helper-binary fork per call (the v1 helper-binary model was replaced in v0.2.0). Any stryke script that declares use AWS (or a submodule like use AWS::S3) resolves the package automatically. Credentials and region come from the standard AWS chain (env vars, ~/.aws/config|credentials, IMDS) — same as the aws CLI.
Quick start: use AWS
| identity check | p to_json AWS::STS::caller_identity() |
| list S3 keys | val @keys = AWS::S3::ls "s3://my-bucket/prefix/", delimiter => "/" |
| get an object | p AWS::S3::get "s3://my-bucket/hello.txt" |
| put an object | AWS::S3::put "s3://my-bucket/hello.txt", data => "hi" |
| DynamoDB item | AWS::Dynamo::put "users", { id => "u-42", name => "alice" } |
| send SQS message | AWS::SQS::send $queue_url, "payload" |
| invoke Lambda + capture | val $reply = AWS::Lambda::call "my-fn", { hello => "world" } |
| publish to SNS | AWS::SNS::publish "alert", topic_arn => $arn |
| read a parameter | p AWS::SSM::get "/app/db/url" |
| fetch a secret | p AWS::Secrets::get "prod/api-key" |
| LocalStack endpoint override | AWS::S3::ls "s3://...", endpoint => "http://localhost:4566" |
Every public fn accepts per-call connection overrides as named options — region, profile, and endpoint (for LocalStack / MinIO):
AWS::S3::ls "s3://...",
region => "us-west-2",
profile => "prod",
endpoint => "http://localstack:4566"
The full surface is mirrored below and in the README "API reference" section.
Configuration
| Source | Effect |
|---|---|
| standard AWS chain | Credentials + region from env vars, ~/.aws/config|credentials, IMDS — same resolution order as the aws CLI |
region => "..." | Per-call region override; a separate SdkConfig is cached per region |
profile => "..." | Per-call named profile from ~/.aws/config |
endpoint => "..." | Per-call endpoint override (LocalStack, MinIO, VPC endpoints) |
$ENV{AWS_ENDPOINT_URL} | Global default endpoint for all calls |
API reference
Each row is a public stryke fn. %opts on any fn carries the named connection overrides above plus the per-fn options noted. Responses are plain stryke values (scalars / arrayrefs / hashrefs). Deferred ops die with a message naming the cdylib export that needs adding.
use AWS — plumbing
| Fn | Returns / notes |
|---|---|
| AWS::version() | cdylib package version string |
| AWS::ping(%opts) | STS-backed connectivity probe |
Plus the flat AWS::<service>_<op> fns that the namespaced wrappers below delegate to.
use AWS::S3
| Fn | Returns / notes |
|---|---|
| S3::ls $uri, %opts | @entries — {type=>"object", key, size, etag, last_modified, storage_class} or {type=>"prefix", key} when delimiter is set |
| S3::get $uri, %opts | $body (or $path when output=>"PATH") |
| S3::put $uri, %opts | \%resp — data=>$bytes | input=>"PATH" |
| S3::head $uri, %opts | \%resp (content_length, content_type, etag, …) |
| S3::rm $uri, %opts | \%resp |
| S3::buckets %opts | @buckets |
| S3::versions $uri, %opts | \%resp — { versions, delete_markers } |
| S3::location $uri, %opts | \%resp — { bucket, location_constraint, region } |
| S3::tags $uri, %opts | \%tags — { key => value } |
| S3::mb $uri, %opts | \%resp — CreateBucket; { bucket, location, created } |
| S3::rb $uri, %opts | 1 | 0 — DeleteBucket (bucket must be empty) |
| S3::presign $uri, %opts | dies — deferred in the cdylib |
use AWS::Dynamo
| Fn | Returns / notes |
|---|---|
| Dynamo::get $table, $key, %opts | \%item | undef |
| Dynamo::put $table, $item, %opts | { ok: 1 } |
| Dynamo::delete $table, $key, %opts | 1 | 0 |
| Dynamo::query $table, %opts | @items — opts: key_condition, values, names, filter, index, limit |
| Dynamo::scan $table, %opts | @items — opts: filter, values, limit |
| Dynamo::describe $table, %opts | \%info — status, item_count, key_schema, arn |
| Dynamo::tables %opts | @names |
| Dynamo::transact %opts | $count — opts: puts, deletes; atomic |
| Dynamo::ttl $table, %opts | \%info — { status, attribute_name } |
| Dynamo::create $table, $hash_key, %opts | \%resp — CreateTable (on-demand); opts: hash_key_type, range_key, range_key_type |
| Dynamo::drop $table, %opts | \%resp — DeleteTable; { table, status, deleted } |
| Dynamo::batch_write $table, $rows, %opts | dies — deferred in the cdylib |
| Dynamo::scan_stream $table, %opts | dies — deferred in the cdylib |
query/scan take a DynamoDB expression plus a values hashref of :placeholder => value bindings (and optional names for #alias). Nested maps/lists round-trip recursively; binary attributes come back as "base64:…".
use AWS::SQS
| Fn | Returns / notes |
|---|---|
| SQS::send $queue, $body, %opts | \%resp — opts: delay_seconds, dedup_id, group_id |
| SQS::receive $queue, %opts | @messages — opts: max, wait, visibility |
| SQS::delete $queue, $receipt, %opts | { ok: 1 } |
| SQS::list %opts | @urls (prefix filter deferred) |
| SQS::purge $queue, %opts | 1 | 0 — delete all messages |
| SQS::attrs $queue, %opts | \%attributes — GetQueueAttributes (All) |
| SQS::set_attrs $queue, $attributes, %opts | \%resp — SetQueueAttributes |
| SQS::change_visibility $queue, $receipt, $timeout, %opts | \%resp |
| SQS::pump $queue, %opts | $count — callback + auto-delete on success |
use AWS::Lambda
| Fn | Returns / notes |
|---|---|
| Lambda::invoke $name, $payload, %opts | \%resp — { function, status_code, result } |
| Lambda::call $name, $payload, %opts | $result — just .result (undef unless status_code == 200) |
| Lambda::list %opts | @functions |
| Lambda::get $name, %opts | \%resp — GetFunction: { runtime, handler, arn, memory_size, timeout, … } |
invocation_type => "event" (fire-and-forget) is deferred in the cdylib.
use AWS::STS
| Fn | Returns / notes |
|---|---|
| STS::caller_identity %opts | { account, arn, user_id } |
| STS::assume_role $role_arn, %opts | \%creds — opts: session_name; temp credentials |
use AWS::SNS
| Fn | Returns / notes |
|---|---|
| SNS::topics %opts | @topic_arns |
| SNS::create $name, %opts | $topic_arn |
| SNS::publish $message, %opts | $message_id — opts: topic_arn | target_arn | phone_number, subject |
| SNS::subscribe $topic_arn, $protocol, $endpoint, %opts | $subscription_arn |
| SNS::unsubscribe $subscription_arn, %opts | 1 | 0 |
| SNS::subscriptions $topic_arn, %opts | @{ {subscription_arn, protocol, endpoint, owner} } |
| SNS::delete $topic_arn, %opts | 1 | 0 — DeleteTopic |
use AWS::SSM — Parameter Store
| Fn | Returns / notes |
|---|---|
| SSM::get $name, %opts | $value — opts: with_decryption |
| SSM::put $name, $value, %opts | $version — opts: type (String|StringList|SecureString), overwrite |
| SSM::by_path $path, %opts | @{ {name, value} } — opts: recursive, with_decryption |
| SSM::delete $name, %opts | 1 | 0 |
| SSM::get_many $names_or_aref, %opts | \%resp — GetParameters (≤10): { parameters, invalid_parameters } |
use AWS::Secrets — Secrets Manager
| Fn | Returns / notes |
|---|---|
| Secrets::get $secret_id, %opts | $secret_string |
| Secrets::create $name, $secret_string, %opts | { arn, name } |
| Secrets::put $secret_id, $secret_string, %opts | $version_id |
| Secrets::list %opts | @{ {name, arn} } |
use AWS::SES — email (v2)
| Fn | Returns / notes |
|---|---|
| SES::send $from, $to_or_aref, %opts | $message_id — opts: subject, body, html |
use AWS::CloudWatch
| Fn | Returns / notes |
|---|---|
| CloudWatch::put $namespace, $metric_name, $value, %opts | 1 — opts: unit |
| CloudWatch::list %opts | @{ {namespace, metric_name, dimensions} } — opts: namespace, metric_name |
use AWS::Logs — CloudWatch Logs
| Fn | Returns / notes |
|---|---|
| Logs::groups %opts | @groups — DescribeLogGroups; opts: prefix |
| Logs::create $name, %opts | 1 — CreateLogGroup |
| Logs::filter $log_group, %opts | @events — opts: filter_pattern, start_time, end_time, limit |
| Logs::events $log_group, $log_stream, %opts | @events — opts: limit, start_from_head |
| Logs::put $log_group, $log_stream, $events, %opts | $next_token — PutLogEvents; events: [{message, timestamp(ms)}]; opts: sequence_token |
use AWS::EC2
| Fn | Returns / notes |
|---|---|
| EC2::instances %opts | @instances — DescribeInstances; opts: instance_ids |
| EC2::start $ids, %opts | @changes — StartInstances |
| EC2::stop $ids, %opts | @changes — StopInstances; opts: force |
| EC2::security_groups %opts | @groups — opts: group_ids |
| EC2::vpcs %opts | @vpcs — opts: vpc_ids |
| EC2::reboot $ids, %opts | @ids — RebootInstances |
use AWS::KMS — Key Management Service
| Fn | Returns / notes |
|---|---|
| KMS::keys %opts | @{ {key_id, key_arn} } — ListKeys |
| KMS::describe $key_id, %opts | \%info — DescribeKey |
| KMS::encrypt $key_id, $plaintext, %opts | $ciphertext (base64) |
| KMS::decrypt $ciphertext, %opts | $plaintext — opts: key_id |
| KMS::data_key $key_id, %opts | { key_id, plaintext, ciphertext } — opts: key_spec (default AES_256) |
use AWS::IAM
| Fn | Returns / notes |
|---|---|
| IAM::users %opts | @users — ListUsers |
| IAM::roles %opts | @roles — ListRoles |
| IAM::user %opts | \%user — GetUser; opts: user_name |
| IAM::role_policies $role_name, %opts | @{ {policy_name, policy_arn} } |
| IAM::role $role_name, %opts | \%role — GetRole; { role_name, role_id, arn, path, description, max_session_duration } |
use AWS::Kinesis
| Fn | Returns / notes |
|---|---|
| Kinesis::list %opts | @stream_names — ListStreams |
| Kinesis::describe $stream_name, %opts | \%info — DescribeStream |
| Kinesis::put $stream_name, $partition_key, $data, %opts | \%resp — PutRecord |
use AWS::ECR — Elastic Container Registry
| Fn | Returns / notes |
|---|---|
| ECR::repositories %opts | @repos — DescribeRepositories; opts: repository_names |
| ECR::images $repository_name, %opts | @{ {image_digest, image_tag} } — ListImages |
use AWS::StepFunctions
| Fn | Returns / notes |
|---|---|
| StepFunctions::list %opts | @{ {name, arn, type, creation_date} } — ListStateMachines |
| StepFunctions::start $state_machine_arn, %opts | { execution_arn, start_date } — opts: name, input |
| StepFunctions::describe $execution_arn, %opts | \%info — DescribeExecution |
Flat extras on use AWS
| Fn | Returns / notes |
|---|---|
| AWS::s3_copy_object $src_bucket, $src_key, $bucket, $key, %opts | \%resp |
| AWS::s3_delete_objects $bucket, \@keys, %opts | @deleted — batch delete (≤1000) |
| AWS::ddb_update_item $table, \%key, \%updates, %opts | 1 | 0 — SET each attr |
| AWS::ddb_batch_get_item $table, \@keys, %opts | @items — ≤100 keys |
| AWS::ddb_batch_write_item $table, %opts | $count — opts: puts (≤25), deletes |
DynamoDB type encoding
DynamoDB items are plain JSON in and out — no AttributeValue wrappers. The cdylib maps plain JSON to DynamoDB AttributeValue automatically:
| JSON | AttributeValue |
|---|---|
| null | NULL(true) |
| bool | BOOL |
| number | N (stringified to preserve precision) |
| string | S (or B when the string is "base64:...") |
| array | L (heterogeneous) |
| object | M |
Set types (SS / NS / BS) round-trip out as JSON arrays. On the write path, pass them as an array (stored as L), or extend the cdylib export list if you need a typed set.
LocalStack / MinIO
val %ls = (endpoint => "http://localhost:4566", region => "us-east-1") AWS::S3::buckets(%ls) |> ep AWS::SQS::list(%ls) |> ep
$ENV{AWS_ENDPOINT_URL} works as a global default endpoint for every call.
Pure helpers (no AWS calls)
These open no client — pure string parsing / validation, so they run with no credentials. Available on use AWS.
| Fn | Returns |
|---|---|
| AWS::parse_arn($arn) | { partition, service, region, account_id, resource, resource_type, resource_id } |
| AWS::build_arn(%opts) | $arn — parts → ARN; inverse of parse_arn |
| AWS::parse_s3_uri($uri) | { bucket, key } |
| AWS::build_s3_uri($b, $k?) | $uri — inverse of parse_s3_uri |
| AWS::s3_uri_to_arn($uri, $partition?) | { arn, bucket, key } — s3://b/k → arn:aws:s3:::b/k |
| AWS::arn_to_s3_uri($arn) | { uri, bucket, key } — inverse of s3_uri_to_arn |
| AWS::valid_bucket_name($n) | { name, valid, reason } |
| AWS::valid_s3_key($key) | { key, valid, reason, bytes } — non-empty, ≤1024 UTF-8 bytes |
| AWS::valid_sqs_queue_name($n) | { name, valid, reason, fifo } — ≤80 chars, FIFO ends .fifo |
| AWS::valid_account_id($id) | { account_id, valid, reason } — exactly 12 decimal digits |
| AWS::valid_arn($arn) | { arn, valid, reason } — non-throwing structure check |
| AWS::arn_matches($pattern, $arn) | { pattern, arn, matches } — IAM policy resource matching (* / ?) |
| AWS::partition_for_region($r) | { region, partition } — cn-*→aws-cn, us-gov-*→aws-us-gov, … |
| AWS::valid_region($r) | { region, valid, partition, reason } — botocore regionRegex per partition |
| AWS::dns_suffix_for_partition($p) | { partition, dns_suffix } — aws→amazonaws.com, aws-cn→amazonaws.com.cn, … |
| AWS::service_endpoint($svc, $region) | { service, region, partition, dns_suffix, endpoint, url } |
| AWS::parse_service_endpoint($endpoint) | inverse of service_endpoint; host or URL → parts |
| AWS::s3_object_url($bucket, $region, $key?) | { url, bucket, region, partition, host } — virtual-hosted https URL |
| AWS::parse_s3_url($url) | inverse of s3_object_url; virtual-hosted + path styles |
| AWS::region_for_az($az) | { az, region, zone_letter } — us-east-1a → us-east-1 |
Tests
cargo test # compiles, no live calls s test t/ # creds-aware end-to-end # Opt into per-service round-trips with a writable resource: export STRYKE_AWS_TEST_BUCKET=my-test-bucket export STRYKE_AWS_TEST_TABLE=stryke-aws-demo # PK `id: S` export STRYKE_AWS_TEST_QUEUE=https://sqs.us-east-1.amazonaws.com/.../my-q s test t/
The suite skips cleanly when the cdylib isn't installed, when credentials are missing, or when the per-service env vars are unset. The dev workflow targets — make (release build), make debug, make test, make install, make clean — wrap the same steps.
Why a package, not a builtin
The official aws-sdk-rust crates pull in tokio, hyper, rustls, and a fat chain of smithy / signing / endpoint-resolution support code. The 17 SDKs combined are too much to bake into stryke core. This package ships them once, opt-in.
The stryke side is a thin .stk wrapper that calls aws__* FFI symbols on the cdylib; the heavy code lives in libstryke_aws.{dylib,so}, dlopened in-process on first use AWS. Core stryke is never linked against this package's deps.
FFI layer
Each AWS::* wrapper builds a JSON args dict and calls a sibling aws__* symbol resolved out of libstryke_aws.{dylib,so}, listed in the [ffi] exports table in stryke.toml. The cdylib is dlopened in-process on first use AWS (via stryke's pkg::commands::try_load_ffi_for resolver hook); a shared tokio runtime + a per-region aws_config::SdkConfig cache is held in OnceCell — no fork-per-call, no full IMDS/SSO/env creds chain on each call. Responses are JSON; errors come back as {"error": "<msg>"} and the wrapper dies with the message.
Layout
stryke-aws/ ├── Cargo.toml # cdylib crate manifest (crate-type = ["cdylib"], publish = false) ├── src/ │ └── lib.rs # cdylib — aws__* extern "C" exports ├── lib/ # stryke .stk wrappers — AWS, AWS::S3, AWS::Dynamo, … (17 services) ├── stryke.toml # stryke package manifest ([ffi] exports table) ├── t/ # zunit-style tests ├── examples/ # runnable .stk examples ├── Makefile # `make install` builds + installs └── docs/ # this site (GitHub Pages)
Sibling packages
Part of the stryke package family. Browse the others via the MenkeTechnologiesMeta umbrella repo:
- stryke-arrow — Apache Arrow / Parquet / Feather
- stryke-azure — Blob, Queues, Cosmos DB, Key Vault, Entra
- stryke-clickhouse — ClickHouse
- stryke-demo — live demos for every connector
- stryke-docker — Docker daemon API
- stryke-duckdb — embedded DuckDB SQL engine
- stryke-email — email + campaign client
- stryke-fleet — parallel expect/PTY automation
- stryke-gcp — Cloud Storage, Pub/Sub, Secret Manager, BigQuery, Firestore
- stryke-grpc — reflection-based gRPC client
- stryke-gui — mouse / keyboard / screen / clipboard automation
- stryke-k8s — Kubernetes
- stryke-kafka — Apache Kafka
- stryke-mcpd — MCP servers without a runtime
- stryke-mongo — MongoDB
- stryke-mssql — Microsoft SQL Server
- stryke-mysql — MySQL / MariaDB
- stryke-neo4j — Neo4j graph (Bolt)
- stryke-office — Office docs / PDF / images
- stryke-parquet — Parquet file inspector
- stryke-polars — Polars + ndarray + linalg + FFT
- stryke-postgres — PostgreSQL
- stryke-redis — Redis / Valkey
- stryke-scrape — web scraping / crawling
- stryke-scylla — ScyllaDB / Cassandra
- stryke-search — Elasticsearch / OpenSearch
- stryke-selenium — browser automation (WebDriver)
- stryke-spark — Spark Connect (no JVM)
- stryke-utils — pure-stryke utility belt
- stryke-zmq — ZeroMQ messaging