// STRYKE-AZURE — AZURE CLIENT FOR STRYKE // BLOB + QUEUE + COSMOS + KEY VAULT + IDENTITY + ARM MANAGEMENT PLANE

stryke package · namespace Azure · cdylib libstryke_azure · opt-in (kept out of stryke core)

Report GitHub Issues

>_STRYKE-AZURE

The cloud, one stryke pipe away. Azure client for stryke — Blob Storage, Storage Queues, Cosmos DB, Key Vault Secrets and Keys, Entra identity, plus an Azure Resource Manager management plane: Virtual Machines, Storage-account management, Service Bus, Monitor, Container Instances / AKS, Resource Groups, and Subscriptions. An opt-in package, kept out of the stryke core binary so the daily-driver install stays slim. The Azure SDK pulls in tokio, hyper, rustls, and reqwest — far too heavy to bake into stryke core.

It holds one shared tokio runtime and one Entra credential (DeveloperToolsCredential — the Azure CLI / azd chain), reused across every service client. No per-call fork, no per-call credential rebuild. The typed data-plane clients come from the GA Azure SDK for Rust; the management plane reaches Azure Resource Manager over REST using the same shared credential and the same azure_core HTTP stack — one auth path, one runtime, one HTTP client.

89
FFI exports
13
Packages (Azure + 12)
v0.18.0
Package version
cdylib
Load model (dlopen)

Install

# build the cdylib, install as a stryke package
cd ~/projects/stryke-azure
cargo build --release
s pkg install -g .

# one-liner
make install

# sign in first (DeveloperToolsCredential uses the az / azd chain)
az login

The release build produces target/release/libstryke_azure.{dylib,so}; s pkg install -g . places it in ~/.stryke/store/azure@<ver>/. The cdylib is dlopened in-process on first use Azure.

Service map

stryke-azure mirrors the surface of stryke-aws, mapped onto Azure's GA Rust SDK.

AWS serviceAzure service / backing crate
S3Blob Storage — azure_storage_blob
SQSStorage Queues — azure_storage_queue
DynamoDBCosmos DB (NoSQL) — azure_data_cosmos (preview)
Secrets Manager / SSM Parameter StoreKey Vault Secrets — azure_security_keyvault_secrets
KMSKey Vault Keys — azure_security_keyvault_keys
STSEntra identity token — azure_identity
EC2Virtual Machines — ARM REST
ECS / EKSContainer Instances / AKS — ARM REST
SNS / SQS (enterprise)Service Bus — ARM + data-plane REST
CloudWatchAzure Monitor metrics — ARM REST
(account mgmt)Storage account management — ARM REST
(resource mgmt)Resource Groups — ARM REST

The typed data-plane services (Blob, Queue, Cosmos, Secrets, Keys, identity) come from the GA Azure SDK for Rust. The management plane has no GA typed Rust SDK crate, so those ops reach Azure Resource Manager over REST — using the same shared credential and the same azure_core HTTP client the typed clients use under the hood.

Usage — data plane

use Azure
use Azure::Blob
use Azure::Cosmos
use Azure::Queue
use Azure::Secrets
use Azure::Keys

# Connectivity probe (Entra token; the token value is never returned).
val $tok = Azure::identity_token()
p "expires: $tok->{expires_on}"

# Blob Storage — az://container/blob URIs.
Azure::Blob::create_container("data", account => "mystorage")
val @containers = Azure::Blob::containers(account => "mystorage")
Azure::Blob::put("az://data/hello.txt", data => "hi", account => "mystorage")
val $body = Azure::Blob::get("az://data/hello.txt", account => "mystorage")
Azure::Blob::delete_container("data", account => "mystorage")

# Storage Queues.
Azure::Queue::create("jobs", account => "mystorage")
Azure::Queue::send("jobs", "payload", account => "mystorage")
val @msgs = Azure::Queue::receive("jobs", max => 10, account => "mystorage")
Azure::Queue::drop("jobs", account => "mystorage")   # delete the whole queue

# Cosmos DB (single-partition).
Azure::Cosmos::create_database("appdb", account => "mycosmos")
Azure::Cosmos::create_container("appdb", "users", "/tenant", account => "mycosmos")
Azure::Cosmos::put("appdb", "users", "acme",
    { id => "u1", name => "ada", tier => "gold" })
val $u = Azure::Cosmos::get("appdb", "users", "acme", "u1")

# Key Vault Secrets.
val $pw = Azure::Secrets::get("db-password", vault => "my-kv")
val @vers = Azure::Secrets::versions("db-password", vault => "my-kv")

# Key Vault Keys — RSA encrypt/decrypt (KMS analog) + list/get.
val $enc = Azure::Keys::encrypt("wrap-key", "secret data", vault => "my-kv")
val $clear = Azure::Keys::decrypt("wrap-key", $version, $enc->{ciphertext}, vault => "my-kv")
val @keys = Azure::Keys::ls(vault => "my-kv")
val $jwk = Azure::Keys::get("wrap-key", vault => "my-kv")->{jwk}

