// STRYKE-GCP — GOOGLE CLOUD CLIENT FOR STRYKE // CLOUD STORAGE + PUB/SUB

stryke package · cdylib libstryke_gcp.{dylib,so} · dlopen'd in-process on use GCP · opt-in (kept out of stryke core)

Report GitHub Issues
// Color scheme

>_STRYKE-GCP

GCP from the pipe. Google Cloud client for stryke — Cloud Storage, Pub/Sub, Secret Manager, BigQuery, Firestore, Compute Engine, Cloud Run, Cloud Functions, Cloud Logging, Cloud Monitoring, and IAM. Opt-in package, kept out of the stryke core binary so the daily-driver install stays slim; ships as a Rust cdylib that stryke dlopens in-process on first use GCP.

Scope

Twelve service families ship in v0.19.x, exposed through 84 gcp__* FFI entry points and a set of typed .stk wrappers. The package is intentionally narrower than the full GCP REST surface — the goal is “useful from a shell pipeline”, not exhaustive API coverage.

Cloud Storage (GCS)ls / get / put / rm / head / cp / compose / buckets / get+set IAM policy
Pub/Subpublish / pull / ack / list+create+delete topics+subs / get topic / topic subscriptions / pump
Secret Manageraccess / create / add-version / list / list-versions / delete
Auth identityADC + project resolution
BigQueryquery (jobs.query) + streaming insert + list/get datasets + list/get tables + list jobs
Firestoreget / set / delete / list / query / create (native-mode REST)
Compute Enginelist/get instances / start / stop / reset / list zones / regions / machine types / disks
Cloud Runlist/get services / list revisions (Admin API v2)
Cloud Functionslist / describe (API v2)
Cloud Logginglist entries / write entry / list logs (API v2)
Cloud Monitoringlist time series / list metric descriptors (API v3)
IAMget policy / test permissions / list+get service accounts

Install

# from a release (no rustc on the consumer machine)
s pkg install -g github.com/MenkeTechnologies/stryke-gcp

# from a local checkout
cd ~/projects/stryke-gcp
cargo build --release            # produces target/release/libstryke_gcp.{dylib,so}
s pkg install -g .               # cdylib lands in ~/.stryke/store/gcp@<version>/

# one-liner
make install

The cdylib is dlopened in-process on first use GCP — no helper-binary fork per call. Any stryke script that declares use GCP (or a submodule like use GCP::Storage) resolves the package automatically.

Auth

Uses Application Default Credentials — the same chain as gcloud:

  1. $GOOGLE_APPLICATION_CREDENTIALS pointing at a service-account JSON.
  2. gcloud auth application-default login on dev machines.
  3. The GCE / Cloud Run / GKE metadata server when running on GCP.

Set the project once and forget it:

export GOOGLE_CLOUD_PROJECT=my-project-id

Per call, override the project with project => "..." on any public fn. ADC discovery, metadata-server lookup, WIF, and SA-file resolution run once per stryke session — the cached credentials live in OnceCell and are reused on every subsequent call.

GCP::Storage::ls "gs://my-bucket/", project => "other-project"
GCP::PubSub::publish "my-topic", "x", project => "other-project"

Quick start: use GCP

auth + project checkp to_json GCP::identity()
list GCS keysmy @entries = GCP::Storage::ls "gs://my-bucket/prefix/", delimiter => "/"
put an objectGCP::Storage::put "gs://my-bucket/hello.txt", data => "hello stryke", content_type => "text/plain"
get an objectp GCP::Storage::get "gs://my-bucket/hello.txt"
publish to Pub/SubGCP::PubSub::publish "my-topic", "event payload", attrs => { source => "stryke" }
pull from Pub/Submy @msgs = GCP::PubSub::pull "my-sub", max => 10, ack => 1
pump (pull → callback → ack)GCP::PubSub::pump "my-sub", iterations => 5, callback => sub ($m) { handle_message $m->{data} }

Project / endpoint overrides apply on every public fn via project => "...". The complete reference is below; the README "API reference" section is the authoritative mirror.

API reference: use GCP

Plumbing fns plus the flat GCP::<service>_<op> entry points the namespaced wrappers delegate to.

GCP::version()cdylib package version string
GCP::ping(%opts)connectivity probe
GCP::identity(%opts){ ok, project, credentials_source }

Pure helpers — credential-free string parsing / validation (no network, no auth):

