# Extension Development

Creating custom extensions to add providers, task services, and clusters to the Provisioning platform.

## Extension Overview

Extensions are modular components that extend platform capabilities:

| Extension Type | Purpose | Implementation | Complexity |
| -------------- | ------- | -------------- | ---------- |
| **Providers** | Cloud infrastructure backends | Nushell scripts + Nickel schemas | Moderate |
| **Task Services** | Infrastructure components | Nushell installation scripts | Simple |
| **Clusters** | Complete deployments | Nickel schemas + orchestration | Moderate |
| **Workflows** | Automation templates | Nickel workflow definitions | Simple |

## Extension Structure

Standard extension directory layout:

```text
provisioning/extensions/<type>/<name>/
├── nickel/
│   ├── schema.ncl      # Nickel type definitions
│   ├── defaults.ncl    # Default configuration
│   └── validation.ncl  # Validation rules
├── scripts/
│   ├── install.nu      # Installation script
│   ├── uninstall.nu    # Removal script
│   └── validate.nu     # Validation script
├── templates/
│   └── config.template # Configuration templates
├── tests/
│   └── test_*.nu       # Test scripts
├── docs/
│   └── README.md       # Documentation
└── metadata.toml       # Extension metadata
```

## Extension Metadata

Every extension requires `metadata.toml`:

```toml
# metadata.toml
[extension]
name = "my-provider"
type = "provider"
version = "1.0.0"
description = "Custom cloud provider"
author = "Your Name <[email@example.com](mailto:email@example.com)>"
license = "MIT"

[dependencies]
nushell = ">=0.109.0"
nickel = ">=1.15.1"

[dependencies.extensions]
# Other extensions this depends on
base-provider = "1.0.0"

[capabilities]
create_server = true
delete_server = true
create_network = true

[configuration]
required_fields = ["api_key", "region"]
optional_fields = ["timeout", "retry_attempts"]
```

## Creating a Provider Extension

Providers implement cloud infrastructure backends.

### Provider Structure

```text
provisioning/extensions/providers/my-provider/
├── nickel/
│   ├── schema.ncl
│   ├── server.ncl
│   └── network.ncl
├── scripts/
│   ├── create_server.nu
│   ├── delete_server.nu
│   ├── list_servers.nu
│   └── validate.nu
├── templates/
│   └── server.template
├── tests/
│   └── test_provider.nu
└── metadata.toml
```

### Provider Schema (Nickel)

```nickel
# nickel/schema.ncl
{
  Provider = {
    name | String,
    api_key | String,
    region | String,
    timeout | default = 30 | Number,

    server_config = {
      default_plan | default = "medium" | String,
      allowed_plans | Array String,
    },
  },

  Server = {
    name | String,
    plan | String,
    zone | String,
    hostname | String,
    tags | default = [] | Array String,
  },
}
```

### Provider Implementation (Nushell)

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

# Create server using provider API
export def main [
    config: record  # Provider configuration
    server: record  # Server specification
] {
    # Validate configuration
    validate-config $config

    # Construct API request
    let request = {
        name: $server.name
        plan: $server.plan
        zone: $server.zone
    }

    # Call provider API
    let response = http post $"($config.api_endpoint)/servers" {
        headers: {
            Authorization: $"Bearer ($config.api_key)"
        }
        body: ($request | to json)
    }

    # Return server details
    $response | from json
}

# Validate provider configuration
def validate-config [config: record] {
    if ($config.api_key | is-empty) {
        error make {msg: "api_key is required"}
    }

    if ($config.region | is-empty) {
        error make {msg: "region is required"}
    }
}
```

### Provider Interface Contract

All providers must implement:

```nushell
# Required operations
create_server    # Create new server
delete_server    # Delete existing server
get_server       # Get server details
list_servers     # List all servers
server_status    # Check server status

# Optional operations
create_network   # Create network
delete_network   # Delete network
attach_storage   # Attach storage volume
create_snapshot  # Create server snapshot
```

## Creating a Task Service Extension

Task services are installable infrastructure components.

### Task Service Structure

```text
provisioning/extensions/taskservs/my-service/
├── nickel/
│   ├── schema.ncl
│   └── defaults.ncl
├── scripts/
│   ├── install.nu
│   ├── uninstall.nu
│   ├── health.nu
│   └── validate.nu
├── templates/
│   ├── config.yaml.template
│   └── systemd.service.template
├── tests/
│   └── test_service.nu
├── docs/
│   └── README.md
└── metadata.toml
```

### Task Service Metadata

```toml
# metadata.toml
[extension]
name = "my-service"
type = "taskserv"
version = "2.1.0"
description = "Custom infrastructure service"

[dependencies.taskservs]
# Task services this depends on
containerd = ">=1.7.0"
kubernetes = ">=1.28.0"

[installation]
requires_root = true
platforms = ["linux"]
architectures = ["x86_64", "aarch64"]

