2025-12-11 21:50:42 +00:00
|
|
|
# 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
|
2026-01-08 09:55:37 +00:00
|
|
|
|
2025-12-11 21:50:42 +00:00
|
|
|
- **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
|
2026-01-08 09:55:37 +00:00
|
|
|
|
2025-12-11 21:50:42 +00:00
|
|
|
- **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",
|
2026-01-08 09:55:37 +00:00
|
|
|
settings="prod-settings.ncl",
|
2025-12-11 21:50:42 +00:00
|
|
|
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": [
|
2026-01-08 09:55:37 +00:00
|
|
|
{"name": "web-01", "plan": "2xCPU-4 GB", "zone": "de-fra1"},
|
|
|
|
|
{"name": "web-02", "plan": "2xCPU-4 GB", "zone": "de-fra1"}
|
2025-12-11 21:50:42 +00:00
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"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",
|
2026-01-08 09:55:37 +00:00
|
|
|
settings="config.ncl"
|
2025-12-11 21:50:42 +00:00
|
|
|
)
|
|
|
|
|
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,
|
2026-01-08 09:55:37 +00:00
|
|
|
settings: str = "config.ncl",
|
2025-12-11 21:50:42 +00:00
|
|
|
check_mode: bool = False,
|
|
|
|
|
wait: bool = False) -> str:
|
|
|
|
|
"""Create a server provisioning workflow"""
|
|
|
|
|
|
|
|
|
|
def create_taskserv_workflow(self,
|
|
|
|
|
operation: str,
|
|
|
|
|
taskserv: str,
|
|
|
|
|
infra: str,
|
2026-01-08 09:55:37 +00:00
|
|
|
settings: str = "config.ncl",
|
2025-12-11 21:50:42 +00:00
|
|
|
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',
|
2026-01-08 09:55:37 +00:00
|
|
|
settings: 'prod-settings.ncl'
|
2025-12-11 21:50:42 +00:00
|
|
|
});
|
|
|
|
|
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<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',
|
2026-01-08 09:55:37 +00:00
|
|
|
settings: 'config.ncl'
|
2025-12-11 21:50:42 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
```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 <infra>', 'Infrastructure target')
|
2026-01-08 09:55:37 +00:00
|
|
|
.option('-s, --settings <settings>', 'Settings file', 'config.ncl')
|
2025-12-11 21:50:42 +00:00
|
|
|
.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
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```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",
|
2026-01-08 09:55:37 +00:00
|
|
|
Settings: "prod-settings.ncl",
|
2025-12-11 21:50:42 +00:00
|
|
|
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",
|
2026-01-08 09:55:37 +00:00
|
|
|
Settings: "config.ncl",
|
2025-12-11 21:50:42 +00:00
|
|
|
})
|
|
|
|
|
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<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(),
|
2026-01-08 09:55:37 +00:00
|
|
|
settings: Some("prod-settings.ncl".to_string()),
|
2025-12-11 21:50:42 +00:00
|
|
|
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<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
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
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": [
|
2026-01-08 09:55:37 +00:00
|
|
|
{"name": "web-01", "plan": "2xCPU-4 GB", "zone": "de-fra1"},
|
|
|
|
|
{"name": "web-02", "plan": "2xCPU-4 GB", "zone": "de-fra1"}
|
2025-12-11 21:50:42 +00:00
|
|
|
]
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
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
|
|
|
|
|
|
2026-01-12 04:42:18 +00:00
|
|
|
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.
|