origincloud-core 0.31.0

The client every service crate of the Origin Cloud SDK uses: credentials, endpoints, retries, errors, long-running operations and read-your-writes.
Documentation

Origin Cloud SDK for Rust

The Rust client for Origin Cloud's REST APIs. Its service crates are generated from the API definitions in origincloudhq/api; origincloud-core is what they share: credentials, endpoints, retries, errors, long-running operations and read-your-writes.

[dependencies]
origincloud-core = "0.1"
origincloud-pvs = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Crate API
origincloud-core The client every service crate uses, and longrunning::v1beta1 for resuming an operation by name
origincloud-pvs Persistent volumes: volumes, snapshots, storage classes (origincloud_pvs::v1beta1)
origincloud-location Regions and zones, and the services each zone offers (origincloud_location::v1beta1)
origincloud-compute Instances, images, key pairs, volume attachments and the instance catalog (origincloud_compute::v1beta1)
origincloud-network VPCs, subnets, routing, gateways, floating IPs, network interfaces, security groups, ACLs and DNS policies (origincloud_network::v1beta1)
origincloud-resourcemanager Organizations, projects and their quotas (origincloud_resourcemanager::v1beta1)
origincloud-iam Users, service accounts, roles, role bindings, permissions and the audit log (origincloud_iam::v1beta1)
origincloud-billing Billing accounts and the projects they pay for, payment methods, cost and usage, spend limits and the price list (origincloud_billing::v1beta1)
origincloud-observability The metrics the platform collects for a project's compute instances, volumes and network interfaces (origincloud_observability::v1beta1)
origincloud-support Support cases, each a conversation between an organization and Origin Cloud support about a question or a problem (origincloud_support::v1beta1)
origincloud-dns Hosted zones, their record sets, and private zones' VPC attachments (origincloud_dns::v1beta1)
origincloud-sts The security token service: it turns a token from an identity provider outside Origin Cloud, such as a CI job's OIDC token, into the access token of a WORKLOAD service account that trusts it through a federated credential (origincloud_sts::v1beta1)
origincloud-kms Grants: permission for one platform host to use one key for one operation and one encryption context, for at most an hour at a time (origincloud_kms::v1beta1)

Example

use origincloud_core::protobuf::FieldMask;
use origincloud_core::Client;
use origincloud_pvs::v1beta1::{
    CreateVolumeRequest, DeleteVolumeRequest, UpdateVolumeRequest, Volume, VolumeClient,
};

#[tokio::main]
async fn main() -> Result<(), origincloud_core::Error> {
    let client = Client::new()?;
    let volumes = VolumeClient::new(&client);

    let volume = volumes
        .create_volume(CreateVolumeRequest {
            parent: "projects/my-project-123/zones/eu-central1-az1".to_owned(),
            volume: Some(Volume { size_gib: 10, ..Default::default() }),
            ..Default::default()
        })
        .await?
        .wait()
        .await?;

    let volume = volumes
        .update_volume(UpdateVolumeRequest {
            volume: Some(Volume { name: volume.name, size_gib: 20, ..Default::default() }),
            update_mask: Some(FieldMask::new(["size_gib"])),
            ..Default::default()
        })
        .await?
        .wait()
        .await?;
    println!("{} has {} GiB", volume.name, volume.size_gib);

    volumes
        .delete_volume(DeleteVolumeRequest { name: volume.name, ..Default::default() })
        .await?
        .wait()
        .await?;
    Ok(())
}

Credentials