[health_check]
enabled = true
endpoint = " [http://localhost:8000/health"](http://localhost:8000/health")
interval = 30
timeout = 5
```

### Task Service Installation Script

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

export def main [
    config: record  # Service configuration
    server: record  # Target server details
] {
    print "Installing my-service..."

    # Download binaries
    let version = $config.version? | default "latest"
    download-binary $version

    # Install systemd service
    install-systemd-service $config

    # Configure service
    generate-config $config

    # Start service
    start-service

    # Verify installation
    verify-installation

    print "Installation complete"
}

def download-binary [version: string] {
    let url = $" [https://github.com/org/my-service/releases/download/($versio](https://github.com/org/my-service/releases/download/($versio)n)/my-service"
    http get $url | save /usr/local/bin/my-service
    chmod +x /usr/local/bin/my-service
}

def install-systemd-service [config: record] {
    let template = open ../templates/systemd.service.template
    let rendered = $template | str replace --all "{{VERSION}}" $config.version
    $rendered | save /etc/systemd/system/my-service.service
    systemctl daemon-reload
}

def start-service [] {
    systemctl enable my-service
    systemctl start my-service
}

def verify-installation [] {
    let status = systemctl is-active my-service
    if $status != "active" {
        error make {msg: "Service failed to start"}
    }

    # Health check
    sleep 5sec
    let health = http get  [http://localhost:8000/health](http://localhost:8000/health)
    if $health.status != "healthy" {
        error make {msg: "Health check failed"}
    }
}
```

## Creating a Cluster Extension

Clusters combine servers and task services into complete deployments.

### Cluster Schema

```nickel
# nickel/schema.ncl
{
  Cluster = {
    metadata = {
      name | String,
      provider | String,
      environment | default = "production" | String,
    },

    infrastructure = {
      servers | Array {
        name | String,
        role | | [ "control", "worker", "storage" | ],
        plan | String,
      },
    },

    services = {
      taskservs | Array String,
      order | default = [] | Array String,
    },

    networking = {
      private_network | default = true | Bool,
      cidr | default = "10.0.0.0/16" | String,
    },
  },
}
```

### Cluster Definition Example

```nickel
# clusters/kubernetes-ha.ncl
{
  metadata.name = "k8s-ha-cluster",
  metadata.provider = "upcloud",

  infrastructure.servers = [
    {name = "control-01", role = "control", plan = "large"},
    {name = "control-02", role = "control", plan = "large"},
    {name = "control-03", role = "control", plan = "large"},
    {name = "worker-01", role = "worker", plan = "xlarge"},
    {name = "worker-02", role = "worker", plan = "xlarge"},
  ],

  services.taskservs = ["containerd", "etcd", "kubernetes", "cilium"],
  services.order = ["containerd", "etcd", "kubernetes", "cilium"],

  networking.private_network = true,
  networking.cidr = "10.100.0.0/16",
}
```

## Extension Testing

### Test Structure

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

# Test provider configuration validation
export def test_validate_config [] {
    let valid_config = {
        api_key: "test-key"
        region: "us-east-1"
    }

    let result = validate-config $valid_config
    assert equal $result.valid true
}

# Test server creation
export def test_create_server [] {
    let config = load-test-config
    let server_spec = {
        name: "test-server"
        plan: "medium"
        zone: "us-east-1a"
    }

    let result = create-server $config $server_spec
    assert equal $result.status "created"
}

# Run all tests
export def main [] {
    test_validate_config
    test_create_server
    print "All tests passed"
}
```

Run tests:

```bash
# Test extension
provisioning extension test my-provider

# Test specific component
nu tests/test_provider.nu
```

## Extension Packaging

### OCI Registry Publishing

Package and publish extension:

```bash
# Build extension package
provisioning extension build my-provider

# Validate package
provisioning extension validate my-provider-1.0.0.tar.gz

# Publish to registry
provisioning extension publish my-provider-1.0.0.tar.gz \
  --registry registry.example.com
```

Package structure:

```text
my-provider-1.0.0.tar.gz
├── metadata.toml
├── nickel/
├── scripts/
├── templates/
├── tests/
├── docs/
└── manifest.json
```

## Extension Installation

Install extension from registry:

```bash
# Install from OCI registry
provisioning extension install my-provider --version 1.0.0

# Install from local file
provisioning extension install ./my-provider-1.0.0.tar.gz

# List installed extensions
provisioning extension list

# Update extension
provisioning extension update my-provider --version 1.1.0

# Uninstall extension
provisioning extension uninstall my-provider
```

## Best Practices

- Follow naming conventions: lowercase with hyphens
- Version extensions semantically (semver)
- Document all configuration options
- Provide comprehensive tests
- Include usage examples in docs
- Validate input parameters
- Handle errors gracefully
- Log important operations
- Support idempotent operations
- Keep dependencies minimal

## Related Documentation

- [Provider Development](provider-development.md) - Provider specifics
- [Nickel Guide](../infrastructure/nickel-guide.md) - Nickel language
- [Build System](build-system.md) - Building extensions
- [Testing](testing.md) - Testing strategies
