githubEdit

Creating a New Provider

This guide teaches you step-by-step how to implement support for a new cloud provider in spuff. We'll use Hetzner Cloud as an example, but the concepts apply to any provider.

Table of Contents


Prerequisites

Before starting, you need:

  • Experience with Rust and async programming (async/await)

  • Familiarity with the provider's API you're implementing

  • API credentials for testing

  • Read the Provider README to understand the architecture


Architecture Overview

The system uses two main traits:

You need to implement:

  1. ProviderFactory - Creates instances of your provider

  2. Provider - Implements cloud operations


Step 1: Add the ProviderType

First, add the new provider to the ProviderType enum in src/provider/config.rs:


Step 2: Create the Provider File

Create a new file for the provider:

Initial file structure:

Don't forget to declare the module in src/provider/mod.rs:


Step 3: Define API Structures

Define types for serializing/deserializing API responses. Hetzner uses JSON, so we use serde:

Tip: You don't need to map all API fields. Only use what you actually need. Unknown fields are ignored by serde by default.


Step 4: Implement the Provider

Now implement the Provider trait. I'll detail each method:

4.1 Constructor and Helpers

4.2 Implement the Provider Trait


Step 5: Implement the Factory

The Factory is simple - it knows how to create provider instances:


Step 6: Register in the Registry

Add the factory to the registry in src/provider/registry.rs:


Step 7: Write Tests

See testing-providers.md for the complete testing guide. Here's a basic example:


Final Checklist

Before submitting your PR, verify:

Code

Tests

Documentation


Common Pitfalls

1. Image Mapping

Each provider uses different conventions for identifying images:

Provider
Format
Example

DigitalOcean

Slug

ubuntu-24-04-x64

Hetzner

Slug

ubuntu-24.04

AWS

AMI ID

ami-0c55b159cbfafe1f0

Solution: Implement resolve_image() correctly for each provider.

2. Public IP Extraction

Providers return IPs in different ways:

  • Some in network array

  • Some in specific field

  • Some with separate IPv4 and IPv6

Solution: Always prefer IPv4 and handle the case when IP is not yet assigned (use 0.0.0.0).

3. Labels vs Tags

  • DigitalOcean: Tags are string array

  • Hetzner/AWS: Labels are key-value pairs

Solution: Convert HashMap<String, String> to the format the provider expects.

4. Async Actions

Some operations (especially snapshots) are asynchronous:

  1. API returns an action ID

  2. You need to poll until complete

Solution: Implement wait_for_action() with timeout.

5. Rate Limiting

Each provider has different limits. Hetzner is more restrictive than DigitalOcean.

Solution:

  • Return ProviderError::RateLimit with retry_after

  • Respect the poll_interval from timeouts

6. User Data Encoding

  • DigitalOcean: Accepts raw string

  • Hetzner: Accepts raw string

  • AWS: Requires base64

Solution: Do the necessary encoding in create_instance().


Need Help?

  • Review the DigitalOcean implementation (src/provider/digitalocean.rs) as reference

  • Open an issue to discuss before starting

  • Consult the provider ADR to understand design decisions

Last updated

Was this helpful?