| Crates.io | rsdo |
| lib.rs | rsdo |
| version | 0.1.20260101 |
| created_at | 2025-10-18 06:18:42.237159+00 |
| updated_at | 2026-01-01 10:23:12.878338+00 |
| description | A Rust client library for the DigitalOcean API v2 |
| homepage | |
| repository | https://github.com/blacksky-algorithms/rsdo |
| max_upload_size | |
| id | 1888863 |
| size | 319,587 |
Pronunciation:
/ˈrɪz.doʊ/(rizz-do)
because every crate needs a catchy name.
A comprehensive, type-safe Rust client for the DigitalOcean API, automatically generated from the official OpenAPI specification using progenitor.
| Service | Description | Documentation |
|---|---|---|
| Droplets | Virtual machines and compute resources | 📖 Guide |
| Kubernetes | Managed Kubernetes clusters (DOKS) | 📖 Guide |
| Databases | Managed databases (PostgreSQL, MySQL, Redis/Valkey, MongoDB) | 📖 Guide |
| VPC | Virtual Private Cloud networking | 📖 Guide |
| Spaces | S3-compatible object storage with CDN | 📖 Guide |
| Load Balancers | Application and network load balancing | API Reference |
| Firewalls | Cloud firewall rules and policies | API Reference |
| Volumes | Block storage volumes | API Reference |
| Snapshots | Backup and restore functionality | API Reference |
| Images | Custom images and distributions | API Reference |
| SSH Keys | SSH key management | API Reference |
| Domains | DNS management | API Reference |
| Certificates | SSL/TLS certificate management | API Reference |
| Monitoring | Metrics and alerting | API Reference |
| Projects | Resource organization | API Reference |
Add this to your Cargo.toml:
[dependencies]
rsdo = "0.1.0"
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
Get your API token from the DigitalOcean Control Panel:
use rsdo::{Client, Error};
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create HTTP client with authentication
let mut headers = HeaderMap::new();
headers.insert(
AUTHORIZATION,
HeaderValue::from_str("Bearer YOUR_DO_API_TOKEN")?,
);
let http_client = reqwest::Client::builder()
.default_headers(headers)
.build()?;
// Create DigitalOcean client
let client = Client::new_with_client("https://api.digitalocean.com", http_client);
// List your droplets
let response = client.droplets_list(None, None, None, None).await?;
let droplets = response.into_inner().droplets;
println!("Found {} droplets:", droplets.len());
for droplet in droplets {
println!(" {} - {} ({})",
droplet.name,
droplet.status,
droplet.region.slug
);
}
Ok(())
}
For convenience, set your API token as an environment variable:
export DIGITALOCEAN_TOKEN="your-api-token-here"
Then use it in your code:
let token = std::env::var("DIGITALOCEAN_TOKEN")?;
headers.insert(
AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", token))?,
);
use rsdo::types::*;
let droplet_spec = DropletsCreateBody {
name: "my-server".to_string(),
region: "nyc3".to_string(),
size: "s-2vcpu-2gb".to_string(),
image: "ubuntu-22-04-x64".to_string(),
ssh_keys: Some(vec!["your-ssh-key-id".to_string()]),
backups: Some(true),
ipv6: Some(true),
monitoring: Some(true),
tags: Some(vec!["web".to_string(), "production".to_string()]),
user_data: Some("#!/bin/bash\napt-get update\napt-get install -y nginx".to_string()),
};
let response = client.droplets_create(&droplet_spec).await?;
let droplet = response.into_inner().droplet;
println!("Created droplet: {} ({})", droplet.name, droplet.id);
let cluster_spec = KubernetesCreateClusterBody {
name: "my-k8s-cluster".to_string(),
region: "nyc3".to_string(),
version: "1.28.2-do.0".to_string(),
auto_upgrade: Some(true),
node_pools: vec![
KubernetesNodePool {
name: "worker-pool".to_string(),
size: "s-2vcpu-2gb".to_string(),
count: 3,
auto_scale: Some(true),
min_nodes: Some(1),
max_nodes: Some(5),
// ... other fields
}
],
// ... other fields
};
let response = client.kubernetes_create_cluster(&cluster_spec).await?;
let cluster = response.into_inner().kubernetes_cluster;
println!("Created cluster: {} ({})", cluster.name, cluster.id);
let db_spec = DatabasesCreateClusterBody {
name: "my-postgres-db".to_string(),
engine: "pg".to_string(),
version: "15".to_string(),
region: "nyc3".to_string(),
size: "db-s-2vcpu-2gb".to_string(),
num_nodes: 2, // High availability
db_name: Some("myapp".to_string()),
db_user: Some("myapp_user".to_string()),
tags: Some(vec!["production".to_string(), "postgres".to_string()]),
// ... other fields
};
let response = client.databases_create_cluster(&db_spec).await?;
let database = response.into_inner().database;
println!("Created database: {} ({})", database.name, database.id);
use aws_sdk_s3::{Client as S3Client, Config, Credentials, Region};
// Setup S3 client for Spaces
let credentials = Credentials::new("access_key", "secret_key", None, None, "spaces");
let config = aws_sdk_s3::config::Builder::new()
.credentials_provider(credentials)
.region(Region::new("nyc3"))
.endpoint_url("https://nyc3.digitaloceanspaces.com")
.build();
let s3_client = S3Client::from_conf(config);
// Upload file
s3_client
.put_object()
.bucket("my-space")
.key("uploads/file.jpg")
.body(aws_sdk_s3::primitives::ByteStream::from_path("local-file.jpg").await?)
.acl("public-read".into())
.send()
.await?;
println!("File uploaded to Spaces!");
The client provides comprehensive error handling:
use rsdo::Error;
match client.droplets_get("invalid-id").await {
Ok(response) => {
let droplet = response.into_inner().droplet;
println!("Droplet: {}", droplet.name);
}
Err(Error::ErrorResponse(resp)) => {
match resp.status().as_u16() {
404 => println!("Droplet not found"),
401 => println!("Authentication failed - check your API token"),
429 => println!("Rate limit exceeded - slow down requests"),
_ => println!("API error: {}", resp.status()),
}
}
Err(Error::InvalidRequest(msg)) => {
println!("Invalid request: {}", msg);
}
Err(Error::CommunicationError(err)) => {
println!("Network error: {}", err);
}
Err(err) => {
println!("Unexpected error: {:?}", err);
}
}
Many API endpoints support pagination:
let mut page = 1;
let per_page = 50;
loop {
let response = client.droplets_list(
Some(per_page),
Some(page),
None, // tag filter
None, // name filter
).await?;
let droplets_response = response.into_inner();
let droplets = droplets_response.droplets;
println!("Page {} - {} droplets", page, droplets.len());
for droplet in droplets {
println!(" {}", droplet.name);
}
// Check if there are more pages
if let Some(links) = droplets_response.links {
if links.pages.as_ref().and_then(|p| p.next.as_ref()).is_some() {
page += 1;
} else {
break;
}
} else {
break;
}
}
You can configure the client using environment variables:
# Required
export DIGITALOCEAN_TOKEN="your-api-token"
# Optional
export DIGITALOCEAN_API_URL="https://api.digitalocean.com" # Custom API endpoint
export DIGITALOCEAN_TIMEOUT="30" # Request timeout in seconds
use std::time::Duration;
let http_client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.user_agent("MyApp/1.0")
.default_headers(headers)
.build()?;
let client = Client::new_with_client("https://api.digitalocean.com", http_client);
Check out the comprehensive guides for complete, production-ready examples:
git clone https://github.com/your-username/rsdo.git
cd rsdo
cargo build
# Unit tests
cargo test
# Integration tests (requires API token)
DIGITALOCEAN_TOKEN="your-token" cargo test --features integration
This client is auto-generated from the DigitalOcean OpenAPI specification. To regenerate:
cargo build # The build.rs script handles regeneration
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Current MSRV: 1.70.0
This crate follows an aggressive MSRV policy to take advantage of the latest Rust language features, performance improvements, and safety enhancements:
Cargo.toml:
[dependencies]
rsdo = ">=1.0.0, <1.2.0" # Example: avoid MSRV bumps in 1.2.0+
rust-version field in our Cargo.toml for the current MSRVThis policy allows us to provide the safest, most performant, and maintainable DigitalOcean client possible by leveraging the latest Rust ecosystem improvements.
Full API documentation is available at docs.rs/rsdo.
For DigitalOcean API documentation, see: https://docs.digitalocean.com/reference/api/
DigitalOcean API has rate limits:
The client doesn't automatically handle rate limiting, but you can implement retry logic:
use tokio::time::{sleep, Duration};
async fn with_retry<T, F, Fut>(mut f: F) -> Result<T, Error<T>>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = Result<T, Error<T>>>,
{
let mut attempts = 0;
let max_attempts = 3;
loop {
match f().await {
Ok(result) => return Ok(result),
Err(Error::ErrorResponse(resp)) if resp.status().as_u16() == 429 => {
attempts += 1;
if attempts >= max_attempts {
return Err(Error::ErrorResponse(resp));
}
// Exponential backoff
let delay = Duration::from_secs(2_u64.pow(attempts));
sleep(delay).await;
}
Err(e) => return Err(e),
}
}
}
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
🚀 Ready to build amazing things with DigitalOcean and Rust!
For questions, issues, or contributions, please visit our GitHub repository.