# Provider Development

Implementing custom cloud provider integrations for the Provisioning platform.

## Provider Architecture

Providers abstract cloud infrastructure APIs through a unified interface, allowing infrastructure definitions to be portable across clouds.

### Provider Interface

All providers must implement these core operations:

```nushell
# Server lifecycle
create_server     # Provision new server
delete_server     # Remove server
get_server        # Fetch server details
list_servers      # List all servers
update_server     # Modify server configuration
server_status     # Get current state

# Network operations (optional)
create_network    # Create private network
delete_network    # Remove network
attach_network    # Attach server to network

# Storage operations (optional)
attach_volume     # Attach storage volume
detach_volume     # Detach storage volume
create_snapshot   # Snapshot server disk
```

## Provider Template

Use the official provider template:

```bash
# Generate provider scaffolding
provisioning generate provider --name my-cloud --template standard

# Creates:
# extensions/providers/my-cloud/
# ├── nickel/
# │   ├── schema.ncl
# │   ├── server.ncl
# │   └── network.ncl
# ├── scripts/
# │   ├── create_server.nu
# │   ├── delete_server.nu
# │   └── list_servers.nu
# └── metadata.toml
```

## Provider Schema (Nickel)

Define provider configuration schema:

```nickel
# nickel/schema.ncl
{
  ProviderConfig = {
    name | String,
    api_endpoint | String,
    api_key | String,
    region | String,
    timeout | default = 30 | Number,
    retry_attempts | default = 3 | Number,

    plans = {
      small  = {cpu = 2, memory = 4096, disk = 25},
      medium = {cpu = 4, memory = 8192, disk = 50},
      large  = {cpu = 8, memory = 16384, disk = 100},
    },

    regions | Array String,
  },

  ServerSpec = {
    name | String,
    plan | String,
    zone | String,
    image | default = "ubuntu-24.04" | String,
    ssh_keys | Array String,
    user_data | default = "" | String,
  },
}
```

## Implementing Server Creation

Create server implementation:

```nushell
# scripts/create_server.nu
#!/usr/bin/env nu

export def main [
    config: record,  # Provider configuration
    spec: record     # Server specification
]: nothing -> record {
    # Validate inputs
    validate-provider-config $config
    validate-server-spec $spec

    # Map plan to provider-specific values
    let plan = get-plan-details $config $spec.plan

    # Construct API request
    let request = {
        hostname: $spec.name
        plan: $plan.name
        zone: $spec.zone
        storage_devices: [{
            action: "create"
            storage: $plan.disk
            title: "root"
        }]
        login: {
            user: "root"
            keys: $spec.ssh_keys
        }
    }

    # Call provider API with retry logic
    let server = retry-api-call | { |
        http post $"($config.api_endpoint)/server" {
            headers: {Authorization: $"Bearer ($config.api_key)"}
            body: ($request | to json)
        } | from json
    } $config.retry_attempts

    # Wait for server to be ready
    wait-for-server-ready $config $server.uuid

    # Return server details
    {
        id: $server.uuid
        name: $server.hostname
        ip_address: $server.ip_addresses.0.address
        status: "running"
        provider: $config.name
    }
}

def validate-provider-config [config: record] {
    if ($config.api_key | is-empty) {
        error make {msg: "API key required"}
    }
    if ($config.region | is-empty) {
        error make {msg: "Region required"}
    }
}

def get-plan-details [config: record, plan_name: string]: nothing -> record {
    $config.plans | get $plan_name
}

def retry-api-call [operation: closure, max_attempts: int]: nothing -> any {
    mut attempt = 1
    mut last_error = null

    while $attempt <= $max_attempts {
        try {
            return (do $operation)
        } catch | { err |
            $last_error = $err
            if $attempt < $max_attempts {
                sleep (1sec * $attempt)  # Exponential backoff
                $attempt = $attempt + 1
            }
        }
    }

    error make {msg: $"API call failed after ($max_attempts) attempts: ($last_error)"}
}

def wait-for-server-ready [config: record, server_id: string] {
    mut ready = false
    mut attempts = 0
    let max_wait = 120  # 2 minutes

    while not $ready and $attempts < $max_wait {
        let status = http get $"($config.api_endpoint)/server/($server_id)" {
            headers: {Authorization: $"Bearer ($config.api_key)"}
        } | from json

        if $status.state == "started" {
            $ready = true
        } else {
            sleep 1sec
            $attempts = $attempts + 1
        }
    }

    if not $ready {
        error make {msg: "Server failed to start within timeout"}
    }
}
```

## Provider Testing

Comprehensive provider testing:

```nushell
# tests/test_provider.nu
use std assert

export def test_create_server [] {
    # Mock provider config
    let config = {
        name: "test-cloud"
        api_endpoint: " [http://localhost:8080"](http://localhost:8080")
        api_key: "test-key"
        region: "test-region"
        plans: {
            small: {cpu: 2, memory: 4096, disk: 25}
        }
    }

    # Mock server spec
    let spec = {
        name: "test-server"
        plan: "small"
        zone: "test-zone"
        ssh_keys: ["ssh-rsa AAAA..."]
    }

    # Test server creation
    let server = create-server $config $spec

    assert ($server.id != null)
    assert ($server.name == "test-server")
    assert ($server.status == "running")
}

export def test_list_servers [] {
    let config = load-test-config
    let servers = list-servers $config

    assert ($servers | length) > 0
}

export def main [] {
    print "Running provider tests..."
    test_create_server
    test_list_servers
    print "All tests passed!"
}
```

## Error Handling

Robust error handling for provider operations:

```nushell
# Handle API errors gracefully
def handle-api-error [error: record]: nothing -> record {
    match $error.status {
        401 => {error make {msg: "Authentication failed - check API key"}}
        403 => {error make {msg: "Permission denied - insufficient privileges"}}
        404 => {error make {msg: "Resource not found"}}
        429 => {error make {msg: "Rate limit exceeded - retry later"}}
        500 => {error make {msg: "Provider API error - contact support"}}
        _   => {error make {msg: $"Unknown error: ($error.message)"}}
    }
}
```

## Provider Best Practices

- Implement idempotent operations where possible
- Handle rate limiting with exponential backoff
- Validate all inputs before API calls
- Log all API requests and responses (without secrets)
- Use connection pooling for better performance
- Cache provider capabilities and quotas
- Implement proper timeout handling
- Return consistent error messages
- Test against provider sandbox/staging environment
- Version provider schemas carefully

## Related Documentation

- [Extension Development](extension-development.md) - Extension basics
- [API Guide](api-guide.md) - REST API patterns
- [Testing](testing.md) - Testing strategies
