#ci-cd #cloudflare #api-client #api #client

lmrc-cloudflare

Cloudflare API client library for the LMRC Stack - comprehensive DNS, zones, and cache management with automatic retry logic

28 releases

Uses new Rust 2024

0.3.16 Dec 11, 2025
0.3.15 Dec 11, 2025
0.3.9 Nov 30, 2025
0.2.12 Nov 27, 2025

#1812 in Network programming


Used in 2 crates (via lmrc-pipeline)

MIT/Apache

140KB
2K SLoC

lmrc-cloudflare

Part of the LMRC Stack - Infrastructure-as-Code toolkit for building production-ready Rust applications

Crates.io Documentation 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:

[dependencies]
lmrc-cloudflare = "0.2"
tokio = { version = "1", features = ["full"] }

Quick Start

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

// 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

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

// 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

client.dns()
    .delete_record(&zone_id, &record_id)
    .await?;

Find Records

// 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:

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?;

println!("Applied {} changes", changes.iter().filter(|c| c.action.is_mutating()).count());

Zone Management

// 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

// 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:

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

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

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
  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
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:

at your option.

Acknowledgments

This client is not officially associated with Cloudflare, Inc.

Dependencies

~6–21MB
~215K SLoC