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
# Install from PyPI
pip install provisioning-client
# Or install development version
pip install git+https://github.com/provisioning-systems/python-client.git
Quick Start
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.k",
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
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
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-4GB", "zone": "de-fra1"},
{"name": "web-02", "plan": "2xCPU-4GB", "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
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.k"
)
print(f"Workflow created successfully: {task_id}")
except Exception as e:
print(f"Failed after retries: {e}")
API Reference
ProvisioningClient Class
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.k",
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.k",
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
# npm
npm install @provisioning/client
# yarn
yarn add @provisioning/client
# pnpm
pnpm add @provisioning/client
Quick Start
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.k'
});
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
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<Task[]>([]);
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.k'
});
// Add to tasks list
setTasks(prev => [...prev, {
id: taskId,
name: 'Server Creation',
status: 'Pending'
}]);
} catch (error) {
console.error('Failed to create workflow:', error);
}
};
return (
<div className="workflow-dashboard">
<div className="header">
<h1>Workflow Dashboard</h1>
<div className={`connection-status ${connected ? 'connected' : 'disconnected'}`}>
{connected ? '🟢 Connected' : '🔴 Disconnected'}
</div>
</div>
<div className="controls">
<button onClick={createServerWorkflow}>
Create Server Workflow
</button>
</div>
<div className="tasks">
{tasks.map(task => (
<div key={task.id} className="task-card">
<h3>{task.name}</h3>
<div className="task-status">
<span className={`status ${task.status.toLowerCase()}`}>
{task.status}
</span>
{task.progress && (
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${task.progress}%` }}
/>
<span className="progress-text">{task.progress}%</span>
</div>
)}
</div>
</div>
))}
</div>
</div>
);
};
export default WorkflowDashboard;
Node.js CLI Tool
#!/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 <infra>', 'Infrastructure target')
.option('-s, --settings <settings>', 'Settings file', 'config.k')
.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 <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
interface ProvisioningClientOptions {
baseUrl?: string;
authUrl?: string;
username?: string;
password?: string;
token?: string;
}
class ProvisioningClient extends EventEmitter {
constructor(options: ProvisioningClientOptions);
async authenticate(): Promise<string>;
async createServerWorkflow(config: {
infra: string;
settings?: string;
check_mode?: boolean;
wait?: boolean;
}): Promise<string>;
async createTaskservWorkflow(config: {
operation: string;
taskserv: string;
infra: string;
settings?: string;
check_mode?: boolean;
wait?: boolean;
}): Promise<string>;
async getTaskStatus(taskId: string): Promise<Task>;
async listTasks(statusFilter?: string): Promise<Task[]>;
async waitForTaskCompletion(
taskId: string,
timeout?: number,
pollInterval?: number
): Promise<Task>;
async connectWebSocket(eventTypes?: string[]): Promise<void>;
disconnectWebSocket(): void;
async executeBatchOperation(batchConfig: BatchConfig): Promise<any>;
async getBatchStatus(batchId: string): Promise<any>;
}
Go SDK
Installation
go get github.com/provisioning-systems/go-client
Quick Start
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.k",
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
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
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.k",
})
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:
[dependencies]
provisioning-rs = "2.0.0"
tokio = { version = "1.0", features = ["full"] }
Quick Start
use provisioning_rs::{ProvisioningClient, Config, CreateServerRequest};
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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.k".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
use provisioning_rs::{ProvisioningClient, Config, WebSocketEvent};
use futures_util::StreamExt;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
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
use provisioning_rs::{BatchOperationRequest, BatchOperation};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
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-4GB", "zone": "de-fra1"},
{"name": "web-02", "plan": "2xCPU-4GB", "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
- Token Management: Store tokens securely and implement automatic refresh
- Environment Variables: Use environment variables for credentials
- HTTPS: Always use HTTPS in production environments
- Token Expiration: Handle token expiration gracefully
Error Handling
- Specific Exceptions: Handle specific error types appropriately
- Retry Logic: Implement exponential backoff for transient failures
- Circuit Breakers: Use circuit breakers for resilient integrations
- Logging: Log errors with appropriate context
Performance Optimization
- Connection Pooling: Reuse HTTP connections
- Async Operations: Use asynchronous operations where possible
- Batch Operations: Group related operations for efficiency
- Caching: Cache frequently accessed data appropriately
WebSocket Connections
- Reconnection: Implement automatic reconnection with backoff
- Event Filtering: Subscribe only to needed event types
- Error Handling: Handle WebSocket errors gracefully
- Resource Cleanup: Properly close WebSocket connections
Testing
- Unit Tests: Test SDK functionality with mocked responses
- Integration Tests: Test against real API endpoints
- Error Scenarios: Test error handling paths
- 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.