Usage — management plane (Azure Resource Manager)

These take a subscription from the subscription => opt or AZURE_SUBSCRIPTION_ID; scoped ops add resource_group => "<rg>". The management plane reaches ARM over REST on the shared credential and HTTP stack.

use Azure::ResourceGroups
use Azure::Compute
use Azure::Storage
use Azure::ServiceBus
use Azure::Monitor
use Azure::Containers
use Azure::Subscriptions

# Subscriptions + regions — tenant-scoped discovery (no subscription opt for ls).
val @subs = Azure::Subscriptions::ls()
val @regions = Azure::Subscriptions::locations(subscription => $sub)

# Resource Groups + provider-agnostic inventory.
val @rgs = Azure::ResourceGroups::ls(subscription => $sub)
Azure::ResourceGroups::create("app-rg", "eastus", subscription => $sub)
val @stores = Azure::ResourceGroups::resources(subscription => $sub,
    filter => "resourceType eq 'Microsoft.Storage/storageAccounts'")

# Virtual Machines — list, get, power actions (long-running), live state + SKUs.
val @vms = Azure::Compute::ls(subscription => $sub, resource_group => "app-rg")
Azure::Compute::start("web1", subscription => $sub, resource_group => "app-rg")
val $st = Azure::Compute::status("web1", subscription => $sub, resource_group => "app-rg")
p "power: $st->{power_state}"                          # running / stopped / deallocated
val @sizes = Azure::Compute::skus(subscription => $sub, location => "eastus")

# Storage account management (distinct from data-plane Blob/Queue).
val @accts = Azure::Storage::ls(subscription => $sub)
val $keys = Azure::Storage::keys("mystorage", subscription => $sub, resource_group => "app-rg")

# Service Bus — queues/topics/namespaces listing (mgmt) + send/receive (data plane).
val @queues = Azure::ServiceBus::queues("myns", subscription => $sub, resource_group => "app-rg")
val @topics = Azure::ServiceBus::topics("myns", subscription => $sub, resource_group => "app-rg")
val @namespaces = Azure::ServiceBus::namespaces(subscription => $sub)
Azure::ServiceBus::send("myns", "orders", "payload")
val $msg = Azure::ServiceBus::receive("myns", "orders")

# Monitor — metrics for any resource by its ARM id.
val $m = Azure::Monitor::metrics($vm_resource_id, metrics => "Percentage CPU")

# Container Instances + AKS (incl. live node pools).
val @groups = Azure::Containers::groups(subscription => $sub)
val @clusters = Azure::Containers::clusters(subscription => $sub)
val @pools = Azure::Containers::node_pools("mycluster", subscription => $sub, resource_group => "app-rg")

Pure helpers (no Azure)

These open no client — credential-free string parsing, building, and validation usable from any pipeline:

Azure::parse_resource_id($id)            # /subscriptions/.../providers/... → { subscription, resource_group, provider, types, resource_type, name }
Azure::resource_id_parent($id)           # RBAC-scope "dirname" → { id, parent, has_parent }
Azure::build_resource_id(%opts)          # inverse of parse_resource_id (canonical ARM casing)
Azure::parse_connection_string($cs)      # Key=Value;... → { pairs => { Key => Value } }
Azure::build_connection_string(%pairs)   # inverse of parse_connection_string (byte-identical round-trip)
Azure::redact_connection_string($cs)     # mask AccountKey / SharedAccessSignature → { redacted, masked_count }
Azure::parse_blob_uri($uri)              # https://<acct>.blob.core.windows.net/<container>/<blob> → { account, service, host, container, blob }
Azure::build_blob_uri(%opts)             # inverse of parse_blob_uri
Azure::storage_endpoint(%opts)           # account/service/cloud → { endpoint, url, suffix, … } (public/china/usgov)
Azure::parse_storage_endpoint($endpoint) # inverse: host/URL → { endpoint, account, service, cloud, suffix, url }
Azure::valid_storage_account_name($name) # 3-24 lowercase alphanumerics
Azure::valid_container_name($name)       # Blob container rules
Azure::valid_blob_name($name)            # 1-1024 chars, ≤254 path segments, no trailing dot/slash
Azure::valid_keyvault_secret_name($name) # 1-127 chars, alphanumeric + hyphens
Azure::valid_queue_name($name)           # Queue DNS-label grammar
Azure::valid_table_name($name)           # ^[A-Za-z][A-Za-z0-9]{2,62}$, "tables" reserved
Azure::valid_cosmos_id($id)              # ≤255 chars, no / or \
Azure::valid_guid($guid)                 # 8-4-4-4-12 hex
Azure::normalize_guid($guid)             # canonical lowercase 8-4-4-4-12
Azure::format_guid($guid, $format?)      # .NET specifier: N / D / B {…} / P (…)

