# SDK Documentation This document provides comprehensive documentation for the official SDKs and client libraries available for provisioning. ## Available SDKs Provisioning provides SDKs in multiple languages to facilitate integration: ### Official SDKs - **Python SDK** (`provisioning-client`) - Full-featured Python client - **JavaScript/TypeScript SDK** (`@provisioning/client`) - Node.js and browser support - **Go SDK** (`go-provisioning-client`) - Go client library - **Rust SDK** (`provisioning-rs`) - Native Rust integration ### Community SDKs - **Java SDK** - Community-maintained Java client - **C# SDK** - .NET client library - **PHP SDK** - PHP client library ## Python SDK ### Installation ```bash # Install from PyPI pip install provisioning-client # Or install development version pip install git+https://github.com/provisioning-systems/python-client.git ``` ### Quick Start ```python from provisioning_client import ProvisioningClient import asyncio async def main(): # Initialize client client = ProvisioningClient( base_url="http://localhost:9090", auth_url="http://localhost:8081", username="admin", password="your-password" ) try: # Authenticate token = await client.authenticate() print(f"Authenticated with token: {token[:20]}...") # Create a server workflow task_id = client.create_server_workflow( infra="production", settings="prod-settings.ncl", wait=False ) print(f"Server workflow created: {task_id}") # Wait for completion task = client.wait_for_task_completion(task_id, timeout=600) print(f"Task completed with status: {task.status}") if task.status == "Completed": print(f"Output: {task.output}") elif task.status == "Failed": print(f"Error: {task.error}") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": asyncio.run(main()) ``` ### Advanced Usage #### WebSocket Integration ```python async def monitor_workflows(): client = ProvisioningClient() await client.authenticate() # Set up event handlers async def on_task_update(event): print(f"Task {event['data']['task_id']} status: {event['data']['status']}") async def on_progress_update(event): print(f"Progress: {event['data']['progress']}% - {event['data']['current_step']}") client.on_event('TaskStatusChanged', on_task_update) client.on_event('WorkflowProgressUpdate', on_progress_update) # Connect to WebSocket await client.connect_websocket(['TaskStatusChanged', 'WorkflowProgressUpdate']) # Keep connection alive await asyncio.sleep(3600) # Monitor for 1 hour ``` #### Batch Operations ```python async def execute_batch_deployment(): client = ProvisioningClient() await client.authenticate() batch_config = { "name": "production_deployment", "version": "1.0.0", "storage_backend": "surrealdb", "parallel_limit": 5, "rollback_enabled": True, "operations": [ { "id": "servers", "type": "server_batch", "provider": "upcloud", "dependencies": [], "config": { "server_configs": [ {"name": "web-01", "plan": "2xCPU-4 GB", "zone": "de-fra1"}, {"name": "web-02", "plan": "2xCPU-4 GB", "zone": "de-fra1"} ] } }, { "id": "kubernetes", "type": "taskserv_batch", "provider": "upcloud", "dependencies": ["servers"], "config": { "taskservs": ["kubernetes", "cilium", "containerd"] } } ] } # Execute batch operation batch_result = await client.execute_batch_operation(batch_config) print(f"Batch operation started: {batch_result['batch_id']}") # Monitor progress while True: status = await client.get_batch_status(batch_result['batch_id']) print(f"Batch status: {status['status']} - {status.get('progress', 0)}%") if status['status'] in ['Completed', 'Failed', 'Cancelled']: break await asyncio.sleep(10) print(f"Batch operation finished: {status['status']}") ``` #### Error Handling with Retries ```python from provisioning_client.exceptions import ( ProvisioningAPIError, AuthenticationError, ValidationError, RateLimitError ) from tenacity import retry, stop_after_attempt, wait_exponential class RobustProvisioningClient(ProvisioningClient): @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10) ) async def create_server_workflow_with_retry(self, **kwargs): try: return await self.create_server_workflow(**kwargs) except RateLimitError as e: print(f"Rate limited, retrying in {e.retry_after} seconds...") await asyncio.sleep(e.retry_after) raise except AuthenticationError: print("Authentication failed, re-authenticating...") await self.authenticate() raise except ValidationError as e: print(f"Validation error: {e}") # Don't retry validation errors raise except ProvisioningAPIError as e: print(f"API error: {e}") raise # Usage async def robust_workflow(): client = RobustProvisioningClient() try: task_id = await client.create_server_workflow_with_retry( infra="production", settings="config.ncl" ) print(f"Workflow created successfully: {task_id}") except Exception as e: print(f"Failed after retries: {e}") ``` ### API Reference #### ProvisioningClient Class ```python class ProvisioningClient: def __init__(self, base_url: str = "http://localhost:9090", auth_url: str = "http://localhost:8081", username: str = None, password: str = None, token: str = None): """Initialize the provisioning client""" async def authenticate(self) -> str: """Authenticate and get JWT token""" def create_server_workflow(self, infra: str, settings: str = "config.ncl", check_mode: bool = False, wait: bool = False) -> str: """Create a server provisioning workflow""" def create_taskserv_workflow(self, operation: str, taskserv: str, infra: str, settings: str = "config.ncl", check_mode: bool = False, wait: bool = False) -> str: """Create a task service workflow""" def get_task_status(self, task_id: str) -> WorkflowTask: """Get the status of a specific task""" def wait_for_task_completion(self, task_id: str, timeout: int = 300, poll_interval: int = 5) -> WorkflowTask: """Wait for a task to complete""" async def connect_websocket(self, event_types: List[str] = None): """Connect to WebSocket for real-time updates""" def on_event(self, event_type: str, handler: Callable): """Register an event handler""" ``` ## JavaScript/TypeScript SDK ### Installation ```bash # npm npm install @provisioning/client # yarn yarn add @provisioning/client # pnpm pnpm add @provisioning/client ``` ### Quick Start ```typescript import { ProvisioningClient } from '@provisioning/client'; async function main() { const client = new ProvisioningClient({ baseUrl: 'http://localhost:9090', authUrl: 'http://localhost:8081', username: 'admin', password: 'your-password' }); try { // Authenticate await client.authenticate(); console.log('Authentication successful'); // Create server workflow const taskId = await client.createServerWorkflow({ infra: 'production', settings: 'prod-settings.ncl' }); console.log(`Server workflow created: ${taskId}`); // Wait for completion const task = await client.waitForTaskCompletion(taskId); console.log(`Task completed with status: ${task.status}`); } catch (error) { console.error('Error:', error.message); } } main(); ``` ### React Integration ```tsx import React, { useState, useEffect } from 'react'; import { ProvisioningClient } from '@provisioning/client'; interface Task { id: string; name: string; status: string; progress?: number; } const WorkflowDashboard: React.FC = () => { const [client] = useState(() => new ProvisioningClient({ baseUrl: process.env.REACT_APP_API_URL, username: process.env.REACT_APP_USERNAME, password: process.env.REACT_APP_PASSWORD })); const [tasks, setTasks] = useState([]); const [connected, setConnected] = useState(false); useEffect(() => { const initClient = async () => { try { await client.authenticate(); // Set up WebSocket event handlers client.on('TaskStatusChanged', (event: any) => { setTasks(prev => prev.map(task => task.id === event.data.task_id ? { ...task, status: event.data.status, progress: event.data.progress } : task )); }); client.on('websocketConnected', () => { setConnected(true); }); client.on('websocketDisconnected', () => { setConnected(false); }); // Connect WebSocket await client.connectWebSocket(['TaskStatusChanged', 'WorkflowProgressUpdate']); // Load initial tasks const initialTasks = await client.listTasks(); setTasks(initialTasks); } catch (error) { console.error('Failed to initialize client:', error); } }; initClient(); return () => { client.disconnectWebSocket(); }; }, [client]); const createServerWorkflow = async () => { try { const taskId = await client.createServerWorkflow({ infra: 'production', settings: 'config.ncl' }); // Add to tasks list setTasks(prev => [...prev, { id: taskId, name: 'Server Creation', status: 'Pending' }]); } catch (error) { console.error('Failed to create workflow:', error); } }; return (