Client::new takes the first of:

  1. ORIGINCLOUD_ACCESS_TOKEN: an access token, sent as it is.
  2. ORIGINCLOUD_CLIENT_ID and ORIGINCLOUD_CLIENT_SECRET: a service account's credential, exchanged for access tokens as they expire. This is what automation should use where it can't present an OIDC token.
  3. A CI job's OIDC token, exchanged at the security token service for the access token of the WORKLOAD service account whose federated credential trusts the job: no secret to store. In a GitHub Actions job granted permissions: id-token: write there is nothing to configure; elsewhere, ORIGINCLOUD_OIDC_TOKEN_FILE names a file holding the token (any CI provider's, or a Kubernetes projected token), read again at every exchange, or ORIGINCLOUD_OIDC_TOKEN holds the token itself, as GitLab CI sets it for an id_tokens entry of that name; the file comes first. The token is exchanged again a minute before the access token expires, with a fresh job token; a token given in ORIGINCLOUD_OIDC_TOKEN can't be renewed, so it is exchanged again as it is, until it expires itself. Once found, this step's errors are returned rather than passed over; ORIGINCLOUD_OIDC_DISABLED=true leaves it out, ORIGINCLOUD_STS_ENDPOINT points it at another STS (default https://sts.origincloudapis.eu) and ORIGINCLOUD_OIDC_AUDIENCE requests GitHub's token for another audience (the same default).
  4. The session the origin CLI stored at its last login (origin id login).
  5. On an instance that runs as a service account, its credential from the instance's metadata service: nothing to configure, and no secret on the instance. Off an instance, this step fails within about two seconds; ORIGINCLOUD_METADATA_DISABLED=true leaves it out, and ORIGINCLOUD_METADATA_ENDPOINT points it at another address.

Client::builder().credentials(...) sets them explicitly instead.

Behaviour

  • Endpoints. Every service answers at {service}.origincloudapis.eu, which serves every resource wherever it lives. ClientBuilder::region, or ORIGINCLOUD_REGION, picks a region's front door instead, {service}.{region}.origincloudapis.eu, for the services that have one; a global service, such as location, keeps its regionless host.
  • Names. A resource's name is its relative name; its full resource name, //{service}.origincloudapis.eu/{name}, is what references carry. origincloud_core::FullName parses one (refusing a URN, a relative name, a regional or zonal host, a version), builds one with from_relative, and gives its service, host, relative name and url; principals and roles are iam names (principal, role). A request's name or parent takes either form. A full name is checked against the type the field refers to: the client's own service for its own resources, the referenced type's service otherwise (a billing account's parent takes //resourcemanager.origincloudapis.eu/organizations/o). It is sent as its relative name, in the path and the body alike, and a full name on any other host is refused before anything is sent.
  • Operations. A create, update or delete returns an Operation; its wait returns the resource once the change is complete, or the error the change failed with.
  • Retries. A call answered as retryable is sent again with a growing delay, when sending it again is safe: a read, or a mutation with a request id. The client fills in an empty request id, so a retried create never creates twice. ClientBuilder::max_attempts bounds every call; a service client's with_options(CallOptions::new().max_attempts(1)) turns retries off for the calls made through it, for a mutation that must not be sent twice.
  • Read-your-writes. The client keeps the consistency token each write answers with and sends it with later calls, so a read right after a write, or after waiting on an operation, sees it. The token answered last is the one kept: of concurrent writes, a read is sure to see the last answered. Clients given the same ClientBuilder::consistency_token_store share one token. A call the API refuses because its token isn't one is sent again once without it, and the token is dropped.
  • Pages. Each list method has an all_... counterpart that streams every page.
  • Errors. A refused call returns Error::Api; branch on Error::reason, the platform's error code. ApiError::metadata says more about some reasons, such as the name of the resource a CONFLICT is about.
  • Runtime. The clients are async, on Tokio, and send their calls with reqwest; in a browser, see below.

In a browser

The crates build for wasm32-unknown-unknown, for a web app written in Rust (Leptos, Yew, Dioxus, ...). There is no Tokio there: calls go out through the browser's fetch, retries wait on setTimeout, and the futures run on the app's executor, such as wasm_bindgen_futures::spawn_local. They aren't Send there (the bound is origincloud_core::MaybeSend, the boxed future MaybeSendFuture), but Client and the Credentials it holds still are Send + Sync, so a client can live in the framework's reactive state.

A browser has no environment, files, CI token or metadata service, so only these credentials exist there, and Client::new without them fails with Error::NoCredentials:

  • AccessToken: a token the page already holds, such as the user's from its OIDC login.
  • Your own Credentials, for a token the page renews itself.
  • ServiceAccount only with the feature service-account-wasm, for a wasm runtime that isn't a browser (Node.js, Deno, an edge worker). It is left out by default because a browser app, its Web Workers included, is downloaded by whoever loads it, and a client secret in it with it.
let client = Client::builder()
    .credentials(AccessToken::new(token))
    .build()?;

ClientBuilder::timeout bounds each attempt there too; a reqwest::Client set with ClientBuilder::http_client has no connect timeout or proxy settings in a browser, which leaves them to the browser. A response body is read whole and then held to the same 32 MiB limit. A retry waits at most about 24.8 days there, the longest a browser timer can, however long the server asks.

A call to a method that authenticates no caller, such as billing's legal documents, carries no User-Agent and no consistency token, only Accept, so the browser sends it without a CORS preflight.

Versions

A service crate's modules are its API's versions: origincloud_pvs::v1beta1 is the preview version of the PVS API and may change in incompatible ways, like the API itself. The crates follow semantic versioning; before 1.0, a minor version may change the Rust API incompatibly.

Contributing

Issues and pull requests are welcome. The service crates are generated from the API definitions, so a change to what a service offers starts as an issue; see CONTRIBUTING.md for how the SDK is built and checked.

License

Apache License 2.0; see LICENSE.