Each valid_* helper returns { name, valid, reason } so a pipeline can branch on the boolean and surface the reason.

Connection options

Each call takes a trailing %opts hash carrying the target account/vault. Authentication uses DeveloperToolsCredential — sign in with az login or azd auth login first.

ServiceOptions
Blob / Queueaccount => "<storageacct>" (or AZURE_STORAGE_ACCOUNT), or endpoint => "https://..."
Cosmosaccount => "<cosmosacct>" (or AZURE_COSMOS_ACCOUNT), or endpoint, plus region => "East US"
Secrets / Keysvault => "<kvname>" (or AZURE_KEYVAULT_NAME), or vault_url => "https://<kv>.vault.azure.net/"
Management plane (ARM)subscription => "<id>" (or AZURE_SUBSCRIPTION_ID); scoped ops add resource_group => "<rg>"; sovereign clouds via arm_endpoint => "..."
Service Bus (data)namespace => "<ns>", or sb_endpoint => "https://<ns>.servicebus.windows.net"

Packages

The cdylib exports 89 FFI symbols, surfaced through the flat Azure namespace plus twelve typed sub-packages.

PackageSurface
AzureFlat API over every export (Azure::blob_*, Azure::queue_*, Azure::cosmos_*, Azure::secrets_*, Azure::keys_*, Azure::vm_*, Azure::servicebus_*, Azure::identity_token), plus all pure helpers, Azure::version, and Azure::ping.
Azure::Blobaz://container/blob URI helpers — ls, get, put, head, rm, containers, create_container, delete_container, set_metadata.
Azure::Queuels, send, receive, delete, clear, count, create, drop, and a pump receive→callback→delete loop.
Azure::CosmosDocument helpers — databases, containers, put, get, delete, query, replace, create_database, create_container, delete_database, delete_container.
Azure::SecretsKey Vault — get, set, ls, rm, versions, backup, plus param_get / param_put / param_delete aliases for parameter-store-style callers.
Azure::KeysKey Vault keys (KMS analog) — encrypt, decrypt, ls, get.
Azure::ComputeVirtual Machines — ls, get, start, stop, deallocate, restart, status (live power state), skus (VM sizes).
Azure::ContainersContainer Instances + AKS — groups, group, clusters, cluster, node_pools.
Azure::StorageStorage-account management — ls, get, keys.
Azure::ResourceGroupsResource-group management — ls, get, create, rm, resources (provider-agnostic inventory, ARM $filter).
Azure::ServiceBusService Bus messaging — queues, topics, namespaces, send, receive.
Azure::SubscriptionsTenant/subscription discovery — ls (all subscriptions), locations (regions).
Azure::MonitorAzure Monitor metrics (CloudWatch analog) — metrics.

Why a package, not a builtin

The official Azure SDK for Rust crates pull in tokio, hyper, rustls, and reqwest, plus the typespec client-core and per-service generated models. That dependency tree ships once, on demand, as an opt-in package — stryke core is never linked against it.

The stryke side is a thin .stk wrapper; the heavy code lives in the libstryke_azure cdylib and is dlopened on first use Azure. The cdylib holds one shared tokio runtime and one Entra credential, reused across every service client — no per-call fork, no per-call credential rebuild.

FFI contract

Each export is a #[no_mangle] extern "C" fn azure__* taking a JSON request string and returning a JSON response string. stryke's FFI bridge resolves these symbols at load. Handler errors and panics are caught and surfaced as {"error": "..."} — a panic never crosses the C ABI into the host shell.

# the .stk wrapper decodes the JSON and dies on an {error} payload
fn Azure::_decode ($name, $json) {
    val $o = from_json $json
    if (ref $o eq "HASH" && exists $o->{error}) {
        die "Azure::$name: $o->{error}"
    }
    $o
}

Build & test

make            # release build (default) → target/release/libstryke_azure.{dylib,so}
make debug      # cargo build
make test       # cargo test, then `s test t/`
make install    # cargo build --release && s pkg install -g .
cargo test      # Rust unit tests (endpoint + FFI-safety pins; offline)

The Rust unit tests are offline endpoint-construction and FFI-safety pins. The t/ surface tests run under s test t/ and skip without Azure credentials. CI (.github/workflows/ci.yml) runs check, fmt, clippy -D warnings, test on ubuntu + macos, doc with -D warnings, and a release build on three targets.

Examples

Runnable .stk scripts under examples/:

  • examples/discover.stk — credential probe + read-only tour, CI-safe (an eval-guarded identity_token returns undef with no reachable credential, so the script runs end-to-end without Azure).
  • examples/blob_browse.stk — list a container prefix.

Layout

stryke-azure/
├── Cargo.toml             # cdylib crate (publish = false)
├── src/
│   └── lib.rs            # azure__* FFI exports + service ops
├── lib/                   # stryke .stk wrappers (Azure + namespaces)
├── stryke.toml            # stryke package manifest + ffi exports
├── t/                     # offline surface 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: