2025-12-11 21:50:42 +00:00
<!DOCTYPE HTML>
< html lang = "en" class = "ayu sidebar-visible" dir = "ltr" >
< head >
<!-- Book generated using mdBook -->
< meta charset = "UTF-8" >
< title > Extensions - Provisioning Platform Documentation< / title >
<!-- Custom HTML head -->
< meta name = "description" content = "Complete documentation for the Provisioning Platform - Infrastructure automation with Nushell, KCL, and Rust" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "theme-color" content = "#ffffff" >
< link rel = "icon" href = "../favicon.svg" >
< link rel = "shortcut icon" href = "../favicon.png" >
< link rel = "stylesheet" href = "../css/variables.css" >
< link rel = "stylesheet" href = "../css/general.css" >
< link rel = "stylesheet" href = "../css/chrome.css" >
< link rel = "stylesheet" href = "../css/print.css" media = "print" >
<!-- Fonts -->
< link rel = "stylesheet" href = "../FontAwesome/css/font-awesome.css" >
< link rel = "stylesheet" href = "../fonts/fonts.css" >
<!-- Highlight.js Stylesheets -->
< link rel = "stylesheet" id = "highlight-css" href = "../highlight.css" >
< link rel = "stylesheet" id = "tomorrow-night-css" href = "../tomorrow-night.css" >
< link rel = "stylesheet" id = "ayu-highlight-css" href = "../ayu-highlight.css" >
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
< script >
const path_to_root = "../";
const default_light_theme = "ayu";
const default_dark_theme = "navy";
< / script >
<!-- Start loading toc.js asap -->
< script src = "../toc.js" > < / script >
< / head >
< body >
< div id = "mdbook-help-container" >
< div id = "mdbook-help-popup" >
< h2 class = "mdbook-help-title" > Keyboard shortcuts< / h2 >
< div >
< p > Press < kbd > ←< / kbd > or < kbd > →< / kbd > to navigate between chapters< / p >
< p > Press < kbd > S< / kbd > or < kbd > /< / kbd > to search in the book< / p >
< p > Press < kbd > ?< / kbd > to show this help< / p >
< p > Press < kbd > Esc< / kbd > to hide this help< / p >
< / div >
< / div >
< / div >
< div id = "body-container" >
<!-- Work around some values being stored in localStorage wrapped in quotes -->
< script >
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') & & theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') & & sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
< / script >
<!-- Set the theme before any content is loaded, prevents flash -->
< script >
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('ayu')
html.classList.add(theme);
html.classList.add("js");
< / script >
< input type = "checkbox" id = "sidebar-toggle-anchor" class = "hidden" >
<!-- Hide / unhide sidebar before it is displayed -->
< script >
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
< / script >
< nav id = "sidebar" class = "sidebar" aria-label = "Table of contents" >
<!-- populated by js -->
< mdbook-sidebar-scrollbox class = "sidebar-scrollbox" > < / mdbook-sidebar-scrollbox >
< noscript >
< iframe class = "sidebar-iframe-outer" src = "../toc.html" > < / iframe >
< / noscript >
< div id = "sidebar-resize-handle" class = "sidebar-resize-handle" >
< div class = "sidebar-resize-indicator" > < / div >
< / div >
< / nav >
< div id = "page-wrapper" class = "page-wrapper" >
< div class = "page" >
< div id = "menu-bar-hover-placeholder" > < / div >
< div id = "menu-bar" class = "menu-bar sticky" >
< div class = "left-buttons" >
< label id = "sidebar-toggle" class = "icon-button" for = "sidebar-toggle-anchor" title = "Toggle Table of Contents" aria-label = "Toggle Table of Contents" aria-controls = "sidebar" >
< i class = "fa fa-bars" > < / i >
< / label >
< button id = "theme-toggle" class = "icon-button" type = "button" title = "Change theme" aria-label = "Change theme" aria-haspopup = "true" aria-expanded = "false" aria-controls = "theme-list" >
< i class = "fa fa-paint-brush" > < / i >
< / button >
< ul id = "theme-list" class = "theme-popup" aria-label = "Themes" role = "menu" >
< li role = "none" > < button role = "menuitem" class = "theme" id = "default_theme" > Auto< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "light" > Light< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "rust" > Rust< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "coal" > Coal< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "navy" > Navy< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "ayu" > Ayu< / button > < / li >
< / ul >
< button id = "search-toggle" class = "icon-button" type = "button" title = "Search (`/`)" aria-label = "Toggle Searchbar" aria-expanded = "false" aria-keyshortcuts = "/ s" aria-controls = "searchbar" >
< i class = "fa fa-search" > < / i >
< / button >
< / div >
< h1 class = "menu-title" > Provisioning Platform Documentation< / h1 >
< div class = "right-buttons" >
< a href = "../print.html" title = "Print this book" aria-label = "Print this book" >
< i id = "print-button" class = "fa fa-print" > < / i >
< / a >
< a href = "https://github.com/provisioning/provisioning-platform" title = "Git repository" aria-label = "Git repository" >
< i id = "git-repository-button" class = "fa fa-github" > < / i >
< / a >
< a href = "https://github.com/provisioning/provisioning-platform/edit/main/provisioning/docs/src/development/extensions.md" title = "Suggest an edit" aria-label = "Suggest an edit" >
< i id = "git-edit-button" class = "fa fa-edit" > < / i >
< / a >
< / div >
< / div >
< div id = "search-wrapper" class = "hidden" >
< form id = "searchbar-outer" class = "searchbar-outer" >
< input type = "search" id = "searchbar" name = "searchbar" placeholder = "Search this book ..." aria-controls = "searchresults-outer" aria-describedby = "searchresults-header" >
< / form >
< div id = "searchresults-outer" class = "searchresults-outer hidden" >
< div id = "searchresults-header" class = "searchresults-header" > < / div >
< ul id = "searchresults" >
< / ul >
< / div >
< / div >
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
< script >
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
< / script >
< div id = "content" class = "content" >
< main >
< h1 id = "extension-development-guide" > < a class = "header" href = "#extension-development-guide" > Extension Development Guide< / a > < / h1 >
< p > This document provides comprehensive guidance on creating providers, task services, and clusters for provisioning, including templates, testing frameworks, publishing, and best practices.< / p >
< h2 id = "table-of-contents" > < a class = "header" href = "#table-of-contents" > Table of Contents< / a > < / h2 >
< ol >
< li > < a href = "#overview" > Overview< / a > < / li >
< li > < a href = "#extension-types" > Extension Types< / a > < / li >
< li > < a href = "#provider-development" > Provider Development< / a > < / li >
< li > < a href = "#task-service-development" > Task Service Development< / a > < / li >
< li > < a href = "#cluster-development" > Cluster Development< / a > < / li >
< li > < a href = "#testing-and-validation" > Testing and Validation< / a > < / li >
< li > < a href = "#publishing-and-distribution" > Publishing and Distribution< / a > < / li >
< li > < a href = "#best-practices" > Best Practices< / a > < / li >
< li > < a href = "#troubleshooting" > Troubleshooting< / a > < / li >
< / ol >
< h2 id = "overview" > < a class = "header" href = "#overview" > Overview< / a > < / h2 >
< p > Provisioning supports three types of extensions that enable customization and expansion of functionality:< / p >
< ul >
< li > < strong > Providers< / strong > : Cloud provider implementations for resource management< / li >
< li > < strong > Task Services< / strong > : Infrastructure service components (databases, monitoring, etc.)< / li >
< li > < strong > Clusters< / strong > : Complete deployment solutions combining multiple services< / li >
< / ul >
< p > < strong > Key Features< / strong > :< / p >
< ul >
< li > < strong > Template-Based Development< / strong > : Comprehensive templates for all extension types< / li >
< li > < strong > Workspace Integration< / strong > : Extensions developed in isolated workspace environments< / li >
< li > < strong > Configuration-Driven< / strong > : KCL schemas for type-safe configuration< / li >
< li > < strong > Version Management< / strong > : GitHub integration for version tracking< / li >
< li > < strong > Testing Framework< / strong > : Comprehensive testing and validation tools< / li >
< li > < strong > Hot Reloading< / strong > : Development-time hot reloading support< / li >
< / ul >
< p > < strong > Location< / strong > : < code > workspace/extensions/< / code > < / p >
< h2 id = "extension-types" > < a class = "header" href = "#extension-types" > Extension Types< / a > < / h2 >
< h3 id = "extension-architecture" > < a class = "header" href = "#extension-architecture" > Extension Architecture< / a > < / h3 >
2026-01-08 09:55:37 +00:00
< pre > < code class = "language-plaintext" > Extension Ecosystem
2025-12-11 21:50:42 +00:00
├── Providers # Cloud resource management
│ ├── AWS # Amazon Web Services
│ ├── UpCloud # UpCloud platform
│ ├── Local # Local development
│ └── Custom # User-defined providers
├── Task Services # Infrastructure components
│ ├── Kubernetes # Container orchestration
│ ├── Database Services # PostgreSQL, MongoDB, etc.
│ ├── Monitoring # Prometheus, Grafana, etc.
│ ├── Networking # Cilium, CoreDNS, etc.
│ └── Custom Services # User-defined services
└── Clusters # Complete solutions
├── Web Stack # Web application deployment
├── CI/CD Pipeline # Continuous integration/deployment
├── Data Platform # Data processing and analytics
└── Custom Clusters # User-defined clusters
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "extension-discovery" > < a class = "header" href = "#extension-discovery" > Extension Discovery< / a > < / h3 >
< p > < strong > Discovery Order< / strong > :< / p >
< ol >
< li > < code > workspace/extensions/{type}/{user}/{name}< / code > - User-specific extensions< / li >
< li > < code > workspace/extensions/{type}/{name}< / code > - Workspace shared extensions< / li >
< li > < code > workspace/extensions/{type}/template< / code > - Templates< / li >
< li > Core system paths (fallback)< / li >
< / ol >
< p > < strong > Path Resolution< / strong > :< / p >
< pre > < code class = "language-nushell" > # Automatic extension discovery
2025-12-11 21:50:42 +00:00
use workspace/lib/path-resolver.nu
# Find provider extension
let provider_path = (path-resolver resolve_extension "providers" "my-aws-provider")
# List all available task services
let taskservs = (path-resolver list_extensions "taskservs" --include-core)
# Resolve cluster definition
let cluster_path = (path-resolver resolve_extension "clusters" "web-stack")
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h2 id = "provider-development" > < a class = "header" href = "#provider-development" > Provider Development< / a > < / h2 >
< h3 id = "provider-architecture" > < a class = "header" href = "#provider-architecture" > Provider Architecture< / a > < / h3 >
< p > Providers implement cloud resource management through a standardized interface that supports multiple cloud platforms while maintaining consistent APIs.< / p >
< p > < strong > Core Responsibilities< / strong > :< / p >
< ul >
< li > < strong > Authentication< / strong > : Secure API authentication and credential management< / li >
< li > < strong > Resource Management< / strong > : Server creation, deletion, and lifecycle management< / li >
< li > < strong > Configuration< / strong > : Provider-specific settings and validation< / li >
< li > < strong > Error Handling< / strong > : Comprehensive error handling and recovery< / li >
< li > < strong > Rate Limiting< / strong > : API rate limiting and retry logic< / li >
< / ul >
< h3 id = "creating-a-new-provider" > < a class = "header" href = "#creating-a-new-provider" > Creating a New Provider< / a > < / h3 >
< p > < strong > 1. Initialize from Template< / strong > :< / p >
< pre > < code class = "language-bash" > # Copy provider template
2025-12-11 21:50:42 +00:00
cp -r workspace/extensions/providers/template workspace/extensions/providers/my-cloud
# Navigate to new provider
cd workspace/extensions/providers/my-cloud
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< p > < strong > 2. Update Configuration< / strong > :< / p >
< pre > < code class = "language-bash" > # Initialize provider metadata
2025-12-11 21:50:42 +00:00
nu init-provider.nu \
--name "my-cloud" \
--display-name "MyCloud Provider" \
--author "$USER" \
--description "MyCloud platform integration"
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "provider-structure" > < a class = "header" href = "#provider-structure" > Provider Structure< / a > < / h3 >
< pre > < code class = "language-plaintext" > my-cloud/
2025-12-11 21:50:42 +00:00
├── README.md # Provider documentation
2026-01-12 04:42:18 +00:00
├── schemas/ # Nickel configuration schemas
│ ├── settings.ncl # Provider settings schema
│ ├── servers.ncl # Server configuration schema
│ ├── networks.ncl # Network configuration schema
│ └── manifest.toml # Nickel module dependencies
2025-12-11 21:50:42 +00:00
├── nulib/ # Nushell implementation
│ ├── provider.nu # Main provider interface
│ ├── servers/ # Server management
│ │ ├── create.nu # Server creation logic
│ │ ├── delete.nu # Server deletion logic
│ │ ├── list.nu # Server listing
│ │ ├── status.nu # Server status checking
│ │ └── utils.nu # Server utilities
│ ├── auth/ # Authentication
│ │ ├── client.nu # API client setup
│ │ ├── tokens.nu # Token management
│ │ └── validation.nu # Credential validation
│ └── utils/ # Provider utilities
│ ├── api.nu # API interaction helpers
│ ├── config.nu # Configuration helpers
│ └── validation.nu # Input validation
├── templates/ # Jinja2 templates
│ ├── server-config.j2 # Server configuration
│ ├── cloud-init.j2 # Cloud initialization
│ └── network-config.j2 # Network configuration
├── generate/ # Code generation
│ ├── server-configs.nu # Generate server configurations
│ └── infrastructure.nu # Generate infrastructure
└── tests/ # Testing framework
├── unit/ # Unit tests
│ ├── test-auth.nu # Authentication tests
│ ├── test-servers.nu # Server management tests
│ └── test-validation.nu # Validation tests
├── integration/ # Integration tests
│ ├── test-lifecycle.nu # Complete lifecycle tests
│ └── test-api.nu # API integration tests
└── mock/ # Mock data and services
├── api-responses.json # Mock API responses
└── test-configs.toml # Test configurations
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "provider-implementation" > < a class = "header" href = "#provider-implementation" > Provider Implementation< / a > < / h3 >
< p > < strong > Main Provider Interface< / strong > (< code > nulib/provider.nu< / code > ):< / p >
< pre > < code class = "language-nushell" > #!/usr/bin/env nu
2025-12-11 21:50:42 +00:00
# MyCloud Provider Implementation
# Provider metadata
export const PROVIDER_NAME = "my-cloud"
export const PROVIDER_VERSION = "1.0.0"
export const API_VERSION = "v1"
# Main provider initialization
export def "provider init" [
--config-path: string = "" # Path to provider configuration
--validate: bool = true # Validate configuration on init
] -> record {
let config = if $config_path == "" {
load_provider_config
} else {
open $config_path | from toml
}
if $validate {
validate_provider_config $config
}
# Initialize API client
let client = (setup_api_client $config)
# Return provider instance
{
name: $PROVIDER_NAME,
version: $PROVIDER_VERSION,
config: $config,
client: $client,
initialized: true
}
}
# Server management interface
export def "provider create-server" [
name: string # Server name
plan: string # Server plan/size
--zone: string = "auto" # Deployment zone
--template: string = "ubuntu22" # OS template
--dry-run: bool = false # Show what would be created
] -> record {
let provider = (provider init)
# Validate inputs
if ($name | str length) == 0 {
error make {msg: "Server name cannot be empty"}
}
if not (is_valid_plan $plan) {
error make {msg: $"Invalid server plan: ($plan)"}
}
# Build server configuration
let server_config = {
name: $name,
plan: $plan,
zone: (resolve_zone $zone),
template: $template,
provider: $PROVIDER_NAME
}
if $dry_run {
return {action: "create", config: $server_config, status: "dry-run"}
}
# Create server via API
let result = try {
create_server_api $server_config $provider.client
} catch { |e|
error make {
msg: $"Server creation failed: ($e.msg)",
help: "Check provider credentials and quota limits"
}
}
{
server: $name,
status: "created",
id: $result.id,
ip_address: $result.ip_address,
created_at: (date now)
}
}
export def "provider delete-server" [
name: string # Server name or ID
--force: bool = false # Force deletion without confirmation
] -> record {
let provider = (provider init)
# Find server
let server = try {
find_server $name $provider.client
} catch {
error make {msg: $"Server not found: ($name)"}
}
if not $force {
let confirm = (input $"Delete server '($name)' (y/N)? ")
if $confirm != "y" and $confirm != "yes" {
return {action: "delete", server: $name, status: "cancelled"}
}
}
# Delete server
let result = try {
delete_server_api $server.id $provider.client
} catch { |e|
error make {msg: $"Server deletion failed: ($e.msg)"}
}
{
server: $name,
status: "deleted",
deleted_at: (date now)
}
}
export def "provider list-servers" [
--zone: string = "" # Filter by zone
--status: string = "" # Filter by status
--format: string = "table" # Output format: table, json, yaml
] -> list< record> {
let provider = (provider init)
let servers = try {
list_servers_api $provider.client
} catch { |e|
error make {msg: $"Failed to list servers: ($e.msg)"}
}
# Apply filters
let filtered = $servers
| if $zone != "" { filter {|s| $s.zone == $zone} } else { $in }
| if $status != "" { filter {|s| $s.status == $status} } else { $in }
match $format {
"json" => ($filtered | to json),
"yaml" => ($filtered | to yaml),
_ => $filtered
}
}
# Provider testing interface
export def "provider test" [
--test-type: string = "basic" # Test type: basic, full, integration
] -> record {
match $test_type {
"basic" => test_basic_functionality,
"full" => test_full_functionality,
"integration" => test_integration,
_ => (error make {msg: $"Unknown test type: ($test_type)"})
}
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< p > < strong > Authentication Module< / strong > (< code > nulib/auth/client.nu< / code > ):< / p >
< pre > < code class = "language-nushell" > # API client setup and authentication
2025-12-11 21:50:42 +00:00
export def setup_api_client [config: record] -> record {
# Validate credentials
if not ("api_key" in $config) {
error make {msg: "API key not found in configuration"}
}
if not ("api_secret" in $config) {
error make {msg: "API secret not found in configuration"}
}
# Setup HTTP client with authentication
let client = {
base_url: ($config.api_url? | default "https://api.my-cloud.com"),
api_key: $config.api_key,
api_secret: $config.api_secret,
timeout: ($config.timeout? | default 30),
retries: ($config.retries? | default 3)
}
# Test authentication
try {
test_auth_api $client
} catch { |e|
error make {
msg: $"Authentication failed: ($e.msg)",
help: "Check your API credentials and network connectivity"
}
}
$client
}
def test_auth_api [client: record] -> bool {
let response = http get $"($client.base_url)/auth/test" --headers {
"Authorization": $"Bearer ($client.api_key)",
"Content-Type": "application/json"
}
$response.status == "success"
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< p > < strong > Nickel Configuration Schema< / strong > (< code > schemas/settings.ncl< / code > ):< / p >
< pre > < code class = "language-nickel" > # MyCloud Provider Configuration Schema
2025-12-11 21:50:42 +00:00
2026-01-12 04:42:18 +00:00
let MyCloudConfig = {
# MyCloud provider configuration
api_url | string | default = "https://api.my-cloud.com",
api_key | string,
api_secret | string,
timeout | number | default = 30,
retries | number | default = 3,
2025-12-11 21:50:42 +00:00
# Rate limiting
2026-01-12 04:42:18 +00:00
rate_limit | {
requests_per_minute | number | default = 60,
burst_size | number | default = 10,
} | default = {},
2025-12-11 21:50:42 +00:00
# Default settings
2026-01-12 04:42:18 +00:00
defaults | {
zone | string | default = "us-east-1",
template | string | default = "ubuntu-22.04",
network | string | default = "default",
} | default = {},
} in
MyCloudConfig
let MyCloudServerConfig = {
# MyCloud server configuration
name | string,
plan | string,
zone | string | optional,
template | string | default = "ubuntu-22.04",
storage | number | default = 25,
tags | { } | default = {},
2025-12-11 21:50:42 +00:00
# Network configuration
2026-01-12 04:42:18 +00:00
network | {
vpc_id | string | optional,
subnet_id | string | optional,
public_ip | bool | default = true,
firewall_rules | array | default = [],
} | optional,
} in
MyCloudServerConfig
let FirewallRule = {
# Firewall rule configuration
port | (number | string),
protocol | string | default = "tcp",
source | string | default = "0.0.0.0/0",
description | string | optional,
} in
FirewallRule
< / code > < / pre >
< h3 id = "provider-testing" > < a class = "header" href = "#provider-testing" > Provider Testing< / a > < / h3 >
< p > < strong > Unit Testing< / strong > (< code > tests/unit/test-servers.nu< / code > ):< / p >
< pre > < code class = "language-nushell" > # Unit tests for server management
2025-12-11 21:50:42 +00:00
use ../../../nulib/provider.nu
def test_server_creation [] {
# Test valid server creation
let result = (provider create-server "test-server" "small" --dry-run)
assert ($result.action == "create")
assert ($result.config.name == "test-server")
assert ($result.config.plan == "small")
assert ($result.status == "dry-run")
print "✅ Server creation test passed"
}
def test_invalid_server_name [] {
# Test invalid server name
try {
provider create-server "" "small" --dry-run
assert false "Should have failed with empty name"
} catch { |e|
assert ($e.msg | str contains "Server name cannot be empty")
}
print "✅ Invalid server name test passed"
}
def test_invalid_plan [] {
# Test invalid server plan
try {
provider create-server "test" "invalid-plan" --dry-run
assert false "Should have failed with invalid plan"
} catch { |e|
assert ($e.msg | str contains "Invalid server plan")
}
print "✅ Invalid plan test passed"
}
def main [] {
print "Running server management unit tests..."
test_server_creation
test_invalid_server_name
test_invalid_plan
print "✅ All server management tests passed"
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< p > < strong > Integration Testing< / strong > (< code > tests/integration/test-lifecycle.nu< / code > ):< / p >
< pre > < code class = "language-nushell" > # Integration tests for complete server lifecycle
2025-12-11 21:50:42 +00:00
use ../../../nulib/provider.nu
def test_complete_lifecycle [] {
let test_server = $"test-server-(date now | format date '%Y%m%d%H%M%S')"
try {
# Test server creation (dry run)
let create_result = (provider create-server $test_server "small" --dry-run)
assert ($create_result.status == "dry-run")
# Test server listing
let servers = (provider list-servers --format json)
assert ($servers | length) > = 0
# Test provider info
let provider_info = (provider init)
assert ($provider_info.name == "my-cloud")
assert $provider_info.initialized
print $"✅ Complete lifecycle test passed for ($test_server)"
} catch { |e|
print $"❌ Integration test failed: ($e.msg)"
exit 1
}
}
def main [] {
print "Running provider integration tests..."
test_complete_lifecycle
print "✅ All integration tests passed"
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h2 id = "task-service-development" > < a class = "header" href = "#task-service-development" > Task Service Development< / a > < / h2 >
< h3 id = "task-service-architecture" > < a class = "header" href = "#task-service-architecture" > Task Service Architecture< / a > < / h3 >
< p > Task services are infrastructure components that can be deployed and managed across different environments. They provide standardized interfaces for installation, configuration, and lifecycle management.< / p >
< p > < strong > Core Responsibilities< / strong > :< / p >
< ul >
< li > < strong > Installation< / strong > : Service deployment and setup< / li >
< li > < strong > Configuration< / strong > : Dynamic configuration management< / li >
< li > < strong > Health Checking< / strong > : Service status monitoring< / li >
< li > < strong > Version Management< / strong > : Automatic version updates from GitHub< / li >
< li > < strong > Integration< / strong > : Integration with other services and clusters< / li >
< / ul >
< h3 id = "creating-a-new-task-service" > < a class = "header" href = "#creating-a-new-task-service" > Creating a New Task Service< / a > < / h3 >
< p > < strong > 1. Initialize from Template< / strong > :< / p >
< pre > < code class = "language-bash" > # Copy task service template
2025-12-11 21:50:42 +00:00
cp -r workspace/extensions/taskservs/template workspace/extensions/taskservs/my-service
# Navigate to new service
cd workspace/extensions/taskservs/my-service
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< p > < strong > 2. Initialize Service< / strong > :< / p >
< pre > < code class = "language-bash" > # Initialize service metadata
2025-12-11 21:50:42 +00:00
nu init-service.nu \
--name "my-service" \
--display-name "My Custom Service" \
--type "database" \
--github-repo "myorg/my-service"
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "task-service-structure" > < a class = "header" href = "#task-service-structure" > Task Service Structure< / a > < / h3 >
< pre > < code class = "language-plaintext" > my-service/
2025-12-11 21:50:42 +00:00
├── README.md # Service documentation
2026-01-12 04:42:18 +00:00
├── schemas/ # Nickel schemas
│ ├── version.ncl # Version and GitHub integration
│ ├── config.ncl # Service configuration schema
│ └── manifest.toml # Module dependencies
2025-12-11 21:50:42 +00:00
├── nushell/ # Nushell implementation
│ ├── taskserv.nu # Main service interface
│ ├── install.nu # Installation logic
│ ├── uninstall.nu # Removal logic
│ ├── config.nu # Configuration management
│ ├── status.nu # Status and health checking
│ ├── versions.nu # Version management
│ └── utils.nu # Service utilities
├── templates/ # Jinja2 templates
│ ├── deployment.yaml.j2 # Kubernetes deployment
│ ├── service.yaml.j2 # Kubernetes service
│ ├── configmap.yaml.j2 # Configuration
│ ├── install.sh.j2 # Installation script
│ └── systemd.service.j2 # Systemd service
├── manifests/ # Static manifests
│ ├── rbac.yaml # RBAC definitions
│ ├── pvc.yaml # Persistent volume claims
│ └── ingress.yaml # Ingress configuration
├── generate/ # Code generation
│ ├── manifests.nu # Generate Kubernetes manifests
│ ├── configs.nu # Generate configurations
│ └── docs.nu # Generate documentation
└── tests/ # Testing framework
├── unit/ # Unit tests
├── integration/ # Integration tests
└── fixtures/ # Test fixtures and data
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "task-service-implementation" > < a class = "header" href = "#task-service-implementation" > Task Service Implementation< / a > < / h3 >
< p > < strong > Main Service Interface< / strong > (< code > nushell/taskserv.nu< / code > ):< / p >
< pre > < code class = "language-nushell" > #!/usr/bin/env nu
2025-12-11 21:50:42 +00:00
# My Custom Service Task Service Implementation
export const SERVICE_NAME = "my-service"
export const SERVICE_TYPE = "database"
export const SERVICE_VERSION = "1.0.0"
# Service installation
export def "taskserv install" [
target: string # Target server or cluster
--config: string = "" # Custom configuration file
--dry-run: bool = false # Show what would be installed
--wait: bool = true # Wait for installation to complete
] -> record {
# Load service configuration
let service_config = if $config != "" {
open $config | from toml
} else {
load_default_config
}
# Validate target environment
let target_info = validate_target $target
if not $target_info.valid {
error make {msg: $"Invalid target: ($target_info.reason)"}
}
if $dry_run {
let install_plan = generate_install_plan $target $service_config
return {
action: "install",
service: $SERVICE_NAME,
target: $target,
plan: $install_plan,
status: "dry-run"
}
}
# Perform installation
print $"Installing ($SERVICE_NAME) on ($target)..."
let install_result = try {
install_service $target $service_config $wait
} catch { |e|
error make {
msg: $"Installation failed: ($e.msg)",
help: "Check target connectivity and permissions"
}
}
{
service: $SERVICE_NAME,
target: $target,
status: "installed",
version: $install_result.version,
endpoint: $install_result.endpoint?,
installed_at: (date now)
}
}
# Service removal
export def "taskserv uninstall" [
target: string # Target server or cluster
--force: bool = false # Force removal without confirmation
--cleanup-data: bool = false # Remove persistent data
] -> record {
let target_info = validate_target $target
if not $target_info.valid {
error make {msg: $"Invalid target: ($target_info.reason)"}
}
# Check if service is installed
let status = get_service_status $target
if $status.status != "installed" {
error make {msg: $"Service ($SERVICE_NAME) is not installed on ($target)"}
}
if not $force {
let confirm = (input $"Remove ($SERVICE_NAME) from ($target)? (y/N) ")
if $confirm != "y" and $confirm != "yes" {
return {action: "uninstall", service: $SERVICE_NAME, status: "cancelled"}
}
}
print $"Removing ($SERVICE_NAME) from ($target)..."
let removal_result = try {
uninstall_service $target $cleanup_data
} catch { |e|
error make {msg: $"Removal failed: ($e.msg)"}
}
{
service: $SERVICE_NAME,
target: $target,
status: "uninstalled",
data_removed: $cleanup_data,
uninstalled_at: (date now)
}
}
# Service status checking
export def "taskserv status" [
target: string # Target server or cluster
--detailed: bool = false # Show detailed status information
] -> record {
let target_info = validate_target $target
if not $target_info.valid {
error make {msg: $"Invalid target: ($target_info.reason)"}
}
let status = get_service_status $target
if $detailed {
let health = check_service_health $target
let metrics = get_service_metrics $target
$status | merge {
health: $health,
metrics: $metrics,
checked_at: (date now)
}
} else {
$status
}
}
# Version management
export def "taskserv check-updates" [
--target: string = "" # Check updates for specific target
] -> record {
let current_version = get_current_version
let latest_version = get_latest_version_from_github
let update_available = $latest_version != $current_version
{
service: $SERVICE_NAME,
current_version: $current_version,
latest_version: $latest_version,
update_available: $update_available,
target: $target,
checked_at: (date now)
}
}
export def "taskserv update" [
target: string # Target to update
--version: string = "latest" # Specific version to update to
--dry-run: bool = false # Show what would be updated
] -> record {
let current_status = (taskserv status $target)
if $current_status.status != "installed" {
error make {msg: $"Service not installed on ($target)"}
}
let target_version = if $version == "latest" {
get_latest_version_from_github
} else {
$version
}
if $dry_run {
return {
action: "update",
service: $SERVICE_NAME,
target: $target,
from_version: $current_status.version,
to_version: $target_version,
status: "dry-run"
}
}
print $"Updating ($SERVICE_NAME) on ($target) to version ($target_version)..."
let update_result = try {
update_service $target $target_version
} catch { |e|
error make {msg: $"Update failed: ($e.msg)"}
}
{
service: $SERVICE_NAME,
target: $target,
status: "updated",
from_version: $current_status.version,
to_version: $target_version,
updated_at: (date now)
}
}
# Service testing
export def "taskserv test" [
target: string = "local" # Target for testing
--test-type: string = "basic" # Test type: basic, integration, full
] -> record {
match $test_type {
"basic" => test_basic_functionality $target,
"integration" => test_integration $target,
"full" => test_full_functionality $target,
_ => (error make {msg: $"Unknown test type: ($test_type)"})
}
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< p > < strong > Version Configuration< / strong > (< code > schemas/version.ncl< / code > ):< / p >
< pre > < code class = "language-nickel" > # Version management with GitHub integration
2025-12-11 21:50:42 +00:00
2026-01-12 04:42:18 +00:00
let version_config = {
service_name = "my-service",
2025-12-11 21:50:42 +00:00
# GitHub repository for version checking
github = {
2026-01-12 04:42:18 +00:00
owner = "myorg",
repo = "my-service",
2025-12-11 21:50:42 +00:00
# Release configuration
release = {
2026-01-12 04:42:18 +00:00
tag_prefix = "v",
prerelease = false,
draft = false,
},
2025-12-11 21:50:42 +00:00
# Asset patterns for different platforms
assets = {
2026-01-12 04:42:18 +00:00
linux_amd64 = "my-service-{version}-linux-amd64.tar.gz",
darwin_amd64 = "my-service-{version}-darwin-amd64.tar.gz",
windows_amd64 = "my-service-{version}-windows-amd64.zip",
},
},
2025-12-11 21:50:42 +00:00
# Version constraints and compatibility
compatibility = {
2026-01-12 04:42:18 +00:00
min_kubernetes_version = "1.20.0",
max_kubernetes_version = "1.28.*",
2025-12-11 21:50:42 +00:00
# Dependencies
requires = {
2026-01-12 04:42:18 +00:00
"cert-manager" = "> =1.8.0",
"ingress-nginx" = "> =1.0.0",
},
2025-12-11 21:50:42 +00:00
# Conflicts
conflicts = {
2026-01-12 04:42:18 +00:00
"old-my-service" = "*",
},
},
2025-12-11 21:50:42 +00:00
# Installation configuration
installation = {
2026-01-12 04:42:18 +00:00
default_namespace = "my-service",
create_namespace = true,
2025-12-11 21:50:42 +00:00
# Resource requirements
resources = {
requests = {
2026-01-12 04:42:18 +00:00
cpu = "100m",
memory = "128Mi",
},
2025-12-11 21:50:42 +00:00
limits = {
2026-01-12 04:42:18 +00:00
cpu = "500m",
memory = "512Mi",
},
},
2025-12-11 21:50:42 +00:00
# Persistence
persistence = {
2026-01-12 04:42:18 +00:00
enabled = true,
storage_class = "default",
size = "10Gi",
},
},
2025-12-11 21:50:42 +00:00
# Health check configuration
health_check = {
2026-01-12 04:42:18 +00:00
initial_delay_seconds = 30,
period_seconds = 10,
timeout_seconds = 5,
failure_threshold = 3,
2025-12-11 21:50:42 +00:00
# Health endpoints
endpoints = {
2026-01-12 04:42:18 +00:00
liveness = "/health/live",
readiness = "/health/ready",
},
},
} in
version_config
< / code > < / pre >
< h2 id = "cluster-development" > < a class = "header" href = "#cluster-development" > Cluster Development< / a > < / h2 >
< h3 id = "cluster-architecture" > < a class = "header" href = "#cluster-architecture" > Cluster Architecture< / a > < / h3 >
< p > Clusters represent complete deployment solutions that combine multiple task services, providers, and configurations to create functional environments.< / p >
< p > < strong > Core Responsibilities< / strong > :< / p >
< ul >
< li > < strong > Service Orchestration< / strong > : Coordinate multiple task service deployments< / li >
< li > < strong > Dependency Management< / strong > : Handle service dependencies and startup order< / li >
< li > < strong > Configuration Management< / strong > : Manage cross-service configuration< / li >
< li > < strong > Health Monitoring< / strong > : Monitor overall cluster health< / li >
< li > < strong > Scaling< / strong > : Handle cluster scaling operations< / li >
< / ul >
< h3 id = "creating-a-new-cluster" > < a class = "header" href = "#creating-a-new-cluster" > Creating a New Cluster< / a > < / h3 >
< p > < strong > 1. Initialize from Template< / strong > :< / p >
< pre > < code class = "language-bash" > # Copy cluster template
2025-12-11 21:50:42 +00:00
cp -r workspace/extensions/clusters/template workspace/extensions/clusters/my-stack
# Navigate to new cluster
cd workspace/extensions/clusters/my-stack
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< p > < strong > 2. Initialize Cluster< / strong > :< / p >
< pre > < code class = "language-bash" > # Initialize cluster metadata
2025-12-11 21:50:42 +00:00
nu init-cluster.nu \
--name "my-stack" \
--display-name "My Application Stack" \
--type "web-application"
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "cluster-implementation" > < a class = "header" href = "#cluster-implementation" > Cluster Implementation< / a > < / h3 >
< p > < strong > Main Cluster Interface< / strong > (< code > nushell/cluster.nu< / code > ):< / p >
< pre > < code class = "language-nushell" > #!/usr/bin/env nu
2025-12-11 21:50:42 +00:00
# My Application Stack Cluster Implementation
export const CLUSTER_NAME = "my-stack"
export const CLUSTER_TYPE = "web-application"
export const CLUSTER_VERSION = "1.0.0"
# Cluster creation
export def "cluster create" [
target: string # Target infrastructure
--config: string = "" # Custom configuration file
--dry-run: bool = false # Show what would be created
--wait: bool = true # Wait for cluster to be ready
] -> record {
let cluster_config = if $config != "" {
open $config | from toml
} else {
load_default_cluster_config
}
if $dry_run {
let deployment_plan = generate_deployment_plan $target $cluster_config
return {
action: "create",
cluster: $CLUSTER_NAME,
target: $target,
plan: $deployment_plan,
status: "dry-run"
}
}
print $"Creating cluster ($CLUSTER_NAME) on ($target)..."
# Deploy services in dependency order
let services = get_service_deployment_order $cluster_config.services
let deployment_results = []
for service in $services {
print $"Deploying service: ($service.name)"
let result = try {
deploy_service $service $target $wait
} catch { |e|
# Rollback on failure
rollback_cluster $target $deployment_results
error make {msg: $"Service deployment failed: ($e.msg)"}
}
$deployment_results = ($deployment_results | append $result)
}
# Configure inter-service communication
configure_service_mesh $target $deployment_results
{
cluster: $CLUSTER_NAME,
target: $target,
status: "created",
services: $deployment_results,
created_at: (date now)
}
}
# Cluster deletion
export def "cluster delete" [
target: string # Target infrastructure
--force: bool = false # Force deletion without confirmation
--cleanup-data: bool = false # Remove persistent data
] -> record {
let cluster_status = get_cluster_status $target
if $cluster_status.status != "running" {
error make {msg: $"Cluster ($CLUSTER_NAME) is not running on ($target)"}
}
if not $force {
let confirm = (input $"Delete cluster ($CLUSTER_NAME) from ($target)? (y/N) ")
if $confirm != "y" and $confirm != "yes" {
return {action: "delete", cluster: $CLUSTER_NAME, status: "cancelled"}
}
}
print $"Deleting cluster ($CLUSTER_NAME) from ($target)..."
# Delete services in reverse dependency order
let services = get_service_deletion_order $cluster_status.services
let deletion_results = []
for service in $services {
print $"Removing service: ($service.name)"
let result = try {
remove_service $service $target $cleanup_data
} catch { |e|
print $"Warning: Failed to remove service ($service.name): ($e.msg)"
}
$deletion_results = ($deletion_results | append $result)
}
{
cluster: $CLUSTER_NAME,
target: $target,
status: "deleted",
services_removed: $deletion_results,
data_removed: $cleanup_data,
deleted_at: (date now)
}
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h2 id = "testing-and-validation" > < a class = "header" href = "#testing-and-validation" > Testing and Validation< / a > < / h2 >
< h3 id = "testing-framework" > < a class = "header" href = "#testing-framework" > Testing Framework< / a > < / h3 >
< p > < strong > Test Types< / strong > :< / p >
< ul >
< li > < strong > Unit Tests< / strong > : Individual function and module testing< / li >
< li > < strong > Integration Tests< / strong > : Cross-component interaction testing< / li >
< li > < strong > End-to-End Tests< / strong > : Complete workflow testing< / li >
< li > < strong > Performance Tests< / strong > : Load and performance validation< / li >
< li > < strong > Security Tests< / strong > : Security and vulnerability testing< / li >
< / ul >
< h3 id = "extension-testing-commands" > < a class = "header" href = "#extension-testing-commands" > Extension Testing Commands< / a > < / h3 >
< p > < strong > Workspace Testing Tools< / strong > :< / p >
< pre > < code class = "language-bash" > # Validate extension syntax and structure
2025-12-11 21:50:42 +00:00
nu workspace.nu tools validate-extension providers/my-cloud
# Run extension unit tests
nu workspace.nu tools test-extension taskservs/my-service --test-type unit
# Integration testing with real infrastructure
nu workspace.nu tools test-extension clusters/my-stack --test-type integration --target test-env
# Performance testing
nu workspace.nu tools test-extension providers/my-cloud --test-type performance --duration 5m
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "automated-testing" > < a class = "header" href = "#automated-testing" > Automated Testing< / a > < / h3 >
< p > < strong > Test Runner< / strong > (< code > tests/run-tests.nu< / code > ):< / p >
< pre > < code class = "language-nushell" > #!/usr/bin/env nu
2025-12-11 21:50:42 +00:00
# Automated test runner for extensions
def main [
extension_type: string # Extension type: providers, taskservs, clusters
extension_name: string # Extension name
--test-types: string = "all" # Test types to run: unit, integration, e2e, all
--target: string = "local" # Test target environment
--verbose: bool = false # Verbose test output
--parallel: bool = true # Run tests in parallel
] -> record {
let extension_path = $"workspace/extensions/($extension_type)/($extension_name)"
if not ($extension_path | path exists) {
error make {msg: $"Extension not found: ($extension_path)"}
}
let test_types = if $test_types == "all" {
["unit", "integration", "e2e"]
} else {
$test_types | split row ","
}
print $"Running tests for ($extension_type)/($extension_name)..."
let test_results = []
for test_type in $test_types {
print $"Running ($test_type) tests..."
let result = try {
run_test_suite $extension_path $test_type $target $verbose
} catch { |e|
{
test_type: $test_type,
status: "failed",
error: $e.msg,
duration: 0
}
}
$test_results = ($test_results | append $result)
}
let total_tests = ($test_results | length)
let passed_tests = ($test_results | where status == "passed" | length)
let failed_tests = ($test_results | where status == "failed" | length)
{
extension: $"($extension_type)/($extension_name)",
test_results: $test_results,
summary: {
total: $total_tests,
passed: $passed_tests,
failed: $failed_tests,
success_rate: ($passed_tests / $total_tests * 100)
},
completed_at: (date now)
}
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h2 id = "publishing-and-distribution" > < a class = "header" href = "#publishing-and-distribution" > Publishing and Distribution< / a > < / h2 >
< h3 id = "extension-publishing" > < a class = "header" href = "#extension-publishing" > Extension Publishing< / a > < / h3 >
< p > < strong > Publishing Process< / strong > :< / p >
< ol >
< li > < strong > Validation< / strong > : Comprehensive testing and validation< / li >
< li > < strong > Documentation< / strong > : Complete documentation and examples< / li >
< li > < strong > Packaging< / strong > : Create distribution packages< / li >
< li > < strong > Registry< / strong > : Publish to extension registry< / li >
< li > < strong > Versioning< / strong > : Semantic version tagging< / li >
< / ol >
< h3 id = "publishing-commands" > < a class = "header" href = "#publishing-commands" > Publishing Commands< / a > < / h3 >
< pre > < code class = "language-bash" > # Validate extension for publishing
2025-12-11 21:50:42 +00:00
nu workspace.nu tools validate-for-publish providers/my-cloud
# Create distribution package
nu workspace.nu tools package-extension providers/my-cloud --version 1.0.0
# Publish to registry
nu workspace.nu tools publish-extension providers/my-cloud --registry official
# Tag version
nu workspace.nu tools tag-extension providers/my-cloud --version 1.0.0 --push
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "extension-registry" > < a class = "header" href = "#extension-registry" > Extension Registry< / a > < / h3 >
< p > < strong > Registry Structure< / strong > :< / p >
< pre > < code class = "language-plaintext" > Extension Registry
2025-12-11 21:50:42 +00:00
├── providers/
│ ├── aws/ # Official AWS provider
│ ├── upcloud/ # Official UpCloud provider
│ └── community/ # Community providers
├── taskservs/
│ ├── kubernetes/ # Official Kubernetes service
│ ├── databases/ # Database services
│ └── monitoring/ # Monitoring services
└── clusters/
├── web-stacks/ # Web application stacks
├── data-platforms/ # Data processing platforms
└── ci-cd/ # CI/CD pipelines
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h2 id = "best-practices" > < a class = "header" href = "#best-practices" > Best Practices< / a > < / h2 >
< h3 id = "code-quality" > < a class = "header" href = "#code-quality" > Code Quality< / a > < / h3 >
< p > < strong > Function Design< / strong > :< / p >
< pre > < code class = "language-nushell" > # Good: Single responsibility, clear parameters, comprehensive error handling
2025-12-11 21:50:42 +00:00
export def "provider create-server" [
name: string # Server name (must be unique in region)
plan: string # Server plan (see list-plans for options)
--zone: string = "auto" # Deployment zone (auto-selects optimal zone)
--dry-run: bool = false # Preview changes without creating resources
] -> record { # Returns creation result with server details
# Validate inputs first
if ($name | str length) == 0 {
error make {
msg: "Server name cannot be empty"
help: "Provide a unique name for the server"
}
}
# Implementation with comprehensive error handling
# ...
}
# Bad: Unclear parameters, no error handling
def create [n, p] {
# Missing validation and error handling
api_call $n $p
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< p > < strong > Configuration Management< / strong > :< / p >
< pre > < code class = "language-nushell" > # Good: Configuration-driven with validation
2025-12-11 21:50:42 +00:00
def get_api_endpoint [provider: string] -> string {
let config = get-config-value $"providers.($provider).api_url"
if ($config | is-empty) {
error make {
msg: $"API URL not configured for provider ($provider)",
help: $"Add 'api_url' to providers.($provider) configuration"
}
}
$config
}
# Bad: Hardcoded values
def get_api_endpoint [] {
"https://api.provider.com" # Never hardcode!
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "error-handling" > < a class = "header" href = "#error-handling" > Error Handling< / a > < / h3 >
< p > < strong > Comprehensive Error Context< / strong > :< / p >
< pre > < code class = "language-nushell" > def create_server_with_context [name: string, config: record] -> record {
2025-12-11 21:50:42 +00:00
try {
# Validate configuration
validate_server_config $config
} catch { |e|
error make {
msg: $"Invalid server configuration: ($e.msg)",
label: {text: "configuration error", span: $e.span?},
help: "Check configuration syntax and required fields"
}
}
try {
# Create server via API
let result = api_create_server $name $config
return $result
} catch { |e|
match $e.msg {
$msg if ($msg | str contains "quota") => {
error make {
msg: $"Server creation failed: quota limit exceeded",
help: "Contact support to increase quota or delete unused servers"
}
},
$msg if ($msg | str contains "auth") => {
error make {
msg: "Server creation failed: authentication error",
help: "Check API credentials and permissions"
}
},
_ => {
error make {
msg: $"Server creation failed: ($e.msg)",
help: "Check network connectivity and try again"
}
}
}
}
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "testing-practices" > < a class = "header" href = "#testing-practices" > Testing Practices< / a > < / h3 >
< p > < strong > Test Organization< / strong > :< / p >
< pre > < code class = "language-nushell" > # Organize tests by functionality
2025-12-11 21:50:42 +00:00
# tests/unit/server-creation-test.nu
def test_valid_server_creation [] {
# Test valid cases with various inputs
let valid_configs = [
{name: "test-1", plan: "small"},
{name: "test-2", plan: "medium"},
{name: "test-3", plan: "large"}
]
for config in $valid_configs {
let result = create_server $config.name $config.plan --dry-run
assert ($result.status == "dry-run")
assert ($result.config.name == $config.name)
}
}
def test_invalid_inputs [] {
# Test error conditions
let invalid_cases = [
{name: "", plan: "small", error: "empty name"},
{name: "test", plan: "invalid", error: "invalid plan"},
{name: "test with spaces", plan: "small", error: "invalid characters"}
]
for case in $invalid_cases {
try {
create_server $case.name $case.plan --dry-run
assert false $"Should have failed: ($case.error)"
} catch { |e|
# Verify specific error message
assert ($e.msg | str contains $case.error)
}
}
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "documentation-standards" > < a class = "header" href = "#documentation-standards" > Documentation Standards< / a > < / h3 >
< p > < strong > Function Documentation< / strong > :< / p >
< pre > < code class = "language-nushell" > # Comprehensive function documentation
2025-12-11 21:50:42 +00:00
def "provider create-server" [
name: string # Server name - must be unique within the provider
plan: string # Server size plan (run 'provider list-plans' for options)
--zone: string = "auto" # Target zone - 'auto' selects optimal zone based on load
--template: string = "ubuntu22" # OS template - see 'provider list-templates' for options
--storage: int = 25 # Storage size in GB (minimum 10, maximum 2048)
--dry-run: bool = false # Preview mode - shows what would be created without creating
] -> record { # Returns server creation details including ID and IP
"""
Creates a new server instance with the specified configuration.
This function provisions a new server using the provider's API, configures
basic security settings, and returns the server details upon successful creation.
Examples:
# Create a small server with default settings
provider create-server "web-01" "small"
# Create with specific zone and storage
provider create-server "db-01" "large" --zone "us-west-2" --storage 100
# Preview what would be created
provider create-server "test" "medium" --dry-run
Error conditions:
- Invalid server name (empty, invalid characters)
- Invalid plan (not in supported plans list)
- Insufficient quota or permissions
- Network connectivity issues
Returns:
Record with keys: server, status, id, ip_address, created_at
"""
# Implementation...
}
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h2 id = "troubleshooting" > < a class = "header" href = "#troubleshooting" > Troubleshooting< / a > < / h2 >
< h3 id = "common-development-issues" > < a class = "header" href = "#common-development-issues" > Common Development Issues< / a > < / h3 >
< h4 id = "extension-not-found" > < a class = "header" href = "#extension-not-found" > Extension Not Found< / a > < / h4 >
< p > < strong > Error< / strong > : < code > Extension 'my-provider' not found< / code > < / p >
< pre > < code class = "language-bash" > # Solution: Check extension location and structure
2025-12-11 21:50:42 +00:00
ls -la workspace/extensions/providers/my-provider
nu workspace/lib/path-resolver.nu resolve_extension "providers" "my-provider"
# Validate extension structure
nu workspace.nu tools validate-extension providers/my-provider
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h4 id = "configuration-errors" > < a class = "header" href = "#configuration-errors" > Configuration Errors< / a > < / h4 >
< p > < strong > Error< / strong > : < code > Invalid Nickel configuration< / code > < / p >
< pre > < code class = "language-bash" > # Solution: Validate Nickel syntax
nickel check workspace/extensions/providers/my-provider/schemas/
2025-12-11 21:50:42 +00:00
2026-01-12 04:42:18 +00:00
# Format Nickel files
nickel fmt workspace/extensions/providers/my-provider/schemas/
2025-12-11 21:50:42 +00:00
# Test with example data
2026-01-12 04:42:18 +00:00
nickel eval workspace/extensions/providers/my-provider/schemas/settings.ncl
< / code > < / pre >
< h4 id = "api-integration-issues" > < a class = "header" href = "#api-integration-issues" > API Integration Issues< / a > < / h4 >
< p > < strong > Error< / strong > : < code > Authentication failed< / code > < / p >
< pre > < code class = "language-bash" > # Solution: Test credentials and connectivity
2025-12-11 21:50:42 +00:00
curl -H "Authorization: Bearer $API_KEY" https://api.provider.com/auth/test
# Debug API calls
export PROVISIONING_DEBUG=true
export PROVISIONING_LOG_LEVEL=debug
nu workspace/extensions/providers/my-provider/nulib/provider.nu test --test-type basic
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "debug-mode" > < a class = "header" href = "#debug-mode" > Debug Mode< / a > < / h3 >
< p > < strong > Enable Extension Debugging< / strong > :< / p >
< pre > < code class = "language-bash" > # Set debug environment
2025-12-11 21:50:42 +00:00
export PROVISIONING_DEBUG=true
export PROVISIONING_LOG_LEVEL=debug
export PROVISIONING_WORKSPACE_USER=$USER
# Run extension with debug
nu workspace/extensions/providers/my-provider/nulib/provider.nu create-server test-server small --dry-run
2026-01-12 04:42:18 +00:00
< / code > < / pre >
< h3 id = "performance-optimization" > < a class = "header" href = "#performance-optimization" > Performance Optimization< / a > < / h3 >
< p > < strong > Extension Performance< / strong > :< / p >
< pre > < code class = "language-bash" > # Profile extension performance
2025-12-11 21:50:42 +00:00
time nu workspace/extensions/providers/my-provider/nulib/provider.nu list-servers
# Monitor resource usage
nu workspace/tools/runtime-manager.nu monitor --duration 1m --interval 5s
# Optimize API calls (use caching)
export PROVISIONING_CACHE_ENABLED=true
export PROVISIONING_CACHE_TTL=300 # 5 minutes
< / code > < / pre >
2026-01-12 04:42:18 +00:00
< p > This extension development guide provides a comprehensive framework for creating high-quality, maintainable extensions that integrate seamlessly with provisioning’ s architecture and workflows.< / p >
2025-12-11 21:50:42 +00:00
< / main >
< nav class = "nav-wrapper" aria-label = "Page navigation" >
<!-- Mobile navigation buttons -->
2026-01-08 09:55:37 +00:00
< a rel = "prev" href = "../development/build-system.html" class = "mobile-nav-chapters previous" title = "Previous chapter" aria-label = "Previous chapter" aria-keyshortcuts = "Left" >
2025-12-11 21:50:42 +00:00
< i class = "fa fa-angle-left" > < / i >
< / a >
2026-01-08 09:55:37 +00:00
< a rel = "next prefetch" href = "../development/distribution-process.html" class = "mobile-nav-chapters next" title = "Next chapter" aria-label = "Next chapter" aria-keyshortcuts = "Right" >
2025-12-11 21:50:42 +00:00
< i class = "fa fa-angle-right" > < / i >
< / a >
< div style = "clear: both" > < / div >
< / nav >
< / div >
< / div >
< nav class = "nav-wide-wrapper" aria-label = "Page navigation" >
2026-01-08 09:55:37 +00:00
< a rel = "prev" href = "../development/build-system.html" class = "nav-chapters previous" title = "Previous chapter" aria-label = "Previous chapter" aria-keyshortcuts = "Left" >
2025-12-11 21:50:42 +00:00
< i class = "fa fa-angle-left" > < / i >
< / a >
2026-01-08 09:55:37 +00:00
< a rel = "next prefetch" href = "../development/distribution-process.html" class = "nav-chapters next" title = "Next chapter" aria-label = "Next chapter" aria-keyshortcuts = "Right" >
2025-12-11 21:50:42 +00:00
< i class = "fa fa-angle-right" > < / i >
< / a >
< / nav >
< / div >
< script >
window.playground_copyable = true;
< / script >
< script src = "../elasticlunr.min.js" > < / script >
< script src = "../mark.min.js" > < / script >
< script src = "../searcher.js" > < / script >
< script src = "../clipboard.min.js" > < / script >
< script src = "../highlight.js" > < / script >
< script src = "../book.js" > < / script >
<!-- Custom JS scripts -->
< / div >
< / body >
< / html >