provisioning/docs/book/development/extensions.html

1577 lines
61 KiB
HTML
Raw Normal View History

<!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
] -&gt; 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
] -&gt; 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
] -&gt; 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
] -&gt; list&lt;record&gt; {
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" =&gt; ($filtered | to json),
"yaml" =&gt; ($filtered | to yaml),
_ =&gt; $filtered
}
}
# Provider testing interface
export def "provider test" [
--test-type: string = "basic" # Test type: basic, full, integration
] -&gt; record {
match $test_type {
"basic" =&gt; test_basic_functionality,
"full" =&gt; test_full_functionality,
"integration" =&gt; test_integration,
_ =&gt; (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] -&gt; 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] -&gt; 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) &gt; 0, "API key cannot be empty"
len(api_secret) &gt; 0, "API secret cannot be empty"
timeout &gt; 0, "Timeout must be positive"
retries &gt;= 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) &gt; 0, "Server name cannot be empty"
plan in ["small", "medium", "large", "xlarge"], "Invalid plan"
storage &gt;= 10, "Minimum storage is 10GB"
storage &lt;= 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) &gt;= 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
] -&gt; 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
] -&gt; 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
] -&gt; 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
] -&gt; 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
] -&gt; 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
] -&gt; record {
match $test_type {
"basic" =&gt; test_basic_functionality $target,
"integration" =&gt; test_integration $target,
"full" =&gt; test_full_functionality $target,
_ =&gt; (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": "&gt;=1.8.0"
"ingress-nginx": "&gt;=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
] -&gt; 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
] -&gt; 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
] -&gt; 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
] -&gt; 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] -&gt; 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] -&gt; 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") =&gt; {
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") =&gt; {
error make {
msg: "Server creation failed: authentication error",
help: "Check API credentials and permissions"
}
},
_ =&gt; {
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
] -&gt; 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 provisionings 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>