Examples & Workflows
Practical, real-world workflows using the CiderStack SDKs.
CI/CD pipeline runner
Provision ephemeral macOS VMs for CI/CD pipelines — build, test, and tear down automatically.
JavaScript
import { FleetClient, VMState } from "@ciderstack/fleet-sdk";
const client = new FleetClient({
host: process.env.FLEET_HOST!,
apiToken: process.env.FLEET_TOKEN!,
});
async function runCIPipeline() {
// 1. Create a disposable VM from a base image
const vmId = await client.createVM({
name: `ci-${Date.now()}`,
cpuCount: 8,
memoryMB: 16384,
diskGB: 100,
ociImage: "ghcr.io/myorg/macos-ci-base:latest",
});
// 2. Mark as disposable with 2-hour safety TTL
await client.updateVMSettings(vmId, {
intent: "disposable",
ttlSeconds: 7200,
});
// 3. Wait for VM to boot
let vm = await client.getVM(vmId);
while (vm?.state !== VMState.Running) {
await new Promise(r => setTimeout(r, 5000));
vm = await client.getVM(vmId);
}
try {
// 4. Build
const build = await client.execCommand(vmId,
"cd /Users/admin/project && xcodebuild -scheme MyApp build",
{ sshUser: "admin", sshPassword: "password", timeout: 600 },
);
if (!build.success) throw new Error(`Build failed: ${build.stderr}`);
// 5. Test
const test = await client.execCommand(vmId,
"cd /Users/admin/project && xcodebuild test -scheme MyApp",
{ sshUser: "admin", sshPassword: "password", timeout: 600 },
);
console.log(test.stdout);
return test.success;
} finally {
// 6. Tear down
await client.stopVM(vmId);
await client.deleteVM(vmId);
}
}Python
import os
import time
from ciderstack import FleetClient, VMState
client = FleetClient(
os.environ["FLEET_HOST"],
api_token=os.environ["FLEET_TOKEN"],
)
def run_ci_pipeline():
# 1. Create a disposable VM
vm_id = client.create_vm(
name=f"ci-{int(time.time())}",
cpu_count=8,
memory_mb=16384,
disk_gb=100,
oci_image="ghcr.io/myorg/macos-ci-base:latest",
)
# 2. Mark as disposable with 2-hour safety TTL
client.update_vm_settings(vm_id, intent="disposable", ttl_seconds=7200)
# 3. Wait for VM to boot
vm = client.get_vm(vm_id)
while vm is None or vm.state != VMState.RUNNING:
time.sleep(5)
vm = client.get_vm(vm_id)
try:
# 4. Build
build = client.exec_command(
vm_id,
"cd /Users/admin/project && xcodebuild -scheme MyApp build",
ssh_user="admin", ssh_password="password", timeout=600,
)
if not build.success:
raise RuntimeError(f"Build failed: {build.stderr}")
# 5. Test
test = client.exec_command(
vm_id,
"cd /Users/admin/project && xcodebuild test -scheme MyApp",
ssh_user="admin", ssh_password="password", timeout=600,
)
print(test.stdout)
return test.success
finally:
# 6. Tear down
client.stop_vm(vm_id)
client.delete_vm(vm_id)Fleet monitoring
Poll fleet health and print a status report.
JavaScript
import { FleetClient } from "@ciderstack/fleet-sdk";
const client = new FleetClient({
host: "192.168.1.100",
apiToken: "csk_...",
});
async function printFleetStatus() {
const overview = await client.getFleetOverview();
const s = overview.stats;
console.log("=== Fleet Status ===");
console.log(`Nodes: ${s.connectedNodes}/${s.totalNodes}`);
console.log(`VMs: ${s.runningVMs}/${s.totalVMs} running`);
console.log(`CPU: ${s.aggregatedCPUUsage.toFixed(1)}%`);
console.log(`Memory: ${s.aggregatedMemoryUsage.toFixed(1)}%`);
console.log(`Storage: ${s.aggregatedStorageUsage.toFixed(1)}%`);
// Per-node breakdown
const allNodes = [overview.managerNode, ...overview.workerNodes];
for (const node of allNodes) {
const status = node.connectionState === "connected" ? "UP" : "DOWN";
console.log(`[${status}] ${node.name}: ${node.runningVMCount}/${node.vmCount} VMs`);
}
// Recent events
const events = await client.getFleetEvents(5);
console.log("\n=== Recent Events ===");
for (const e of events) {
console.log(`[${e.eventType}] ${e.message}`);
}
}
// Poll every 30 seconds
setInterval(printFleetStatus, 30000);
printFleetStatus();Python
import time
from ciderstack import FleetClient
client = FleetClient("192.168.1.100", api_token="csk_...")
def print_fleet_status():
overview = client.get_fleet_overview()
stats = overview["overview"]["stats"]
print("=== Fleet Status ===")
print(f"Nodes: {stats['connectedNodes']}/{stats['totalNodes']}")
print(f"VMs: {stats['runningVMs']}/{stats['totalVMs']} running")
print(f"CPU: {stats['aggregatedCPUUsage']:.1f}%")
print(f"Memory: {stats['aggregatedMemoryUsage']:.1f}%")
# Per-node breakdown
mgr = overview["overview"]["managerNode"]
workers = overview["overview"]["workerNodes"]
for node in [mgr] + workers:
status = "UP" if node["connectionState"] == "connected" else "DOWN"
print(f"[{status}] {node['name']}: {node['runningVMCount']}/{node['vmCount']} VMs")
# Recent events
events = client.get_fleet_events(limit=5)
print("\n=== Recent Events ===")
for e in events:
print(f"[{e['eventType']}] {e['message']}")
# Poll every 30 seconds
while True:
print_fleet_status()
time.sleep(30)Snapshot-based testing
Run tests in isolation by snapshotting before and restoring after.
JavaScript
import { FleetClient } from "@ciderstack/fleet-sdk";
const client = new FleetClient({ host: "192.168.1.100", apiToken: "csk_..." });
async function runTestInIsolation(vmId: string, testCommand: string) {
// Snapshot the clean state
const snapshot = await client.createSnapshot(vmId, "pre-test", "Clean state");
try {
// Run the test
const result = await client.execCommand(vmId, testCommand, {
sshUser: "admin",
sshPassword: "password",
timeout: 300,
});
console.log(`Test ${result.success ? "PASSED" : "FAILED"}`);
console.log(result.stdout);
return result.success;
} finally {
// Always restore to clean state
await client.stopVM(vmId);
await client.restoreSnapshot(vmId, snapshot.id);
await client.startVM(vmId);
await client.deleteSnapshot(vmId, snapshot.id);
}
}Python
from ciderstack import FleetClient
client = FleetClient("192.168.1.100", api_token="csk_...")
def run_test_in_isolation(vm_id: str, test_command: str) -> bool:
"""Run a test in snapshot isolation, then restore to clean state."""
snapshot = client.create_snapshot(vm_id, "pre-test", "Clean state")
try:
result = client.exec_command(
vm_id, test_command,
ssh_user="admin", ssh_password="password", timeout=300,
)
print(f"Test {'PASSED' if result.success else 'FAILED'}")
print(result.stdout)
return result.success
finally:
client.stop_vm(vm_id)
client.restore_snapshot(vm_id, snapshot.id)
client.start_vm(vm_id)
client.delete_snapshot(vm_id, snapshot.id)Automated snapshot rotation
Keep only the N most recent snapshots per VM.
JavaScript
import { FleetClient } from "@ciderstack/fleet-sdk";
const client = new FleetClient({ host: "192.168.1.100", apiToken: "csk_..." });
async function rotateSnapshots(vmId: string, maxSnapshots = 5) {
const snapshots = await client.listSnapshots(vmId);
// Sort newest first
snapshots.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
// Delete old snapshots beyond the limit
const toDelete = snapshots.slice(maxSnapshots);
for (const s of toDelete) {
console.log(`Deleting: ${s.name} (${s.createdAt.toISOString()})`);
await client.deleteSnapshot(vmId, s.id);
}
console.log(`Kept ${Math.min(snapshots.length, maxSnapshots)}, deleted ${toDelete.length}`);
}
// Daily snapshot + rotate
async function dailySnapshot(vmId: string) {
const today = new Date().toISOString().split("T")[0];
await client.createSnapshot(vmId, `daily-${today}`, "Automated daily snapshot");
await rotateSnapshots(vmId, 7); // Keep 7 days
}Python
from ciderstack import FleetClient
from datetime import date
client = FleetClient("192.168.1.100", api_token="csk_...")
def rotate_snapshots(vm_id: str, max_snapshots: int = 5):
snapshots = client.list_snapshots(vm_id)
# Sort newest first
snapshots.sort(key=lambda s: s.created_at, reverse=True)
# Delete old ones
to_delete = snapshots[max_snapshots:]
for s in to_delete:
print(f"Deleting: {s.name} ({s.created_at.isoformat()})")
client.delete_snapshot(vm_id, s.id)
print(f"Kept {min(len(snapshots), max_snapshots)}, deleted {len(to_delete)}")
def daily_snapshot(vm_id: str):
today = date.today().isoformat()
client.create_snapshot(vm_id, f"daily-{today}", "Automated daily snapshot")
rotate_snapshots(vm_id, max_snapshots=7)Template-based provisioning
Create standardized environments from templates.
JavaScript
import { FleetClient } from "@ciderstack/fleet-sdk";
const client = new FleetClient({ host: "192.168.1.100", apiToken: "csk_..." });
// Provision a dev VM from a template
async function provisionDevVM(developerName: string) {
const template = await client.getTemplateByName("Developer Workstation");
if (!template) throw new Error("Template not found");
const vmId = await client.createVM({
name: `dev-${developerName}`,
cpuCount: template.defaultCPU,
memoryMB: template.defaultMemoryMB,
diskGB: template.defaultDiskGB,
ociImage: template.id,
});
// Configure as persistent
await client.updateVMSettings(vmId, { intent: "persistent" });
// Add shared folder
await client.addSharedFolder({
vmId,
name: "projects",
hostPath: `/Users/${developerName}/Projects`,
readOnly: false,
});
console.log(`Provisioned ${developerName}'s VM: ${vmId}`);
return vmId;
}
// Create a golden template from a configured VM
async function createGoldenTemplate(vmId: string, version: string) {
await client.stopVM(vmId);
const template = await client.createTemplateFromVM({
vmId,
name: `Developer Workstation v${version}`,
description: `Standard dev environment with Xcode and Homebrew (v${version})`,
category: "dev",
keepOriginalVM: true,
});
console.log(`Template created: ${template.name} (${template.id})`);
return template;
}Python
from ciderstack import FleetClient
client = FleetClient("192.168.1.100", api_token="csk_...")
def provision_dev_vm(developer_name: str) -> str:
"""Provision a dev VM from the standard template."""
template = client.get_template_by_name("Developer Workstation")
if not template:
raise ValueError("Template not found")
vm_id = client.create_vm(
name=f"dev-{developer_name}",
cpu_count=template.default_cpu,
memory_mb=template.default_memory_mb,
disk_gb=template.default_disk_gb,
oci_image=template.id,
)
client.update_vm_settings(vm_id, intent="persistent")
client.add_shared_folder(
vm_id=vm_id,
name="projects",
host_path=f"/Users/{developer_name}/Projects",
read_only=False,
)
print(f"Provisioned {developer_name}'s VM: {vm_id}")
return vm_id
def create_golden_template(vm_id: str, version: str):
"""Create a new golden template from a configured VM."""
client.stop_vm(vm_id)
template = client.create_template_from_vm(
vm_id=vm_id,
name=f"Developer Workstation v{version}",
description=f"Standard dev env with Xcode and Homebrew (v{version})",
category="dev",
keep_original_vm=True,
)
print(f"Template created: {template.name} ({template.id})")
return templateRetry with backoff
Handle transient network errors gracefully.
JavaScript
import { FleetClient, FleetError } from "@ciderstack/fleet-sdk";
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelayMs = 1000,
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries) throw error;
// Don't retry auth or client errors
if (error instanceof FleetError && error.response) throw error;
// Network/timeout — retry with exponential backoff
const delay = baseDelayMs * Math.pow(2, attempt);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error("Unreachable");
}
// Usage
const vms = await withRetry(() => client.listVMs());Python
import time
from ciderstack import FleetClient, FleetError
def with_retry(fn, max_retries=3, base_delay=1.0):
for attempt in range(max_retries + 1):
try:
return fn()
except FleetError as e:
if e.response:
raise # Don't retry auth/client errors
if attempt == max_retries:
raise
time.sleep(base_delay * (2 ** attempt))
except Exception:
if attempt == max_retries:
raise
time.sleep(base_delay * (2 ** attempt))
# Usage
vms = with_retry(lambda: client.list_vms())Ephemeral VM cleanup
Periodically clean up VMs with expired TTLs.
JavaScript
import { FleetClient } from "@ciderstack/fleet-sdk";
const client = new FleetClient({ host: "192.168.1.100", apiToken: "csk_..." });
// Run every 5 minutes
setInterval(async () => {
const cleaned = await client.cleanupVMs();
if (cleaned > 0) {
console.log(`Cleaned up ${cleaned} expired VMs`);
}
}, 5 * 60 * 1000);Python
import time
from ciderstack import FleetClient
client = FleetClient("192.168.1.100", api_token="csk_...")
# Run every 5 minutes
while True:
cleaned = client.cleanup_vms()
if cleaned > 0:
print(f"Cleaned up {cleaned} expired VMs")
time.sleep(300)See also
- JavaScript / TypeScript SDK — Full JS reference
- Python SDK — Full Python reference
- Local GitHub runners — Self-hosted runners on macOS VMs
- CLI Reference — Terminal-based automation