GCP::parse_gs_uri($uri){ bucket, object }
GCP::build_gs_uri($b, $obj?)bucket+object → gs:// URI (inverse of parse_gs_uri)
GCP::gs_uri_to_url($uri)gs://b/o{ url, bucket, object }
GCP::url_to_gs_uri($url)GCS URL → { uri, bucket, object } (inverse of gs_uri_to_url)
GCP::parse_resource_name($n){ parts, pairs, trailing } for projects/p/topics/t
GCP::resource_name_parent($n){ name, parent, has_parent, collection, id }
GCP::build_resource_name(%opts){ parts } or { pairs, trailing } → resource name
GCP::parse_table_ref($ref)BigQuery project.dataset.table / project:dataset.table{ table_ref, project, dataset, table, legacy }
GCP::build_table_ref(%opts)inverse of parse_table_ref
GCP::valid_bucket_name($name){ name, valid, reason } — GCS bucket rules
GCP::valid_project_id($id)6-30 lowercase/digit/hyphen, start letter, no trailing hyphen
GCP::valid_object_name($name)1-1024 UTF-8 bytes, no CR/LF, not ./..
GCP::region_for_zone($zone)us-central1-a{ zone, region, zone_letter }
GCP::valid_label($key, $value?)Resource Manager label rules
GCP::valid_pubsub_id($id)Pub/Sub topic/sub/schema/snapshot id rules
GCP::valid_service_account_id($id)IAM accountId: 6-30 chars, RFC1035
GCP::valid_secret_id($id)Secret Manager secret ID: 1-255 chars
GCP::valid_dataset_id($id)BigQuery dataset id: ≤1024, letters/digits/underscores
GCP::valid_table_id($id)BigQuery table id: ≤1024 UTF-8 bytes (broader than dataset)

Secret Manager (flat ops under use GCP):

GCP::secret_access($secret, %opts)$value — opt version (default "latest")
GCP::secret_create($secret, %opts)new empty secret, automatic replication
GCP::secret_add_version($secret, $value, %opts)add a new version
GCP::secret_list(%opts)@secret_ids
GCP::secret_list_versions($secret, %opts)@{ {name, state, create_time} } (newest first)
GCP::secret_delete($secret, %opts)deletes the secret and all versions

API reference: use GCP::Storage

GCP::Storage::ls $uri, %opts@entries (opt delimiter)
GCP::Storage::get $uri, %opts$body | $path (when output => "PATH")
GCP::Storage::put $uri, %opts\%respdata => $bytes | input => "PATH"
GCP::Storage::head $uri, %opts\%meta (size, content_type, updated, md5_hash, …)
GCP::Storage::cp $src, $dst, %opts\%resp — server-side copy
GCP::Storage::rm $uri, %opts\%resp
GCP::Storage::buckets %opts@buckets
GCP::Storage::compose $bucket, $dst, \@sourcesconcatenate objects
GCP::Storage::get_iam_policy $bucket\%policy
GCP::Storage::set_iam_policy $bucket, \%policy\%resp

ls entries: {type=>"object", key, size, content_type, md5, etag, updated, storage_class, generation} or {type=>"prefix", key} when delimiter is set.

API reference: use GCP::PubSub

GCP::PubSub::publish $topic, $data, %opts\%resp — opts attrs, ordering_key
GCP::PubSub::pull $sub, %opts@messages — opts max, deadline, ack
GCP::PubSub::ack $sub, $ids_or_aref, %opts\%resp
GCP::PubSub::topics %opts@topic_names
GCP::PubSub::subs %opts@{ {name, topic} }
GCP::PubSub::create_topic $topic, %opts\%resp
GCP::PubSub::create_sub $name, $topic, %opts\%resp — opt ack_deadline
GCP::PubSub::delete_topic $topic, %opts\%resp
GCP::PubSub::delete_sub $sub, %opts\%resp
GCP::PubSub::get_topic $topic, %opts{ topic, resource }
GCP::PubSub::topic_subs $topic, %opts@subscription_ids
GCP::PubSub::pump $sub, %opts$count — callback + auto-ack

Topic / subscription names accept bare (my-topic) or fully qualified (projects/PROJECT/topics/my-topic) forms. Bare names expand against $opts{project} or $GOOGLE_CLOUD_PROJECT.

API reference: use GCP::BigQuery

GCP::BigQuery::query $sql, %opts{ columns, rows, total_rows, complete } — opts max_results, timeout_ms, project
GCP::BigQuery::rows $sql, %opts@rows — just the row hashrefs
GCP::BigQuery::insert $dataset, $table, \@rows, %opts{ inserted, errors } — streaming insert
GCP::BigQuery::datasets %opts@dataset_ids
GCP::BigQuery::dataset $dataset, %opts{ dataset, resource }
GCP::BigQuery::tables $dataset, %opts@table_ids
GCP::BigQuery::table $dataset, $table, %opts{ dataset, table, resource }
GCP::BigQuery::jobs %opts@{ {id, state, job_type, user_email} } — opts all_users, max_results, state_filter

API reference: use GCP::Firestore

GCP::Firestore::get $collection, $document, %opts\%data | undef
GCP::Firestore::set $collection, $document, \%data, %opts\%resp — create-or-overwrite
GCP::Firestore::delete $collection, $document, %opts\%resp
GCP::Firestore::list $collection, %opts@{ {id, data} } — opt page_size
GCP::Firestore::query $collection, %opts@{ {id, data} } — opts field, op, value, limit
GCP::Firestore::create $collection, \%data, %opts{ collection, id } — auto-id; opt document

Field values cross as plain stryke data; the cdylib handles Firestore's typed encoding (stringValue / integerValue / …) in both directions. Flat forms are GCP::firestore_get / _set / _delete / _list.

