provisioning/docs/book/api/extensions.html
Jesús Pérez 6a59d34bb1
chore: update provisioning configuration and documentation
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.
2025-12-11 21:50:42 +00:00

1366 lines
48 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="ayu sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Extensions API - 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/api/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-api"><a class="header" href="#extension-development-api">Extension Development API</a></h1>
<p>This document provides comprehensive guidance for developing extensions for provisioning, including providers, task services, and cluster configurations.</p>
<h2 id="overview"><a class="header" href="#overview">Overview</a></h2>
<p>Provisioning supports three types of extensions:</p>
<ol>
<li><strong>Providers</strong>: Cloud infrastructure providers (AWS, UpCloud, Local, etc.)</li>
<li><strong>Task Services</strong>: Infrastructure components (Kubernetes, Cilium, Containerd, etc.)</li>
<li><strong>Clusters</strong>: Complete deployment configurations (BuildKit, CI/CD, etc.)</li>
</ol>
<p>All extensions follow a standardized structure and API for seamless integration.</p>
<h2 id="extension-structure"><a class="header" href="#extension-structure">Extension Structure</a></h2>
<h3 id="standard-directory-layout"><a class="header" href="#standard-directory-layout">Standard Directory Layout</a></h3>
<pre><code>extension-name/
├── kcl.mod # KCL module definition
├── kcl/ # KCL configuration files
│ ├── mod.k # Main module
│ ├── settings.k # Settings schema
│ ├── version.k # Version configuration
│ └── lib.k # Common functions
├── nulib/ # Nushell library modules
│ ├── mod.nu # Main module
│ ├── create.nu # Creation operations
│ ├── delete.nu # Deletion operations
│ └── utils.nu # Utility functions
├── templates/ # Jinja2 templates
│ ├── config.j2 # Configuration templates
│ └── scripts/ # Script templates
├── generate/ # Code generation scripts
│ └── generate.nu # Generation commands
├── README.md # Extension documentation
└── metadata.toml # Extension metadata
</code></pre>
<h2 id="provider-extension-api"><a class="header" href="#provider-extension-api">Provider Extension API</a></h2>
<h3 id="provider-interface"><a class="header" href="#provider-interface">Provider Interface</a></h3>
<p>All providers must implement the following interface:</p>
<h4 id="core-operations"><a class="header" href="#core-operations">Core Operations</a></h4>
<ul>
<li><code>create-server(config: record) -&gt; record</code></li>
<li><code>delete-server(server_id: string) -&gt; null</code></li>
<li><code>list-servers() -&gt; list&lt;record&gt;</code></li>
<li><code>get-server-info(server_id: string) -&gt; record</code></li>
<li><code>start-server(server_id: string) -&gt; null</code></li>
<li><code>stop-server(server_id: string) -&gt; null</code></li>
<li><code>reboot-server(server_id: string) -&gt; null</code></li>
</ul>
<h4 id="pricing-and-plans"><a class="header" href="#pricing-and-plans">Pricing and Plans</a></h4>
<ul>
<li><code>get-pricing() -&gt; list&lt;record&gt;</code></li>
<li><code>get-plans() -&gt; list&lt;record&gt;</code></li>
<li><code>get-zones() -&gt; list&lt;record&gt;</code></li>
</ul>
<h4 id="ssh-and-access"><a class="header" href="#ssh-and-access">SSH and Access</a></h4>
<ul>
<li><code>get-ssh-access(server_id: string) -&gt; record</code></li>
<li><code>configure-firewall(server_id: string, rules: list&lt;record&gt;) -&gt; null</code></li>
</ul>
<h3 id="provider-development-template"><a class="header" href="#provider-development-template">Provider Development Template</a></h3>
<h4 id="kcl-configuration-schema"><a class="header" href="#kcl-configuration-schema">KCL Configuration Schema</a></h4>
<p>Create <code>kcl/settings.k</code>:</p>
<pre><code class="language-kcl"># Provider settings schema
schema ProviderSettings {
# Authentication configuration
auth: {
method: "api_key" | "certificate" | "oauth" | "basic"
api_key?: str
api_secret?: str
username?: str
password?: str
certificate_path?: str
private_key_path?: str
}
# API configuration
api: {
base_url: str
version?: str = "v1"
timeout?: int = 30
retries?: int = 3
}
# Default server configuration
defaults: {
plan?: str
zone?: str
os?: str
ssh_keys?: [str]
firewall_rules?: [FirewallRule]
}
# Provider-specific settings
features: {
load_balancer?: bool = false
storage_encryption?: bool = true
backup?: bool = true
monitoring?: bool = false
}
}
schema FirewallRule {
direction: "ingress" | "egress"
protocol: "tcp" | "udp" | "icmp"
port?: str
source?: str
destination?: str
action: "allow" | "deny"
}
schema ServerConfig {
hostname: str
plan: str
zone: str
os: str = "ubuntu-22.04"
ssh_keys: [str] = []
tags?: {str: str} = {}
firewall_rules?: [FirewallRule] = []
storage?: {
size?: int
type?: str
encrypted?: bool = true
}
network?: {
public_ip?: bool = true
private_network?: str
bandwidth?: int
}
}
</code></pre>
<h4 id="nushell-implementation"><a class="header" href="#nushell-implementation">Nushell Implementation</a></h4>
<p>Create <code>nulib/mod.nu</code>:</p>
<pre><code class="language-nushell">use std log
# Provider name and version
export const PROVIDER_NAME = "my-provider"
export const PROVIDER_VERSION = "1.0.0"
# Import sub-modules
use create.nu *
use delete.nu *
use utils.nu *
# Provider interface implementation
export def "provider-info" [] -&gt; record {
{
name: $PROVIDER_NAME,
version: $PROVIDER_VERSION,
type: "provider",
interface: "API",
supported_operations: [
"create-server", "delete-server", "list-servers",
"get-server-info", "start-server", "stop-server"
],
required_auth: ["api_key", "api_secret"],
supported_os: ["ubuntu-22.04", "debian-11", "centos-8"],
regions: (get-zones).name
}
}
export def "validate-config" [config: record] -&gt; record {
mut errors = []
mut warnings = []
# Validate authentication
if ($config | get -o "auth.api_key" | is-empty) {
$errors = ($errors | append "Missing API key")
}
if ($config | get -o "auth.api_secret" | is-empty) {
$errors = ($errors | append "Missing API secret")
}
# Validate API configuration
let api_url = ($config | get -o "api.base_url")
if ($api_url | is-empty) {
$errors = ($errors | append "Missing API base URL")
} else {
try {
http get $"($api_url)/health" | ignore
} catch {
$warnings = ($warnings | append "API endpoint not reachable")
}
}
{
valid: ($errors | is-empty),
errors: $errors,
warnings: $warnings
}
}
export def "test-connection" [config: record] -&gt; record {
try {
let api_url = ($config | get "api.base_url")
let response = (http get $"($api_url)/account" --headers {
Authorization: $"Bearer ($config | get 'auth.api_key')"
})
{
success: true,
account_info: $response,
message: "Connection successful"
}
} catch {|e|
{
success: false,
error: ($e | get msg),
message: "Connection failed"
}
}
}
</code></pre>
<p>Create <code>nulib/create.nu</code>:</p>
<pre><code class="language-nushell">use std log
use utils.nu *
export def "create-server" [
config: record # Server configuration
--check # Check mode only
--wait # Wait for completion
] -&gt; record {
log info $"Creating server: ($config.hostname)"
if $check {
return {
action: "create-server",
hostname: $config.hostname,
check_mode: true,
would_create: true,
estimated_time: "2-5 minutes"
}
}
# Validate configuration
let validation = (validate-server-config $config)
if not $validation.valid {
error make {
msg: $"Invalid server configuration: ($validation.errors | str join ', ')"
}
}
# Prepare API request
let api_config = (get-api-config)
let request_body = {
hostname: $config.hostname,
plan: $config.plan,
zone: $config.zone,
os: $config.os,
ssh_keys: $config.ssh_keys,
tags: $config.tags,
firewall_rules: $config.firewall_rules
}
try {
let response = (http post $"($api_config.base_url)/servers" --headers {
Authorization: $"Bearer ($api_config.auth.api_key)"
Content-Type: "application/json"
} $request_body)
let server_id = ($response | get id)
log info $"Server creation initiated: ($server_id)"
if $wait {
let final_status = (wait-for-server-ready $server_id)
{
success: true,
server_id: $server_id,
hostname: $config.hostname,
status: $final_status,
ip_addresses: (get-server-ips $server_id),
ssh_access: (get-ssh-access $server_id)
}
} else {
{
success: true,
server_id: $server_id,
hostname: $config.hostname,
status: "creating",
message: "Server creation in progress"
}
}
} catch {|e|
error make {
msg: $"Server creation failed: ($e | get msg)"
}
}
}
def validate-server-config [config: record] -&gt; record {
mut errors = []
# Required fields
if ($config | get -o hostname | is-empty) {
$errors = ($errors | append "Hostname is required")
}
if ($config | get -o plan | is-empty) {
$errors = ($errors | append "Plan is required")
}
if ($config | get -o zone | is-empty) {
$errors = ($errors | append "Zone is required")
}
# Validate plan exists
let available_plans = (get-plans)
if not ($config.plan in ($available_plans | get name)) {
$errors = ($errors | append $"Invalid plan: ($config.plan)")
}
# Validate zone exists
let available_zones = (get-zones)
if not ($config.zone in ($available_zones | get name)) {
$errors = ($errors | append $"Invalid zone: ($config.zone)")
}
{
valid: ($errors | is-empty),
errors: $errors
}
}
def wait-for-server-ready [server_id: string] -&gt; string {
mut attempts = 0
let max_attempts = 60 # 10 minutes
while $attempts &lt; $max_attempts {
let server_info = (get-server-info $server_id)
let status = ($server_info | get status)
match $status {
"running" =&gt; { return "running" },
"error" =&gt; { error make { msg: "Server creation failed" } },
_ =&gt; {
log info $"Server status: ($status), waiting..."
sleep 10sec
$attempts = $attempts + 1
}
}
}
error make { msg: "Server creation timeout" }
}
</code></pre>
<h3 id="provider-registration"><a class="header" href="#provider-registration">Provider Registration</a></h3>
<p>Add provider metadata in <code>metadata.toml</code>:</p>
<pre><code class="language-toml">[extension]
name = "my-provider"
type = "provider"
version = "1.0.0"
description = "Custom cloud provider integration"
author = "Your Name &lt;your.email@example.com&gt;"
license = "MIT"
[compatibility]
provisioning_version = "&gt;=2.0.0"
nushell_version = "&gt;=0.107.0"
kcl_version = "&gt;=0.11.0"
[capabilities]
server_management = true
load_balancer = false
storage_encryption = true
backup = true
monitoring = false
[authentication]
methods = ["api_key", "certificate"]
required_fields = ["api_key", "api_secret"]
[regions]
default = "us-east-1"
available = ["us-east-1", "us-west-2", "eu-west-1"]
[support]
documentation = "https://docs.example.com/provider"
issues = "https://github.com/example/provider/issues"
</code></pre>
<h2 id="task-service-extension-api"><a class="header" href="#task-service-extension-api">Task Service Extension API</a></h2>
<h3 id="task-service-interface"><a class="header" href="#task-service-interface">Task Service Interface</a></h3>
<p>Task services must implement:</p>
<h4 id="core-operations-1"><a class="header" href="#core-operations-1">Core Operations</a></h4>
<ul>
<li><code>install(config: record) -&gt; record</code></li>
<li><code>uninstall(config: record) -&gt; null</code></li>
<li><code>configure(config: record) -&gt; null</code></li>
<li><code>status() -&gt; record</code></li>
<li><code>restart() -&gt; null</code></li>
<li><code>upgrade(version: string) -&gt; record</code></li>
</ul>
<h4 id="version-management"><a class="header" href="#version-management">Version Management</a></h4>
<ul>
<li><code>get-current-version() -&gt; string</code></li>
<li><code>get-available-versions() -&gt; list&lt;string&gt;</code></li>
<li><code>check-updates() -&gt; record</code></li>
</ul>
<h3 id="task-service-development-template"><a class="header" href="#task-service-development-template">Task Service Development Template</a></h3>
<h4 id="kcl-schema"><a class="header" href="#kcl-schema">KCL Schema</a></h4>
<p>Create <code>kcl/version.k</code>:</p>
<pre><code class="language-kcl"># Task service version configuration
import version_management
taskserv_version: version_management.TaskservVersion = {
name = "my-service"
version = "1.0.0"
# Version source configuration
source = {
type = "github"
repository = "example/my-service"
release_pattern = "v{version}"
}
# Installation configuration
install = {
method = "binary"
binary_name = "my-service"
binary_path = "/usr/local/bin"
config_path = "/etc/my-service"
data_path = "/var/lib/my-service"
}
# Dependencies
dependencies = [
{ name = "containerd", version = "&gt;=1.6.0" }
]
# Service configuration
service = {
type = "systemd"
user = "my-service"
group = "my-service"
ports = [8080, 9090]
}
# Health check configuration
health_check = {
endpoint = "http://localhost:9090/health"
interval = 30
timeout = 5
retries = 3
}
}
</code></pre>
<h4 id="nushell-implementation-1"><a class="header" href="#nushell-implementation-1">Nushell Implementation</a></h4>
<p>Create <code>nulib/mod.nu</code>:</p>
<pre><code class="language-nushell">use std log
use ../../../lib_provisioning *
export const SERVICE_NAME = "my-service"
export const SERVICE_VERSION = "1.0.0"
export def "taskserv-info" [] -&gt; record {
{
name: $SERVICE_NAME,
version: $SERVICE_VERSION,
type: "taskserv",
category: "application",
description: "Custom application service",
dependencies: ["containerd"],
ports: [8080, 9090],
config_files: ["/etc/my-service/config.yaml"],
data_directories: ["/var/lib/my-service"]
}
}
export def "install" [
config: record = {}
--check # Check mode only
--version: string # Specific version to install
] -&gt; record {
let install_version = if ($version | is-not-empty) {
$version
} else {
(get-latest-version)
}
log info $"Installing ($SERVICE_NAME) version ($install_version)"
if $check {
return {
action: "install",
service: $SERVICE_NAME,
version: $install_version,
check_mode: true,
would_install: true,
requirements_met: (check-requirements)
}
}
# Check system requirements
let req_check = (check-requirements)
if not $req_check.met {
error make {
msg: $"Requirements not met: ($req_check.missing | str join ', ')"
}
}
# Download and install
let binary_path = (download-binary $install_version)
install-binary $binary_path
create-user-and-directories
generate-config $config
install-systemd-service
# Start service
systemctl start $SERVICE_NAME
systemctl enable $SERVICE_NAME
# Verify installation
let health = (check-health)
if not $health.healthy {
error make { msg: "Service failed health check after installation" }
}
{
success: true,
service: $SERVICE_NAME,
version: $install_version,
status: "running",
health: $health
}
}
export def "uninstall" [
--force # Force removal even if running
--keep-data # Keep data directories
] -&gt; null {
log info $"Uninstalling ($SERVICE_NAME)"
# Stop and disable service
try {
systemctl stop $SERVICE_NAME
systemctl disable $SERVICE_NAME
} catch {
log warning "Failed to stop systemd service"
}
# Remove binary
try {
rm -f $"/usr/local/bin/($SERVICE_NAME)"
} catch {
log warning "Failed to remove binary"
}
# Remove configuration
try {
rm -rf $"/etc/($SERVICE_NAME)"
} catch {
log warning "Failed to remove configuration"
}
# Remove data directories (unless keeping)
if not $keep_data {
try {
rm -rf $"/var/lib/($SERVICE_NAME)"
} catch {
log warning "Failed to remove data directories"
}
}
# Remove systemd service file
try {
rm -f $"/etc/systemd/system/($SERVICE_NAME).service"
systemctl daemon-reload
} catch {
log warning "Failed to remove systemd service"
}
log info $"($SERVICE_NAME) uninstalled successfully"
}
export def "status" [] -&gt; record {
let systemd_status = try {
systemctl is-active $SERVICE_NAME | str trim
} catch {
"unknown"
}
let health = (check-health)
let version = (get-current-version)
{
service: $SERVICE_NAME,
version: $version,
systemd_status: $systemd_status,
health: $health,
uptime: (get-service-uptime),
memory_usage: (get-memory-usage),
cpu_usage: (get-cpu-usage)
}
}
def check-requirements [] -&gt; record {
mut missing = []
mut met = true
# Check for containerd
if not (which containerd | is-not-empty) {
$missing = ($missing | append "containerd")
$met = false
}
# Check for systemctl
if not (which systemctl | is-not-empty) {
$missing = ($missing | append "systemctl")
$met = false
}
{
met: $met,
missing: $missing
}
}
def check-health [] -&gt; record {
try {
let response = (http get "http://localhost:9090/health")
{
healthy: true,
status: ($response | get status),
last_check: (date now)
}
} catch {
{
healthy: false,
error: "Health endpoint not responding",
last_check: (date now)
}
}
}
</code></pre>
<h2 id="cluster-extension-api"><a class="header" href="#cluster-extension-api">Cluster Extension API</a></h2>
<h3 id="cluster-interface"><a class="header" href="#cluster-interface">Cluster Interface</a></h3>
<p>Clusters orchestrate multiple components:</p>
<h4 id="core-operations-2"><a class="header" href="#core-operations-2">Core Operations</a></h4>
<ul>
<li><code>create(config: record) -&gt; record</code></li>
<li><code>delete(config: record) -&gt; null</code></li>
<li><code>status() -&gt; record</code></li>
<li><code>scale(replicas: int) -&gt; record</code></li>
<li><code>upgrade(version: string) -&gt; record</code></li>
</ul>
<h4 id="component-management"><a class="header" href="#component-management">Component Management</a></h4>
<ul>
<li><code>list-components() -&gt; list&lt;record&gt;</code></li>
<li><code>component-status(name: string) -&gt; record</code></li>
<li><code>restart-component(name: string) -&gt; null</code></li>
</ul>
<h3 id="cluster-development-template"><a class="header" href="#cluster-development-template">Cluster Development Template</a></h3>
<h4 id="kcl-configuration"><a class="header" href="#kcl-configuration">KCL Configuration</a></h4>
<p>Create <code>kcl/cluster.k</code>:</p>
<pre><code class="language-kcl"># Cluster configuration schema
schema ClusterConfig {
# Cluster metadata
name: str
version: str = "1.0.0"
description?: str
# Components to deploy
components: [Component]
# Resource requirements
resources: {
min_nodes?: int = 1
cpu_per_node?: str = "2"
memory_per_node?: str = "4Gi"
storage_per_node?: str = "20Gi"
}
# Network configuration
network: {
cluster_cidr?: str = "10.244.0.0/16"
service_cidr?: str = "10.96.0.0/12"
dns_domain?: str = "cluster.local"
}
# Feature flags
features: {
monitoring?: bool = true
logging?: bool = true
ingress?: bool = false
storage?: bool = true
}
}
schema Component {
name: str
type: "taskserv" | "application" | "infrastructure"
version?: str
enabled: bool = true
dependencies?: [str] = []
# Component-specific configuration
config?: {str: any} = {}
# Resource requirements
resources?: {
cpu?: str
memory?: str
storage?: str
replicas?: int = 1
}
}
# Example cluster configuration
buildkit_cluster: ClusterConfig = {
name = "buildkit"
version = "1.0.0"
description = "Container build cluster with BuildKit and registry"
components = [
{
name = "containerd"
type = "taskserv"
version = "1.7.0"
enabled = True
dependencies = []
},
{
name = "buildkit"
type = "taskserv"
version = "0.12.0"
enabled = True
dependencies = ["containerd"]
config = {
worker_count = 4
cache_size = "10Gi"
registry_mirrors = ["registry:5000"]
}
},
{
name = "registry"
type = "application"
version = "2.8.0"
enabled = True
dependencies = []
config = {
storage_driver = "filesystem"
storage_path = "/var/lib/registry"
auth_enabled = False
}
resources = {
cpu = "500m"
memory = "1Gi"
storage = "50Gi"
replicas = 1
}
}
]
resources = {
min_nodes = 1
cpu_per_node = "4"
memory_per_node = "8Gi"
storage_per_node = "100Gi"
}
features = {
monitoring = True
logging = True
ingress = False
storage = True
}
}
</code></pre>
<h4 id="nushell-implementation-2"><a class="header" href="#nushell-implementation-2">Nushell Implementation</a></h4>
<p>Create <code>nulib/mod.nu</code>:</p>
<pre><code class="language-nushell">use std log
use ../../../lib_provisioning *
export const CLUSTER_NAME = "my-cluster"
export const CLUSTER_VERSION = "1.0.0"
export def "cluster-info" [] -&gt; record {
{
name: $CLUSTER_NAME,
version: $CLUSTER_VERSION,
type: "cluster",
category: "build",
description: "Custom application cluster",
components: (get-cluster-components),
required_resources: {
min_nodes: 1,
cpu_per_node: "2",
memory_per_node: "4Gi",
storage_per_node: "20Gi"
}
}
}
export def "create" [
config: record = {}
--check # Check mode only
--wait # Wait for completion
] -&gt; record {
log info $"Creating cluster: ($CLUSTER_NAME)"
if $check {
return {
action: "create-cluster",
cluster: $CLUSTER_NAME,
check_mode: true,
would_create: true,
components: (get-cluster-components),
requirements_check: (check-cluster-requirements)
}
}
# Validate cluster requirements
let req_check = (check-cluster-requirements)
if not $req_check.met {
error make {
msg: $"Cluster requirements not met: ($req_check.issues | str join ', ')"
}
}
# Get component deployment order
let components = (get-cluster-components)
let deployment_order = (resolve-component-dependencies $components)
mut deployment_status = []
# Deploy components in dependency order
for component in $deployment_order {
log info $"Deploying component: ($component.name)"
try {
let result = match $component.type {
"taskserv" =&gt; {
taskserv create $component.name --config $component.config --wait
},
"application" =&gt; {
deploy-application $component
},
_ =&gt; {
error make { msg: $"Unknown component type: ($component.type)" }
}
}
$deployment_status = ($deployment_status | append {
component: $component.name,
status: "deployed",
result: $result
})
} catch {|e|
log error $"Failed to deploy ($component.name): ($e.msg)"
$deployment_status = ($deployment_status | append {
component: $component.name,
status: "failed",
error: $e.msg
})
# Rollback on failure
rollback-cluster-deployment $deployment_status
error make { msg: $"Cluster deployment failed at component: ($component.name)" }
}
}
# Configure cluster networking and integrations
configure-cluster-networking $config
setup-cluster-monitoring $config
# Wait for all components to be ready
if $wait {
wait-for-cluster-ready
}
{
success: true,
cluster: $CLUSTER_NAME,
components: $deployment_status,
endpoints: (get-cluster-endpoints),
status: "running"
}
}
export def "delete" [
config: record = {}
--force # Force deletion
] -&gt; null {
log info $"Deleting cluster: ($CLUSTER_NAME)"
let components = (get-cluster-components)
let deletion_order = ($components | reverse) # Delete in reverse order
for component in $deletion_order {
log info $"Removing component: ($component.name)"
try {
match $component.type {
"taskserv" =&gt; {
taskserv delete $component.name --force=$force
},
"application" =&gt; {
remove-application $component --force=$force
},
_ =&gt; {
log warning $"Unknown component type: ($component.type)"
}
}
} catch {|e|
log error $"Failed to remove ($component.name): ($e.msg)"
if not $force {
error make { msg: $"Component removal failed: ($component.name)" }
}
}
}
# Clean up cluster-level resources
cleanup-cluster-networking
cleanup-cluster-monitoring
cleanup-cluster-storage
log info $"Cluster ($CLUSTER_NAME) deleted successfully"
}
def get-cluster-components [] -&gt; list&lt;record&gt; {
[
{
name: "containerd",
type: "taskserv",
version: "1.7.0",
dependencies: []
},
{
name: "my-service",
type: "taskserv",
version: "1.0.0",
dependencies: ["containerd"]
},
{
name: "registry",
type: "application",
version: "2.8.0",
dependencies: []
}
]
}
def resolve-component-dependencies [components: list&lt;record&gt;] -&gt; list&lt;record&gt; {
# Topological sort of components based on dependencies
mut sorted = []
mut remaining = $components
while ($remaining | length) &gt; 0 {
let no_deps = ($remaining | where {|comp|
($comp.dependencies | all {|dep|
$dep in ($sorted | get name)
})
})
if ($no_deps | length) == 0 {
error make { msg: "Circular dependency detected in cluster components" }
}
$sorted = ($sorted | append $no_deps)
$remaining = ($remaining | where {|comp|
not ($comp.name in ($no_deps | get name))
})
}
$sorted
}
</code></pre>
<h2 id="extension-registration-and-discovery"><a class="header" href="#extension-registration-and-discovery">Extension Registration and Discovery</a></h2>
<h3 id="extension-registry"><a class="header" href="#extension-registry">Extension Registry</a></h3>
<p>Extensions are registered in the system through:</p>
<ol>
<li><strong>Directory Structure</strong>: Placed in appropriate directories (providers/, taskservs/, cluster/)</li>
<li><strong>Metadata Files</strong>: <code>metadata.toml</code> with extension information</li>
<li><strong>Module Files</strong>: <code>kcl.mod</code> for KCL dependencies</li>
</ol>
<h3 id="registration-api"><a class="header" href="#registration-api">Registration API</a></h3>
<h4 id="register-extensionpath-string-type-string---record"><a class="header" href="#register-extensionpath-string-type-string---record"><code>register-extension(path: string, type: string) -&gt; record</code></a></h4>
<p>Registers a new extension with the system.</p>
<p><strong>Parameters:</strong></p>
<ul>
<li><code>path</code>: Path to extension directory</li>
<li><code>type</code>: Extension type (provider, taskserv, cluster)</li>
</ul>
<h4 id="unregister-extensionname-string-type-string---null"><a class="header" href="#unregister-extensionname-string-type-string---null"><code>unregister-extension(name: string, type: string) -&gt; null</code></a></h4>
<p>Removes extension from the registry.</p>
<h4 id="list-registered-extensionstype-string---listrecord"><a class="header" href="#list-registered-extensionstype-string---listrecord"><code>list-registered-extensions(type?: string) -&gt; list&lt;record&gt;</code></a></h4>
<p>Lists all registered extensions, optionally filtered by type.</p>
<h3 id="extension-validation"><a class="header" href="#extension-validation">Extension Validation</a></h3>
<h4 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h4>
<ol>
<li><strong>Structure Validation</strong>: Required files and directories exist</li>
<li><strong>Schema Validation</strong>: KCL schemas are valid</li>
<li><strong>Interface Validation</strong>: Required functions are implemented</li>
<li><strong>Dependency Validation</strong>: Dependencies are available</li>
<li><strong>Version Validation</strong>: Version constraints are met</li>
</ol>
<h4 id="validate-extensionpath-string-type-string---record"><a class="header" href="#validate-extensionpath-string-type-string---record"><code>validate-extension(path: string, type: string) -&gt; record</code></a></h4>
<p>Validates extension structure and implementation.</p>
<h2 id="testing-extensions"><a class="header" href="#testing-extensions">Testing Extensions</a></h2>
<h3 id="test-framework"><a class="header" href="#test-framework">Test Framework</a></h3>
<p>Extensions should include comprehensive tests:</p>
<h4 id="unit-tests"><a class="header" href="#unit-tests">Unit Tests</a></h4>
<p>Create <code>tests/unit_tests.nu</code>:</p>
<pre><code class="language-nushell">use std testing
export def test_provider_config_validation [] {
let config = {
auth: { api_key: "test-key", api_secret: "test-secret" },
api: { base_url: "https://api.test.com" }
}
let result = (validate-config $config)
assert ($result.valid == true)
assert ($result.errors | is-empty)
}
export def test_server_creation_check_mode [] {
let config = {
hostname: "test-server",
plan: "1xCPU-1GB",
zone: "test-zone"
}
let result = (create-server $config --check)
assert ($result.check_mode == true)
assert ($result.would_create == true)
}
</code></pre>
<h4 id="integration-tests"><a class="header" href="#integration-tests">Integration Tests</a></h4>
<p>Create <code>tests/integration_tests.nu</code>:</p>
<pre><code class="language-nushell">use std testing
export def test_full_server_lifecycle [] {
# Test server creation
let create_config = {
hostname: "integration-test",
plan: "1xCPU-1GB",
zone: "test-zone"
}
let server = (create-server $create_config --wait)
assert ($server.success == true)
let server_id = $server.server_id
# Test server info retrieval
let info = (get-server-info $server_id)
assert ($info.hostname == "integration-test")
assert ($info.status == "running")
# Test server deletion
delete-server $server_id
# Verify deletion
let final_info = try { get-server-info $server_id } catch { null }
assert ($final_info == null)
}
</code></pre>
<h3 id="running-tests"><a class="header" href="#running-tests">Running Tests</a></h3>
<pre><code class="language-bash"># Run unit tests
nu tests/unit_tests.nu
# Run integration tests
nu tests/integration_tests.nu
# Run all tests
nu tests/run_all_tests.nu
</code></pre>
<h2 id="documentation-requirements"><a class="header" href="#documentation-requirements">Documentation Requirements</a></h2>
<h3 id="extension-documentation"><a class="header" href="#extension-documentation">Extension Documentation</a></h3>
<p>Each extension must include:</p>
<ol>
<li><strong>README.md</strong>: Overview, installation, and usage</li>
<li><strong>API.md</strong>: Detailed API documentation</li>
<li><strong>EXAMPLES.md</strong>: Usage examples and tutorials</li>
<li><strong>CHANGELOG.md</strong>: Version history and changes</li>
</ol>
<h3 id="api-documentation-template"><a class="header" href="#api-documentation-template">API Documentation Template</a></h3>
<pre><code class="language-markdown"># Extension Name API
## Overview
Brief description of the extension and its purpose.
## Installation
Steps to install and configure the extension.
## Configuration
Configuration schema and options.
## API Reference
Detailed API documentation with examples.
## Examples
Common usage patterns and examples.
## Troubleshooting
Common issues and solutions.
</code></pre>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="development-guidelines"><a class="header" href="#development-guidelines">Development Guidelines</a></h3>
<ol>
<li><strong>Follow Naming Conventions</strong>: Use consistent naming for functions and variables</li>
<li><strong>Error Handling</strong>: Implement comprehensive error handling and recovery</li>
<li><strong>Logging</strong>: Use structured logging for debugging and monitoring</li>
<li><strong>Configuration Validation</strong>: Validate all inputs and configurations</li>
<li><strong>Documentation</strong>: Document all public APIs and configurations</li>
<li><strong>Testing</strong>: Include comprehensive unit and integration tests</li>
<li><strong>Versioning</strong>: Follow semantic versioning principles</li>
<li><strong>Security</strong>: Implement secure credential handling and API calls</li>
</ol>
<h3 id="performance-considerations"><a class="header" href="#performance-considerations">Performance Considerations</a></h3>
<ol>
<li><strong>Caching</strong>: Cache expensive operations and API calls</li>
<li><strong>Parallel Processing</strong>: Use parallel execution where possible</li>
<li><strong>Resource Management</strong>: Clean up resources properly</li>
<li><strong>Batch Operations</strong>: Batch API calls when possible</li>
<li><strong>Health Monitoring</strong>: Implement health checks and monitoring</li>
</ol>
<h3 id="security-best-practices"><a class="header" href="#security-best-practices">Security Best Practices</a></h3>
<ol>
<li><strong>Credential Management</strong>: Store credentials securely</li>
<li><strong>Input Validation</strong>: Validate and sanitize all inputs</li>
<li><strong>Access Control</strong>: Implement proper access controls</li>
<li><strong>Audit Logging</strong>: Log all security-relevant operations</li>
<li><strong>Encryption</strong>: Encrypt sensitive data in transit and at rest</li>
</ol>
<p>This extension development API provides a comprehensive framework for building robust, scalable, and maintainable extensions for provisioning.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../api/provider-api.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="../api/sdks.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="../api/provider-api.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="../api/sdks.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>