Update configuration files, templates, and internal documentation for the provisioning repository system. Configuration Updates: - KMS configuration modernization - Plugin system settings - Service port mappings - Test cluster topologies - Installation configuration examples - VM configuration defaults - Cedar authorization policies Documentation Updates: - Library module documentation - Extension API guides - AI system documentation - Service management guides - Test environment setup - Plugin usage guides - Validator configuration documentation All changes are backward compatible.
1577 lines
61 KiB
HTML
1577 lines
61 KiB
HTML
<!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>
|
||
<pre><code>Extension Ecosystem
|
||
├── 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
|
||
</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
|
||
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")
|
||
</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
|
||
cp -r workspace/extensions/providers/template workspace/extensions/providers/my-cloud
|
||
|
||
# Navigate to new provider
|
||
cd workspace/extensions/providers/my-cloud
|
||
</code></pre>
|
||
<p><strong>2. Update Configuration</strong>:</p>
|
||
<pre><code class="language-bash"># Initialize provider metadata
|
||
nu init-provider.nu \
|
||
--name "my-cloud" \
|
||
--display-name "MyCloud Provider" \
|
||
--author "$USER" \
|
||
--description "MyCloud platform integration"
|
||
</code></pre>
|
||
<h3 id="provider-structure"><a class="header" href="#provider-structure">Provider Structure</a></h3>
|
||
<pre><code>my-cloud/
|
||
├── README.md # Provider documentation
|
||
├── kcl/ # KCL configuration schemas
|
||
│ ├── settings.k # Provider settings schema
|
||
│ ├── servers.k # Server configuration schema
|
||
│ ├── networks.k # Network configuration schema
|
||
│ └── kcl.mod # KCL module dependencies
|
||
├── 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
|
||
</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
|
||
# 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)"})
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Authentication Module</strong> (<code>nulib/auth/client.nu</code>):</p>
|
||
<pre><code class="language-nushell"># API client setup and authentication
|
||
|
||
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"
|
||
}
|
||
</code></pre>
|
||
<p><strong>KCL Configuration Schema</strong> (<code>kcl/settings.k</code>):</p>
|
||
<pre><code class="language-kcl"># MyCloud Provider Configuration Schema
|
||
|
||
schema MyCloudConfig:
|
||
"""MyCloud provider configuration"""
|
||
|
||
api_url?: str = "https://api.my-cloud.com"
|
||
api_key: str
|
||
api_secret: str
|
||
timeout?: int = 30
|
||
retries?: int = 3
|
||
|
||
# Rate limiting
|
||
rate_limit?: {
|
||
requests_per_minute?: int = 60
|
||
burst_size?: int = 10
|
||
} = {}
|
||
|
||
# Default settings
|
||
defaults?: {
|
||
zone?: str = "us-east-1"
|
||
template?: str = "ubuntu-22.04"
|
||
network?: str = "default"
|
||
} = {}
|
||
|
||
check:
|
||
len(api_key) > 0, "API key cannot be empty"
|
||
len(api_secret) > 0, "API secret cannot be empty"
|
||
timeout > 0, "Timeout must be positive"
|
||
retries >= 0, "Retries must be non-negative"
|
||
|
||
schema MyCloudServerConfig:
|
||
"""MyCloud server configuration"""
|
||
|
||
name: str
|
||
plan: str
|
||
zone?: str
|
||
template?: str = "ubuntu-22.04"
|
||
storage?: int = 25
|
||
tags?: {str: str} = {}
|
||
|
||
# Network configuration
|
||
network?: {
|
||
vpc_id?: str
|
||
subnet_id?: str
|
||
public_ip?: bool = true
|
||
firewall_rules?: [FirewallRule] = []
|
||
}
|
||
|
||
check:
|
||
len(name) > 0, "Server name cannot be empty"
|
||
plan in ["small", "medium", "large", "xlarge"], "Invalid plan"
|
||
storage >= 10, "Minimum storage is 10GB"
|
||
storage <= 2048, "Maximum storage is 2TB"
|
||
|
||
schema FirewallRule:
|
||
"""Firewall rule configuration"""
|
||
|
||
port: int | str
|
||
protocol: str = "tcp"
|
||
source: str = "0.0.0.0/0"
|
||
description?: str
|
||
|
||
check:
|
||
protocol in ["tcp", "udp", "icmp"], "Invalid protocol"
|
||
</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
|
||
|
||
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"
|
||
}
|
||
</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
|
||
|
||
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"
|
||
}
|
||
</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
|
||
cp -r workspace/extensions/taskservs/template workspace/extensions/taskservs/my-service
|
||
|
||
# Navigate to new service
|
||
cd workspace/extensions/taskservs/my-service
|
||
</code></pre>
|
||
<p><strong>2. Initialize Service</strong>:</p>
|
||
<pre><code class="language-bash"># Initialize service metadata
|
||
nu init-service.nu \
|
||
--name "my-service" \
|
||
--display-name "My Custom Service" \
|
||
--type "database" \
|
||
--github-repo "myorg/my-service"
|
||
</code></pre>
|
||
<h3 id="task-service-structure"><a class="header" href="#task-service-structure">Task Service Structure</a></h3>
|
||
<pre><code>my-service/
|
||
├── README.md # Service documentation
|
||
├── kcl/ # KCL schemas
|
||
│ ├── version.k # Version and GitHub integration
|
||
│ ├── config.k # Service configuration schema
|
||
│ └── kcl.mod # Module dependencies
|
||
├── 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
|
||
</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
|
||
# 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)"})
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Version Configuration</strong> (<code>kcl/version.k</code>):</p>
|
||
<pre><code class="language-kcl"># Version management with GitHub integration
|
||
|
||
version_config: VersionConfig = {
|
||
service_name = "my-service"
|
||
|
||
# GitHub repository for version checking
|
||
github = {
|
||
owner = "myorg"
|
||
repo = "my-service"
|
||
|
||
# Release configuration
|
||
release = {
|
||
tag_prefix = "v"
|
||
prerelease = false
|
||
draft = false
|
||
}
|
||
|
||
# Asset patterns for different platforms
|
||
assets = {
|
||
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"
|
||
}
|
||
}
|
||
|
||
# Version constraints and compatibility
|
||
compatibility = {
|
||
min_kubernetes_version = "1.20.0"
|
||
max_kubernetes_version = "1.28.*"
|
||
|
||
# Dependencies
|
||
requires = {
|
||
"cert-manager": ">=1.8.0"
|
||
"ingress-nginx": ">=1.0.0"
|
||
}
|
||
|
||
# Conflicts
|
||
conflicts = {
|
||
"old-my-service": "*"
|
||
}
|
||
}
|
||
|
||
# Installation configuration
|
||
installation = {
|
||
default_namespace = "my-service"
|
||
create_namespace = true
|
||
|
||
# Resource requirements
|
||
resources = {
|
||
requests = {
|
||
cpu = "100m"
|
||
memory = "128Mi"
|
||
}
|
||
limits = {
|
||
cpu = "500m"
|
||
memory = "512Mi"
|
||
}
|
||
}
|
||
|
||
# Persistence
|
||
persistence = {
|
||
enabled = true
|
||
storage_class = "default"
|
||
size = "10Gi"
|
||
}
|
||
}
|
||
|
||
# Health check configuration
|
||
health_check = {
|
||
initial_delay_seconds = 30
|
||
period_seconds = 10
|
||
timeout_seconds = 5
|
||
failure_threshold = 3
|
||
|
||
# Health endpoints
|
||
endpoints = {
|
||
liveness = "/health/live"
|
||
readiness = "/health/ready"
|
||
}
|
||
}
|
||
}
|
||
</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
|
||
cp -r workspace/extensions/clusters/template workspace/extensions/clusters/my-stack
|
||
|
||
# Navigate to new cluster
|
||
cd workspace/extensions/clusters/my-stack
|
||
</code></pre>
|
||
<p><strong>2. Initialize Cluster</strong>:</p>
|
||
<pre><code class="language-bash"># Initialize cluster metadata
|
||
nu init-cluster.nu \
|
||
--name "my-stack" \
|
||
--display-name "My Application Stack" \
|
||
--type "web-application"
|
||
</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
|
||
# 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)
|
||
}
|
||
}
|
||
</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
|
||
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
|
||
</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
|
||
# 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)
|
||
}
|
||
}
|
||
</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
|
||
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
|
||
</code></pre>
|
||
<h3 id="extension-registry"><a class="header" href="#extension-registry">Extension Registry</a></h3>
|
||
<p><strong>Registry Structure</strong>:</p>
|
||
<pre><code>Extension Registry
|
||
├── 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
|
||
</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
|
||
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
|
||
}
|
||
</code></pre>
|
||
<p><strong>Configuration Management</strong>:</p>
|
||
<pre><code class="language-nushell"># Good: Configuration-driven with validation
|
||
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!
|
||
}
|
||
</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 {
|
||
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"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</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
|
||
# 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)
|
||
}
|
||
}
|
||
}
|
||
</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
|
||
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...
|
||
}
|
||
</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
|
||
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
|
||
</code></pre>
|
||
<h4 id="configuration-errors"><a class="header" href="#configuration-errors">Configuration Errors</a></h4>
|
||
<p><strong>Error</strong>: <code>Invalid KCL configuration</code></p>
|
||
<pre><code class="language-bash"># Solution: Validate KCL syntax
|
||
kcl check workspace/extensions/providers/my-provider/kcl/
|
||
|
||
# Format KCL files
|
||
kcl fmt workspace/extensions/providers/my-provider/kcl/
|
||
|
||
# Test with example data
|
||
kcl run workspace/extensions/providers/my-provider/kcl/settings.k -D api_key="test"
|
||
</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
|
||
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
|
||
</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
|
||
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
|
||
</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
|
||
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>
|
||
<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>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
<a rel="prev" href="../development/distribution-process.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
<a rel="next prefetch" href="../development/PROVIDER_AGNOSTIC_ARCHITECTURE.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
<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">
|
||
<a rel="prev" href="../development/distribution-process.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
<a rel="next prefetch" href="../development/PROVIDER_AGNOSTIC_ARCHITECTURE.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
<!-- Livereload script (if served using the cli tool) -->
|
||
<script>
|
||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||
const socket = new WebSocket(wsAddress);
|
||
socket.onmessage = function (event) {
|
||
if (event.data === "reload") {
|
||
socket.close();
|
||
location.reload();
|
||
}
|
||
};
|
||
|
||
window.onbeforeunload = function() {
|
||
socket.close();
|
||
}
|
||
</script>
|
||
|
||
|
||
|
||
<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>
|