API reference: use GCP::Compute

GCP::Compute::instances $zone, %opts@{ {name, status, machine_type, zone} }
GCP::Compute::get $zone, $instance, %opts{ zone, instance, resource }
GCP::Compute::start $zone, $instance, %opts{ instance, action, operation, status }
GCP::Compute::stop $zone, $instance, %opts{ instance, action, operation, status }
GCP::Compute::reset $zone, $instance, %opts{ instance, action, operation, status }
GCP::Compute::zones %opts@{ {name, region, status} }
GCP::Compute::regions %opts@{ {name, status} }
GCP::Compute::machine_types $zone, %opts@{ {name, guest_cpus, memory_mb, description} }
GCP::Compute::disks $zone, %opts@{ {name, size_gb, type, status, zone} }

API reference: Run / Functions / Logging / Monitoring / IAM

use GCP::Run

GCP::Run::services $region, %opts@{ {name, uri, latest_revision} }
GCP::Run::get $region, $service, %opts{ region, service, resource }
GCP::Run::revisions $region, $service, %opts@{ {name, create_time} }

use GCP::Functions

GCP::Functions::list %opts@{ {name, state, url} } — opt region (default "-" = all)
GCP::Functions::describe $region, $function, %opts{ region, function, resource }

use GCP::Logging

GCP::Logging::entries %opts@{ {timestamp, severity, log_name, text_payload, json_payload} } — opts filter, page_size (50), order (asc/desc)
GCP::Logging::write $log, %opts{ log, written } — opts text | json, severity
GCP::Logging::logs %opts@log_ids

use GCP::Monitoring

GCP::Monitoring::time_series $filter, $start, $end, %opts@series — interval is RFC3339
GCP::Monitoring::descriptors %opts@{ {type, display_name, kind, value_type} } — opt filter

use GCP::IAM

GCP::IAM::get_policy $url, %opts\%policy — full :getIamPolicy endpoint
GCP::IAM::test_permissions $url, \@perms, %opts@granted — full :testIamPermissions endpoint
GCP::IAM::service_accounts %opts@{ {email, display_name, unique_id} }
GCP::IAM::service_account $email, %opts{ email, resource }

Examples

Runnable .stk scripts ship under examples/. Each is read-only or eval-guarded so it exits cleanly when ADC is unreachable.

discover.stks examples/discover.stk — version + ADC source + identity + bucket count tour
whoami.stks examples/whoami.stk — ADC identity + visible GCS bucket summary
gcs_browse.stks examples/gcs_browse.stk gs://bucket/prefix/ — list a GCS prefix
gcs_put_get.stks examples/gcs_put_get.stk gs://bucket/key — round-trip a string through GCS
pubsub_pump.stks examples/pubsub_pump.stk my-sub — long-poll a Pub/Sub subscription
# pubsub_pump.stk core — pull → callback → ack, 5 iterations
val $n = GCP::PubSub::pump($sub,
    iterations => 5,
    max        => 10,
    callback   => sub ($m) {
        p "got $m->{message_id}: $m->{data}"
    })
p "handled $n messages"

Tests & dev workflow

cargo test                       # compiles, no live calls
s test t/                        # ADC-aware end-to-end

# opt into per-service round-trips
export STRYKE_GCP_TEST_BUCKET=my-test-bucket
export STRYKE_GCP_TEST_TOPIC=my-test-topic
export STRYKE_GCP_TEST_SUB=my-test-sub
s test t/

make            # release build
make debug
make test
make install
make clean

The suite skips cleanly when the cdylib isn't installed, when ADC isn't reachable, or when the per-service env vars are unset.

Why a package, not a builtin

GCP integration needs an auth chain (Application Default Credentials), a TLS HTTP client, and JSON serialization — fine to bundle once, opt-in. The cdylib talks to GCP's REST APIs directly via reqwest + google-cloud-auth.

The stryke side is a thin .stk wrapper that calls gcp__* FFI symbols on the cdylib; the heavy code lives in libstryke_gcp.{dylib,so}, dlopened in-process on first use GCP. Core stryke is never linked against this package's deps.

FFI layer

Each GCP::* wrapper builds a JSON args dict and calls a sibling gcp__* symbol resolved out of libstryke_gcp.{dylib,so}. The cdylib is dlopened in-process on first use GCP; a shared tokio runtime + reqwest::Client + cached ADC credentials are held in OnceCell — no fork-per-call, no re-running of ADC discovery / metadata-server / WIF / SA-file lookup on each call. Responses are JSON; errors come back as {"error": "<msg>"} and the wrapper dies with the message.

Layout

stryke-gcp/
├── Cargo.toml             # cdylib crate manifest (crate-type = ["cdylib"], publish = false)
├── src/
│   └── lib.rs            # cdylib — gcp__* extern "C" exports
├── lib/                   # stryke .stk wrapper(s) — `use GCP`, `use GCP::Storage`, …
├── stryke.toml            # stryke package manifest
├── 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: