A comprehensive Rust SDK for the Tapis Framework, providing type-safe async clients for Tapis v3 services.
Current release line: 0.3.1
Tapis is a distributed API framework for science and engineering research. It provides authentication, authorization, workload execution, data management, and supporting platform services across institutional resources.
The repository is a Cargo workspace:
tapis-sdkis the umbrella crate.- Each service also has its own crate (recommended when you only need a subset).
Module (tapis-sdk) |
Crate | Purpose | Docs |
|---|---|---|---|
actors |
tapis-actors |
Actor-based functions and executions | docs.rs/tapis-actors |
apps |
tapis-apps |
Application definitions and sharing | docs.rs/tapis-apps |
authenticator |
tapis-authenticator |
AuthN/AuthZ, clients, and token APIs | docs.rs/tapis-authenticator |
core |
tapis-core |
Shared traits (TokenProvider) for the SDK |
docs.rs/tapis-core |
files |
tapis-files |
File operations, permissions, transfers | docs.rs/tapis-files |
globus_proxy |
tapis-globus-proxy |
Globus proxy and transfer operations | docs.rs/tapis-globus-proxy |
jobs |
tapis-jobs |
Job submission and lifecycle management | docs.rs/tapis-jobs |
meta |
tapis-meta |
Metadata collections and documents | docs.rs/tapis-meta |
notifications |
tapis-notifications |
Event subscriptions and notifications | docs.rs/tapis-notifications |
pgrest |
tapis-pgrest |
Postgres REST-style data access | docs.rs/tapis-pgrest |
pods |
tapis-pods |
Pods, templates, volumes, snapshots | docs.rs/tapis-pods |
sk |
tapis-sk |
Security kernel and vault/secret APIs | docs.rs/tapis-sk |
streams |
tapis-streams |
Streams/channels and telemetry resources | docs.rs/tapis-streams |
systems |
tapis-systems |
Systems, credentials, scheduler profiles | docs.rs/tapis-systems |
tenants |
tapis-tenants |
Tenant, site, owner, LDAP management | docs.rs/tapis-tenants |
tokens |
tapis-tokens |
Token service APIs | docs.rs/tapis-tokens |
workflows |
tapis-workflows |
Workflow and pipeline orchestration | docs.rs/tapis-workflows |
| umbrella | tapis-sdk |
Re-exports all modules above | docs.rs/tapis-sdk |
Use the umbrella crate:
[dependencies]
tapis-sdk = "0.3.1"
tokio = { version = "1", features = ["full"] }Or use individual crates:
[dependencies]
tapis-systems = "0.3.1"
tapis-apps = "0.3.1"
tapis-jobs = "0.3.1"
tokio = { version = "1", features = ["full"] }Wrappers accept optional token injection:
TapisAuthenticator::new(base_url, None)works for endpoints that do not requireX-Tapis-Token.- Other services typically use
Some(token).
use tapis_sdk::authenticator::{models, TapisAuthenticator};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = std::env::var("TAPIS_BASE_URL")
.unwrap_or_else(|_| "https://dev.develop.tapis.io/v3".to_string());
let authenticator = TapisAuthenticator::new(&base_url, None)?;
let mut req = models::NewToken::new();
req.username = Some(std::env::var("TAPIS_USERNAME")?);
req.password = Some(std::env::var("TAPIS_PASSWORD")?);
req.grant_type = Some("password".to_string());
let token_resp = authenticator.tokens.create_token(req).await?;
println!("Token response status: {:?}", token_resp.status);
Ok(())
}Every service crate exposes a with_headers function that scopes extra HTTP headers to a single call (or any block of calls). The X-Tapis-Token set at client construction is still sent automatically — with_headers only adds (or overrides) the headers you supply.
use http::header::{HeaderMap, HeaderValue};
use tapis_sdk::jobs::{models, with_headers, TapisJobs};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = std::env::var("TAPIS_BASE_URL")
.unwrap_or_else(|_| "https://dev.develop.tapis.io/v3".to_string());
let token = std::env::var("TAPIS_TOKEN")?;
let jobs = TapisJobs::new(&base_url, Some(token.as_str()))?;
// Build the extra headers for this call only.
let mut hdrs = HeaderMap::new();
hdrs.insert("X-Tapis-Tenant", HeaderValue::from_static("tacc"));
hdrs.insert("X-Request-Id", HeaderValue::from_static("trace-abc-123"));
// Wrap any call (or async block) in with_headers.
// The JWT is still sent automatically; extra headers are layered on top.
let job = with_headers(
hdrs,
jobs.jobs.get_job("your-job-uuid"),
).await?;
println!("Job: {:?}", job);
// Calls without with_headers are unaffected — no extra headers sent.
let status = jobs.jobs.get_job_status("your-job-uuid").await?;
println!("Status: {:?}", status);
Ok(())
}Precedence: if you supply
X-Tapis-Tokeninsidewith_headers, it overrides the token set on the client for that call. This enables per-call auth (e.g. impersonation or multi-tenant proxying) without rebuilding the client.
Every service client supports an optional TokenProvider for transparent token refresh. When a request is about to be sent and the current JWT has fewer than 5 seconds until expiry, the RefreshMiddleware calls provider.get_token() and substitutes the fresh token — no manual intervention required.
TokenProvider lives in the tapis-core crate (re-exported as tapis_sdk::core::TokenProvider). Implement it on any struct that knows how to obtain a fresh token:
use std::sync::Arc;
use async_trait::async_trait;
use tapis_sdk::core::TokenProvider;
/// Refreshes tokens via the Tapis password grant.
struct PasswordTokenProvider {
base_url: String,
username: String,
password: String,
}
#[async_trait]
impl TokenProvider for PasswordTokenProvider {
async fn get_token(&self) -> Option<String> {
use tapis_sdk::authenticator::{models, TapisAuthenticator};
let auth = TapisAuthenticator::new(&self.base_url, None).ok()?;
let mut req = models::NewToken::new();
req.username = Some(self.username.clone());
req.password = Some(self.password.clone());
req.grant_type = Some("password".to_string());
let resp = auth.tokens.create_token(req).await.ok()?;
resp.result?.access_token?.access_token
}
}Infinite-loop protection:
RefreshMiddlewaredoes not recurse — ifget_token()itself issues HTTP requests through the same client instance, those inner calls bypass the middleware and use whatever token was set directly on that inner client.
Call .with_token_provider(provider) after .new(...). The initial jwt_token is still used for requests while it is valid; the provider is only invoked when a near-expiry condition is detected.
use std::sync::Arc;
use tapis_sdk::jobs::TapisJobs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = std::env::var("TAPIS_BASE_URL")
.unwrap_or_else(|_| "https://dev.develop.tapis.io/v3".to_string());
let token = std::env::var("TAPIS_TOKEN")?;
let provider = Arc::new(PasswordTokenProvider {
base_url: base_url.clone(),
username: std::env::var("TAPIS_USERNAME")?,
password: std::env::var("TAPIS_PASSWORD")?,
});
// Build the client with an initial token and the provider in one call.
// Token refresh happens transparently on every request when needed.
let jobs = TapisJobs::with_token_provider(&base_url, Some(token.as_str()), provider)?;
// Long-running workload — the token is refreshed automatically if it expires.
let resp = jobs
.jobs
.get_jobs(None, None, None, None, None, None, None, None, None)
.await?;
println!("Found {} jobs", resp.result.map(|r| r.len()).unwrap_or(0));
Ok(())
}Note:
with_token_provideris available on every generated service client (TapisJobs,TapisSystems,TapisApps, etc.). The provider only needs to be implemented once and can be shared across multiple clients viaArc::clone.
The snippets below demonstrate a common flow:
- Create a system
- Create an app that references that system
- Submit a job
- Monitor job status until completion
Notes:
- Payload requirements can vary by tenant policy.
- The examples intentionally show minimal payloads that match the generated Rust types.
use tapis_sdk::systems::{models, TapisSystems};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = std::env::var("TAPIS_BASE_URL")
.unwrap_or_else(|_| "https://dev.develop.tapis.io/v3".to_string());
let token = std::env::var("TAPIS_TOKEN")?;
let systems = TapisSystems::new(&base_url, Some(token.as_str()))?;
let system_id = "sdk-demo-system".to_string();
let mut req = models::ReqPostSystem::new(
system_id,
models::SystemTypeEnum::Linux,
"login.example.org".to_string(),
models::AuthnEnum::Password,
true,
);
req.description = Some("Created by tapis-rust-sdk README example".to_string());
req.root_dir = Some("/home/${apiUserId}".to_string());
req.enabled = Some(true);
let resp = systems.systems.create_system(req, Some(true)).await?;
println!("Create system URL: {:?}", resp.result.and_then(|r| r.url));
Ok(())
}use tapis_sdk::apps::{models, TapisApps};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = std::env::var("TAPIS_BASE_URL")
.unwrap_or_else(|_| "https://dev.develop.tapis.io/v3".to_string());
let token = std::env::var("TAPIS_TOKEN")?;
let apps = TapisApps::new(&base_url, Some(token.as_str()))?;
let mut req = models::ReqPostApp::new(
"sdk-demo-app".to_string(),
"1.0.0".to_string(),
"docker://alpine:3.20".to_string(),
);
req.description = Some("Created by tapis-rust-sdk README example".to_string());
req.runtime = Some(models::RuntimeEnum::Docker);
req.enabled = Some(true);
req.version_enabled = Some(true);
let resp = apps.applications.create_app_version(req).await?;
println!("Create app URL: {:?}", resp.result.and_then(|r| r.url));
Ok(())
}use std::time::Duration;
use tapis_sdk::jobs::{models, TapisJobs};
use tokio::time::sleep;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = std::env::var("TAPIS_BASE_URL")
.unwrap_or_else(|_| "https://dev.develop.tapis.io/v3".to_string());
let token = std::env::var("TAPIS_TOKEN")?;
let jobs = TapisJobs::new(&base_url, Some(token.as_str()))?;
let mut submit = models::ReqSubmitJob::new(
"sdk-demo-job".to_string(),
"sdk-demo-app".to_string(),
"1.0.0".to_string(),
);
submit.exec_system_id = Some("sdk-demo-system".to_string());
submit.archive_system_id = Some("sdk-demo-system".to_string());
submit.archive_mode = Some(models::ArchiveModeEnum::SkipOnFail);
let submit_resp = jobs.jobs.submit_job(submit).await?;
let job_uuid = submit_resp
.result
.and_then(|j| j.uuid)
.ok_or("submit_job response did not include a job UUID")?;
println!("Submitted job UUID: {job_uuid}");
loop {
let status_resp = jobs.jobs.get_job_status(&job_uuid).await?;
let status = status_resp
.result
.and_then(|s| s.status)
.unwrap_or_else(|| "UNKNOWN".to_string());
println!("Job {job_uuid} status: {status}");
if matches!(status.as_str(), "FINISHED" | "FAILED" | "CANCELLED") {
break;
}
sleep(Duration::from_secs(5)).await;
}
Ok(())
}use tapis_sdk::pods::TapisPods;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = std::env::var("TAPIS_BASE_URL")
.unwrap_or_else(|_| "https://dev.develop.tapis.io/v3".to_string());
let token = std::env::var("TAPIS_TOKEN")?;
let pods = TapisPods::new(&base_url, Some(token.as_str()))?;
let resp = pods.pods.list_pods().await?;
println!("Found {} pods", resp.result.len());
for pod in resp.result {
println!("{} => {:?}", pod.pod_id, pod.status);
}
Ok(())
}use tapis_sdk::pods::{models, TapisPods};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = std::env::var("TAPIS_BASE_URL")
.unwrap_or_else(|_| "https://dev.develop.tapis.io/v3".to_string());
let token = std::env::var("TAPIS_TOKEN")?;
let pods = TapisPods::new(&base_url, Some(token.as_str()))?;
let mut req = models::NewPod::new("sdk-demo-pod".to_string());
req.image = Some("ubuntu:22.04".to_string());
req.description = Some("Created by tapis-rust-sdk README example".to_string());
let create_resp = pods.pods.create_pod(req).await?;
println!("Created pod: {}", create_resp.result.pod_id);
let get_resp = pods.pods.get_pod("sdk-demo-pod", None, None).await?;
println!("Fetched pod status: {:?}", get_resp.result.status);
Ok(())
}Use the repository example:
tapis-pods/examples/tapis_pods_example.rs
It demonstrates creating a volume, creating a pod using that volume, deleting the pod, then deleting the volume.
use tapis_sdk::authenticator::TapisAuthenticator;
use tapis_sdk::pods::TapisPods;
use tapis_sdk::systems::TapisSystems;
use tapis_sdk::apps::TapisApps;
use tapis_sdk::jobs::TapisJobs;[dependencies]
tapis-sdk = { version = "0.3.1", default-features = false, features = ["rustls-tls"] }Available TLS features:
native-tls(default)rustls-tls
Each sub-crate contains runnable examples under examples/.
Examples to start with:
tapis-authenticator/examples/authenticator_example.rstapis-systems/examples/systems_basic_example.rstapis-apps/examples/apps_basic_example.rstapis-jobs/examples/jobs_basic_example.rstapis-pods/examples/tapis_pods_example.rs
Run one example:
cd tapis-pods
cargo run --example tapis_pods_exampleBuild all crates:
cargo build --workspace --all-targetsRun tests:
cargo test --workspacePublish service crates before publishing the parent crate:
cargo publish -p tapis-actors
cargo publish -p tapis-apps
# ...publish remaining service crates...
cargo publish -p tapis-sdkThis project is licensed under the BSD-3-Clause License. See LICENSE for details.
For support, contact cicsupport@tacc.utexas.edu.