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.
- π Complete API Coverage - All 500+ DigitalOcean API endpoints
- π Type Safety - Fully typed request/response models
- β‘ Async/Await - Built on tokio and reqwest
- π Auto-Generated - Always up-to-date with the latest API
- π‘οΈ Error Handling - Comprehensive error types
- π Rich Documentation - Extensive examples and guides
- π Pagination Support - Built-in pagination helpers
- π·οΈ Resource Tagging - Full support for DigitalOcean tags
| 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 secondsuse 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:
- Web Server Deployment - Full droplet lifecycle with nginx, firewall, and monitoring
- Kubernetes Cluster Setup - End-to-end cluster creation with node pools and configuration
- Multi-tier Database - PostgreSQL + Redis setup with replicas and connection pooling
- Static Website Hosting - Spaces + CDN setup for static sites
- VPC Infrastructure - Private networking setup with proper resource organization
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 integrationThis client is auto-generated from the DigitalOcean OpenAPI specification. To regenerate:
cargo build # The build.rs script handles regenerationContributions are welcome! Please see CONTRIBUTING.md for guidelines.
- API Issues: If you find issues with the generated client, please check if it's an upstream OpenAPI spec issue
- Documentation: Help improve our examples and documentation
- Features: Suggest new features or improvements
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:
- β MSRV can be raised at any time for new features, safety improvements, or maintainability
- β MSRV increases will result in a semver minor release (not patch)
- β We will always document MSRV changes in release notes and changelog
- β No advance warning period - we adopt new language features as soon as they're beneficial
- If you need to support older Rust versions, pin to a specific version range in your
Cargo.toml:[dependencies] rsdo = ">=1.0.0, <1.2.0" # Example: avoid MSRV bumps in 1.2.0+
- Check the
rust-versionfield in ourCargo.tomlfor the current MSRV - Our CI automatically tests against the declared MSRV to ensure compatibility
This 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:
- 5,000 requests per hour per API token
- 250 requests per minute per API token
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.
- Built with progenitor for type-safe API client generation
- Powered by reqwest for HTTP client functionality
- Uses tokio for async runtime
π Ready to build amazing things with DigitalOcean and Rust!
For questions, issues, or contributions, please visit our GitHub repository.