# lmrc-cloudflare
> Part of the [LMRC Stack](https://gitlab.com/lemarco/lmrc-stack) - Infrastructure-as-Code toolkit for building production-ready Rust applications
[](https://crates.io/crates/lmrc-cloudflare)
[](https://docs.rs/lmrc-cloudflare)
[](https://gitlab.com/lemarco/lmrc-stack#license)
A comprehensive, well-documented Rust client for the Cloudflare API, designed specifically for CI/CD and automation workflows.
## Features
- **DNS Management**: Create, read, update, and delete DNS records with full control
- **Zone Management**: List and query zones (domains)
- **Cache Purging**: Clear cache by various methods (all, URLs, tags, hosts, prefixes)
- **Builder Pattern**: Ergonomic API with builder pattern for all operations
- **Custom Error Types**: Detailed error handling for programmatic responses
- **Diff Output**: See what changes will be made before applying them
- **Idempotent Operations**: Safe to run multiple times (perfect for CI/CD)
- **Async/Await**: Built on modern async Rust with tokio and reqwest
- **Well Documented**: Comprehensive docs and examples for every feature
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
lmrc-cloudflare = "0.2"
tokio = { version = "1", features = ["full"] }
```
## Quick Start
```rust
use lmrc_cloudflare::{CloudflareClient, dns::RecordType};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client
let client = CloudflareClient::builder()
.api_token("your-api-token")
.build()?;
// Get zone ID
let zone_id = client.zones().get_zone_id("example.com").await?;
// Create a DNS record
let record = client.dns()
.create_record(&zone_id)
.name("api.example.com")
.record_type(RecordType::A)
.content("192.0.2.1")
.proxied(true)
.send()
.await?;
println!("Created record: {}", record.id);
Ok(())
}
```
## Usage Examples
### DNS Management
#### List DNS Records
```rust
// List all DNS records for a zone
let records = client.dns()
.list_records(&zone_id)
.send()
.await?;
// Filter by type
let a_records = client.dns()
.list_records(&zone_id)
.record_type(RecordType::A)
.send()
.await?;
// Filter by name and type
let specific_record = client.dns()
.list_records(&zone_id)
.name("api.example.com")
.record_type(RecordType::A)
.send()
.await?;
```
#### Create DNS Records
```rust
use lmrc_cloudflare::dns::RecordType;
// Create an A record
let record = client.dns()
.create_record(&zone_id)
.name("api.example.com")
.record_type(RecordType::A)
.content("192.0.2.1")
.proxied(true)
.ttl(1)
.comment("API server")
.send()
.await?;
// Create a CNAME record
let record = client.dns()
.create_record(&zone_id)
.name("www.example.com")
.record_type(RecordType::CNAME)
.content("example.com")
.proxied(true)
.send()
.await?;
```
#### Update DNS Records
```rust
// Update record content
let updated = client.dns()
.update_record(&zone_id, &record_id)
.content("192.0.2.2")
.send()
.await?;
// Update multiple fields
let updated = client.dns()
.update_record(&zone_id, &record_id)
.content("192.0.2.3")
.proxied(false)
.ttl(3600)
.send()
.await?;
```
#### Delete DNS Records
```rust
client.dns()
.delete_record(&zone_id, &record_id)
.await?;
```
#### Find Records
```rust
// Find a record by name and type
let record = client.dns()
.find_record(&zone_id, "api.example.com", RecordType::A)
.await?;
if let Some(record) = record {
println!("Found: {} -> {}", record.name, record.content);
}
```
### Sync Records (Idempotent CI/CD Operations)
Perfect for CI/CD pipelines where you want to ensure DNS records match a desired state:
```rust
use lmrc_cloudflare::dns::{RecordType, DnsRecordBuilder};
// Define desired state
let desired_records = vec![
DnsRecordBuilder::new()
.name("api.example.com")
.record_type(RecordType::A)
.content("192.0.2.1")
.proxied(true),
DnsRecordBuilder::new()
.name("www.example.com")
.record_type(RecordType::CNAME)
.content("example.com")
.proxied(true),
];
// Dry run - see what would change without applying
let changes = client.dns()
.sync_records(&zone_id)
.records(desired_records.clone())
.dry_run(true)
.send()
.await?;
for change in &changes {
match change.action {
ChangeAction::Create => println!("Would create: {}", change.description),
ChangeAction::Update => println!("Would update: {}", change.description),
ChangeAction::NoChange => println!("Already correct: {}", change.description),
_ => {}
}
}
// Apply changes
let changes = client.dns()
.sync_records(&zone_id)
.records(desired_records)
.dry_run(false)
.send()
.await?;
### Zone Management
```rust
// List all zones
let zones = client.zones()
.list()
.send()
.await?;
for zone in zones {
println!("{}: {}", zone.name, zone.id);
}
// Get a specific zone
let zone = client.zones()
.get(&zone_id)
.await?;
// Find zone by name
let zone = client.zones()
.find_by_name("example.com")
.await?;
// Get zone ID by name (convenience method)
let zone_id = client.zones()
.get_zone_id("example.com")
.await?;
```
### Cache Purging
```rust
// Purge everything (careful!)
client.cache()
.purge_everything(&zone_id)
.await?;
// Purge specific URLs
client.cache()
.purge_urls(&zone_id)
.urls(vec![
"https://example.com/page1",
"https://example.com/page2",
])
.send()
.await?;
// Purge by cache tags (Enterprise plan)
client.cache()
.purge_tags(&zone_id)
.tags(vec!["product", "blog"])
.send()
.await?;
// Purge by hosts (Enterprise plan)
client.cache()
.purge_hosts(&zone_id)
.hosts(vec!["www.example.com", "api.example.com"])
.send()
.await?;
// Purge by prefixes (Enterprise plan)
client.cache()
.purge_prefixes(&zone_id)
.prefixes(vec!["example.com/images/", "example.com/videos/"])
.send()
.await?;
```
## Error Handling
The library provides detailed error types for better error handling:
```rust
use lmrc_cloudflare::Error;
match client.zones().get_zone_id("example.com").await {
Ok(zone_id) => println!("Zone ID: {}", zone_id),
Err(Error::NotFound(msg)) => eprintln!("Zone not found: {}", msg),
Err(Error::Unauthorized(msg)) => eprintln!("Authentication failed: {}", msg),
Err(Error::RateLimited { retry_after }) => {
eprintln!("Rate limited, retry after: {:?} seconds", retry_after);
}
Err(Error::Api(api_error)) => {
eprintln!("API error: {} (code: {:?})", api_error.message, api_error.code);
}
Err(e) => eprintln!("Error: {}", e),
}
```
## CI/CD Integration Examples
### GitHub Actions
```yaml
name: Update DNS
on:
push:
branches: [main]
jobs:
update-dns:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Update DNS records
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: cargo run --example sync-dns
```
### Example Sync Script
```rust
use lmrc_cloudflare::{CloudflareClient, dns::{RecordType, DnsRecordBuilder}};
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_token = env::var("CLOUDFLARE_API_TOKEN")?;
let client = CloudflareClient::new(api_token)?;
let zone_id = client.zones().get_zone_id("example.com").await?;
let records = vec![
DnsRecordBuilder::new()
.name("api.example.com")
.record_type(RecordType::A)
.content(env::var("API_SERVER_IP")?)
.proxied(true),
];
let changes = client.dns()
.sync_records(&zone_id)
.records(records)
.dry_run(false)
.send()
.await?;
for change in changes {
println!("{:?}: {}", change.action, change.description);
}
Ok(())
}
```
## Authentication
The client supports Cloudflare API tokens. To create an API token:
1. Go to [Cloudflare Dashboard](https://dash.cloudflare.com/profile/api-tokens)
2. Click "Create Token"
3. Select appropriate permissions (e.g., "Edit zone DNS" for DNS management)
4. Copy the token and use it with the client
```rust
let client = CloudflareClient::builder()
.api_token("your-api-token")
.build()?;
```
## Supported Record Types
- `A` - IPv4 address
- `AAAA` - IPv6 address
- `CNAME` - Canonical name
- `MX` - Mail exchange
- `TXT` - Text record
- `SRV` - Service locator
- `NS` - Name server
- `CAA` - Certificate authority authorization
- `PTR` - Pointer record
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
Part of the LMRC Stack project. Licensed under either of:
- Apache License, Version 2.0 ([LICENSE-APACHE](../../LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](../../LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.
## Acknowledgments
This client is not officially associated with Cloudflare, Inc.