Workflow Dashboard

{connected ? '🟢 Connected' : '🔴 Disconnected'}
{tasks.map(task => (

{task.name}

{task.status} {task.progress && (
{task.progress}%
)}
))}
); }; export default WorkflowDashboard; ``` ### Node.js CLI Tool ```typescript #!/usr/bin/env node import { Command } from 'commander'; import { ProvisioningClient } from '@provisioning/client'; import chalk from 'chalk'; import ora from 'ora'; const program = new Command(); program .name('provisioning-cli') .description('CLI tool for provisioning') .version('1.0.0'); program .command('create-server') .description('Create a server workflow') .requiredOption('-i, --infra ', 'Infrastructure target') .option('-s, --settings ', 'Settings file', 'config.ncl') .option('-c, --check', 'Check mode only') .option('-w, --wait', 'Wait for completion') .action(async (options) => { const client = new ProvisioningClient({ baseUrl: process.env.PROVISIONING_API_URL, username: process.env.PROVISIONING_USERNAME, password: process.env.PROVISIONING_PASSWORD }); const spinner = ora('Authenticating...').start(); try { await client.authenticate(); spinner.text = 'Creating server workflow...'; const taskId = await client.createServerWorkflow({ infra: options.infra, settings: options.settings, check_mode: options.check, wait: false }); spinner.succeed(`Server workflow created: ${chalk.green(taskId)}`); if (options.wait) { spinner.start('Waiting for completion...'); // Set up progress updates client.on('TaskStatusChanged', (event: any) => { if (event.data.task_id === taskId) { spinner.text = `Status: ${event.data.status}`; } }); client.on('WorkflowProgressUpdate', (event: any) => { if (event.data.workflow_id === taskId) { spinner.text = `${event.data.progress}% - ${event.data.current_step}`; } }); await client.connectWebSocket(['TaskStatusChanged', 'WorkflowProgressUpdate']); const task = await client.waitForTaskCompletion(taskId); if (task.status === 'Completed') { spinner.succeed(chalk.green('Workflow completed successfully!')); if (task.output) { console.log(chalk.gray('Output:'), task.output); } } else { spinner.fail(chalk.red(`Workflow failed: ${task.error}`)); process.exit(1); } } } catch (error) { spinner.fail(chalk.red(`Error: ${error.message}`)); process.exit(1); } }); program .command('list-tasks') .description('List all tasks') .option('-s, --status ', 'Filter by status') .action(async (options) => { const client = new ProvisioningClient(); try { await client.authenticate(); const tasks = await client.listTasks(options.status); console.log(chalk.bold('Tasks:')); tasks.forEach(task => { const statusColor = task.status === 'Completed' ? 'green' : task.status === 'Failed' ? 'red' : task.status === 'Running' ? 'yellow' : 'gray'; console.log(` ${task.id} - ${task.name} [${chalk[statusColor](task.status)}]`); }); } catch (error) { console.error(chalk.red(`Error: ${error.message}`)); process.exit(1); } }); program .command('monitor') .description('Monitor workflows in real-time') .action(async () => { const client = new ProvisioningClient(); try { await client.authenticate(); console.log(chalk.bold('🔍 Monitoring workflows...')); console.log(chalk.gray('Press Ctrl+C to stop')); client.on('TaskStatusChanged', (event: any) => { const timestamp = new Date().toLocaleTimeString(); const statusColor = event.data.status === 'Completed' ? 'green' : event.data.status === 'Failed' ? 'red' : event.data.status === 'Running' ? 'yellow' : 'gray'; console.log(`[${chalk.gray(timestamp)}] Task ${event.data.task_id} → ${chalk[statusColor](event.data.status)}`); }); client.on('WorkflowProgressUpdate', (event: any) => { const timestamp = new Date().toLocaleTimeString(); console.log(`[${chalk.gray(timestamp)}] ${event.data.workflow_id}: ${event.data.progress}% - ${event.data.current_step}`); }); await client.connectWebSocket(['TaskStatusChanged', 'WorkflowProgressUpdate']); // Keep the process running process.on('SIGINT', () => { console.log(chalk.yellow('\nStopping monitor...')); client.disconnectWebSocket(); process.exit(0); }); // Keep alive setInterval(() => {}, 1000); } catch (error) { console.error(chalk.red(`Error: ${error.message}`)); process.exit(1); } }); program.parse(); ``` ### API Reference ```typescript interface ProvisioningClientOptions { baseUrl?: string; authUrl?: string; username?: string; password?: string; token?: string; } class ProvisioningClient extends EventEmitter { constructor(options: ProvisioningClientOptions); async authenticate(): Promise; async createServerWorkflow(config: { infra: string; settings?: string; check_mode?: boolean; wait?: boolean; }): Promise; async createTaskservWorkflow(config: { operation: string; taskserv: string; infra: string; settings?: string; check_mode?: boolean; wait?: boolean; }): Promise; async getTaskStatus(taskId: string): Promise; async listTasks(statusFilter?: string): Promise; async waitForTaskCompletion( taskId: string, timeout?: number, pollInterval?: number ): Promise; async connectWebSocket(eventTypes?: string[]): Promise; disconnectWebSocket(): void; async executeBatchOperation(batchConfig: BatchConfig): Promise; async getBatchStatus(batchId: string): Promise; } ``` ## Go SDK ### Installation ```bash go get github.com/provisioning-systems/go-client ``` ### Quick Start ```go package main import ( "context" "fmt" "log" "time" "github.com/provisioning-systems/go-client" ) func main() { // Initialize client client, err := provisioning.NewClient(&provisioning.Config{ BaseURL: "http://localhost:9090", AuthURL: "http://localhost:8081", Username: "admin", Password: "your-password", }) if err != nil { log.Fatalf("Failed to create client: %v", err) } ctx := context.Background() // Authenticate token, err := client.Authenticate(ctx) if err != nil { log.Fatalf("Authentication failed: %v", err) } fmt.Printf("Authenticated with token: %.20s...\n", token) // Create server workflow taskID, err := client.CreateServerWorkflow(ctx, &provisioning.CreateServerRequest{ Infra: "production", Settings: "prod-settings.ncl", Wait: false, }) if err != nil { log.Fatalf("Failed to create workflow: %v", err) } fmt.Printf("Server workflow created: %s\n", taskID) // Wait for completion task, err := client.WaitForTaskCompletion(ctx, taskID, 10*time.Minute) if err != nil { log.Fatalf("Failed to wait for completion: %v", err) } fmt.Printf("Task completed with status: %s\n", task.Status) if task.Status == "Completed" { fmt.Printf("Output: %s\n", task.Output) } else if task.Status == "Failed" { fmt.Printf("Error: %s\n", task.Error) } } ``` ### WebSocket Integration ```go package main import ( "context" "fmt" "log" "os" "os/signal" "github.com/provisioning-systems/go-client" ) func main() { client, err := provisioning.NewClient(&provisioning.Config{ BaseURL: "http://localhost:9090", Username: "admin", Password: "password", }) if err != nil { log.Fatalf("Failed to create client: %v", err) } ctx := context.Background() // Authenticate _, err = client.Authenticate(ctx) if err != nil { log.Fatalf("Authentication failed: %v", err) } // Set up WebSocket connection ws, err := client.ConnectWebSocket(ctx, []string{ "TaskStatusChanged", "WorkflowProgressUpdate", }) if err != nil { log.Fatalf("Failed to connect WebSocket: %v", err) } defer ws.Close() // Handle events go func() { for event := range ws.Events() { switch event.Type { case "TaskStatusChanged": fmt.Printf("Task %s status changed to: %s\n", event.Data["task_id"], event.Data["status"]) case "WorkflowProgressUpdate": fmt.Printf("Workflow progress: %v%% - %s\n", event.Data["progress"], event.Data["current_step"]) } } }() // Wait for interrupt c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c fmt.Println("Shutting down...") } ``` ### HTTP Client with Retry Logic ```go package main import ( "context" "fmt" "time" "github.com/provisioning-systems/go-client" "github.com/cenkalti/backoff/v4" ) type ResilientClient struct { *provisioning.Client } func NewResilientClient(config *provisioning.Config) (*ResilientClient, error) { client, err := provisioning.NewClient(config) if err != nil { return nil, err } return &ResilientClient{Client: client}, nil } func (c *ResilientClient) CreateServerWorkflowWithRetry( ctx context.Context, req *provisioning.CreateServerRequest, ) (string, error) { var taskID string operation := func() error { var err error taskID, err = c.CreateServerWorkflow(ctx, req) // Don't retry validation errors if provisioning.IsValidationError(err) { return backoff.Permanent(err) } return err } exponentialBackoff := backoff.NewExponentialBackOff() exponentialBackoff.MaxElapsedTime = 5 * time.Minute err := backoff.Retry(operation, exponentialBackoff) if err != nil { return "", fmt.Errorf("failed after retries: %w", err) } return taskID, nil } func main() { client, err := NewResilientClient(&provisioning.Config{ BaseURL: "http://localhost:9090", Username: "admin", Password: "password", }) if err != nil { log.Fatalf("Failed to create client: %v", err) } ctx := context.Background() // Authenticate with retry _, err = client.Authenticate(ctx) if err != nil { log.Fatalf("Authentication failed: %v", err) } // Create workflow with retry taskID, err := client.CreateServerWorkflowWithRetry(ctx, &provisioning.CreateServerRequest{ Infra: "production", Settings: "config.ncl", }) if err != nil { log.Fatalf("Failed to create workflow: %v", err) } fmt.Printf("Workflow created successfully: %s\n", taskID) } ``` ## Rust SDK ### Installation Add to your `Cargo.toml`: ```toml [dependencies] provisioning-rs = "2.0.0" tokio = { version = "1.0", features = ["full"] } ``` ### Quick Start ```rust use provisioning_rs::{ProvisioningClient, Config, CreateServerRequest}; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { // Initialize client let config = Config { base_url: "http://localhost:9090".to_string(), auth_url: Some("http://localhost:8081".to_string()), username: Some("admin".to_string()), password: Some("your-password".to_string()), token: None, }; let mut client = ProvisioningClient::new(config); // Authenticate let token = client.authenticate().await?; println!("Authenticated with token: {}...", &token[..20]); // Create server workflow let request = CreateServerRequest { infra: "production".to_string(), settings: Some("prod-settings.ncl".to_string()), check_mode: false, wait: false, }; let task_id = client.create_server_workflow(request).await?; println!("Server workflow created: {}", task_id); // Wait for completion let task = client.wait_for_task_completion(&task_id, std::time::Duration::from_secs(600)).await?; println!("Task completed with status: {:?}", task.status); match task.status { TaskStatus::Completed => { if let Some(output) = task.output { println!("Output: {}", output); } }, TaskStatus::Failed => { if let Some(error) = task.error { println!("Error: {}", error); } }, _ => {} } Ok(()) } ``` ### WebSocket Integration ```rust use provisioning_rs::{ProvisioningClient, Config, WebSocketEvent}; use futures_util::StreamExt; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let config = Config { base_url: "http://localhost:9090".to_string(), username: Some("admin".to_string()), password: Some("password".to_string()), ..Default::default() }; let mut client = ProvisioningClient::new(config); // Authenticate client.authenticate().await?; // Connect WebSocket let mut ws = client.connect_websocket(vec![ "TaskStatusChanged".to_string(), "WorkflowProgressUpdate".to_string(), ]).await?; // Handle events tokio::spawn(async move { while let Some(event) = ws.next().await { match event { Ok(WebSocketEvent::TaskStatusChanged { data }) => { println!("Task {} status changed to: {}", data.task_id, data.status); }, Ok(WebSocketEvent::WorkflowProgressUpdate { data }) => { println!("Workflow progress: {}% - {}", data.progress, data.current_step); }, Ok(WebSocketEvent::SystemHealthUpdate { data }) => { println!("System health: {}", data.overall_status); }, Err(e) => { eprintln!("WebSocket error: {}", e); break; } } } }); // Keep the main thread alive tokio::signal::ctrl_c().await?; println!("Shutting down..."); Ok(()) } ``` ### Batch Operations ```rust use provisioning_rs::{BatchOperationRequest, BatchOperation}; #[tokio::main] async fn main() -> Result<(), Box> { let mut client = ProvisioningClient::new(config); client.authenticate().await?; // Define batch operation let batch_request = BatchOperationRequest { name: "production_deployment".to_string(), version: "1.0.0".to_string(), storage_backend: "surrealdb".to_string(), parallel_limit: 5, rollback_enabled: true, operations: vec![ BatchOperation { id: "servers".to_string(), operation_type: "server_batch".to_string(), provider: "upcloud".to_string(), dependencies: vec![], config: serde_json::json!({ "server_configs": [ {"name": "web-01", "plan": "2xCPU-4 GB", "zone": "de-fra1"}, {"name": "web-02", "plan": "2xCPU-4 GB", "zone": "de-fra1"} ] }), }, BatchOperation { id: "kubernetes".to_string(), operation_type: "taskserv_batch".to_string(), provider: "upcloud".to_string(), dependencies: vec!["servers".to_string()], config: serde_json::json!({ "taskservs": ["kubernetes", "cilium", "containerd"] }), }, ], }; // Execute batch operation let batch_result = client.execute_batch_operation(batch_request).await?; println!("Batch operation started: {}", batch_result.batch_id); // Monitor progress loop { let status = client.get_batch_status(&batch_result.batch_id).await?; println!("Batch status: {} - {}%", status.status, status.progress.unwrap_or(0.0)); match status.status.as_str() { "Completed" | "Failed" | "Cancelled" => break, _ => tokio::time::sleep(std::time::Duration::from_secs(10)).await, } } Ok(()) } ``` ## Best Practices ### Authentication and Security 1. **Token Management**: Store tokens securely and implement automatic refresh 2. **Environment Variables**: Use environment variables for credentials 3. **HTTPS**: Always use HTTPS in production environments 4. **Token Expiration**: Handle token expiration gracefully ### Error Handling 1. **Specific Exceptions**: Handle specific error types appropriately 2. **Retry Logic**: Implement exponential backoff for transient failures 3. **Circuit Breakers**: Use circuit breakers for resilient integrations 4. **Logging**: Log errors with appropriate context ### Performance Optimization 1. **Connection Pooling**: Reuse HTTP connections 2. **Async Operations**: Use asynchronous operations where possible 3. **Batch Operations**: Group related operations for efficiency 4. **Caching**: Cache frequently accessed data appropriately ### WebSocket Connections 1. **Reconnection**: Implement automatic reconnection with backoff 2. **Event Filtering**: Subscribe only to needed event types 3. **Error Handling**: Handle WebSocket errors gracefully 4. **Resource Cleanup**: Properly close WebSocket connections ### Testing 1. **Unit Tests**: Test SDK functionality with mocked responses 2. **Integration Tests**: Test against real API endpoints 3. **Error Scenarios**: Test error handling paths 4. **Load Testing**: Validate performance under load This comprehensive SDK documentation provides developers with everything needed to integrate with provisioning using their preferred programming language, complete with examples, best practices, and detailed API references.