eryph
by

.NET SDK

The eryph .NET SDK provides a comprehensive client library for interacting with the eryph compute API. This guide covers installation, authentication, and common operations.

Overview

The eryph .NET SDK consists of two main packages:

  • Eryph.ComputeClient: Contains the generated API client and models for all eryph resources
  • Eryph.ClientRuntime: Provides authentication, configuration support, and credential discovery

The SDK supports automatic credential and endpoint discovery, making it easy to connect to your eryph installation whether you're using eryph-zero, a local installation, or a configured client.

Installation

Install both SDK packages using NuGet:

# Using Package Manager Console
Install-Package Eryph.ComputeClient
Install-Package Eryph.ClientRuntime

# Using .NET CLI
dotnet add package Eryph.ComputeClient
dotnet add package Eryph.ClientRuntime

Getting Started

Basic Client Setup

The SDK provides automatic discovery of credentials and endpoints:

using Eryph.ClientRuntime.Configuration;
using Eryph.ComputeClient;

// Automatic credential lookup (tries default, zero, local in order)
var lookup = new ClientCredentialsLookup();
var credentials = lookup.FindCredentials();

// Automatic endpoint discovery based on credentials
var endpointLookup = new EndpointLookup();
var computeEndpoint = endpointLookup.GetEndpoint("compute", credentials.Configuration);

// Create client options
var options = new EryphComputeClientOptions(credentials)
{
    Diagnostics =
    {
        IsDistributedTracingEnabled = true,
        IsLoggingEnabled = true
    }
};

// Create the client factory
var factory = new ComputeClientsFactory(options, computeEndpoint);

Credential Discovery

The SDK automatically discovers credentials in the following order:

  1. Default credentials: From stored configuration files
  2. Zero credentials (Windows only): From eryph-zero installation
  3. Local credentials: From local eryph installation
  4. System client: Requires admin/root privileges

For eryph-zero installations on Windows, the SDK can automatically use the system client when running with administrator privileges.

Working with Catlets

Catlets are the primary compute resource in eryph. Here's how to manage them:

Creating a Catlet

using Eryph.ConfigModel.Catlets;
using Eryph.ConfigModel.Json;

var catletsClient = factory.CreateCatletsClient();

// Define catlet configuration
var config = new CatletConfig
{
    Name = "my-test-vm",
    Project = "default",
    Parent = "dbosoft/winsrv2022-standard/starer", // Windows Server 2022 base image
    Cpu = new CatletCpuConfig { Count = 2 },
    Memory = new CatletMemoryConfig { Startup = 4096 }, // 4GB RAM
    Drives = new[]
    {
        new CatletDriveConfig
        {
            Name = "system",
            Size = 50 // 50GB system drive
        }
    }
};

// Serialize configuration and create request
var serializedConfig = CatletConfigJsonSerializer.SerializeToElement(config);
var request = new NewCatletRequest(serializedConfig)
{
    CorrelationId = Guid.NewGuid()
};

// Create catlet (returns an Operation)
var operation = catletsClient.Create(request);
Console.WriteLine($"Creation started - Operation ID: {operation.Id}");

Listing Catlets

// List all catlets
var catlets = catletsClient.List();
foreach (var catlet in catlets)
{
    Console.WriteLine($"Catlet: {catlet.Name} (ID: {catlet.Id}) - Status: {catlet.Status}");
}

// List catlets in a specific project
var projectCatlets = catletsClient.List(projectId: "my-project");

Getting Catlet Details

// Get a single catlet by ID
var catlet = catletsClient.Get("catlet-id");
Console.WriteLine($"Name: {catlet.Name}");
Console.WriteLine($"Status: {catlet.Status}");
Console.WriteLine($"Project: {catlet.ProjectName}");

// Get catlet configuration
var config = catletsClient.GetConfig("catlet-id");
var deserializedConfig = CatletConfigJsonSerializer.Deserialize(config.ToString());

Starting and Stopping Catlets

// Start a catlet
var startOperation = catletsClient.Start("catlet-id");

// Stop a catlet with different modes
var stopRequest = new StopCatletRequestBody
{
    StopMode = CatletStopMode.Shutdown, // Graceful shutdown
    CorrelationId = Guid.NewGuid()
};
var stopOperation = catletsClient.Stop("catlet-id", stopRequest);

// Other stop modes:
// - CatletStopMode.PowerOff: Immediate power off
// - CatletStopMode.SaveState: Save VM state to disk

Getting Catlet Network Information

// Get IP addresses assigned to a catlet
var ipAddresses = catletsClient.GetIp("catlet-id");
foreach (var network in ipAddresses.Value.Networks)
{
    Console.WriteLine($"Network: {network.Name}");
    foreach (var ip in network.IpAddresses)
    {
        Console.WriteLine($"  IP: {ip}");
    }
}

Deleting a Catlet

// Delete a catlet
var deleteResponse = catletsClient.Delete("catlet-id");
var deleteOperation = deleteResponse.Value;

Handling Operations

Most API calls that modify resources return an Operation object representing a long-running task. Operations should be monitored until completion.

Monitoring Operation Progress

async Task<Operation> WaitForOperationAsync(
    ComputeClientsFactory factory, 
    string operationId,
    CancellationToken cancellationToken = default)
{
    var operationsClient = factory.CreateOperationsClient();
    
    while (!cancellationToken.IsCancellationRequested)
    {
        var operation = operationsClient.Get(operationId);
        
        switch (operation.Status)
        {
            case OperationStatus.Completed:
                Console.WriteLine("Operation completed successfully!");
                return operation;
                
            case OperationStatus.Failed:
                Console.WriteLine($"Operation failed: {operation.StatusMessage}");
                throw new Exception($"Operation failed: {operation.StatusMessage}");
                
            case OperationStatus.Queued:
            case OperationStatus.Running:
                Console.WriteLine($"Operation {operation.Status}...");
                break;
        }
        
        await Task.Delay(1000, cancellationToken);
    }
    
    throw new OperationCanceledException();
}

Getting Detailed Operation Information

// Get operation with logs, tasks, and resources
var detailedOperation = operationsClient.Get(
    operationId, 
    expand: "logs,tasks,resources"
);

// Display operation logs
foreach (var logEntry in detailedOperation.LogEntries)
{
    Console.WriteLine($"[{logEntry.Timestamp}] {logEntry.Message}");
}

// Check task progress
foreach (var task in detailedOperation.Tasks)
{
    Console.WriteLine($"Task '{task.Name}': {task.Status}");
    if (!string.IsNullOrEmpty(task.StatusMessage))
    {
        Console.WriteLine($"  Message: {task.StatusMessage}");
    }
}

// Get created resources
foreach (var resource in detailedOperation.Resources)
{
    Console.WriteLine($"Created {resource.ResourceType}: {resource.ResourceId}");
}

Working with Other Resources

Projects

var projectsClient = factory.CreateProjectsClient();

// List all projects
var projects = projectsClient.List();
foreach (var project in projects)
{
    Console.WriteLine($"Project: {project.Name} (ID: {project.Id})");
}

// Get a specific project
var project = projectsClient.Get("project-id");

Virtual Networks

var networksClient = factory.CreateVirtualNetworksClient();

// List networks
var networks = networksClient.List();
foreach (var network in networks)
{
    Console.WriteLine($"Network: {network.Name} - Subnets: {network.Subnets.Count}");
}

Virtual Disks

var disksClient = factory.CreateVirtualDisksClient();

// List all disks
var disks = disksClient.List();
foreach (var disk in disks)
{
    Console.WriteLine($"Disk: {disk.Name} - Size: {disk.SizeBytes / 1024 / 1024 / 1024}GB");
}

Gene Pool (VM Templates)

var genesClient = factory.CreateGenesClient();

// List available genes (VM templates)
var genes = genesClient.List();
foreach (var gene in genes)
{
    Console.WriteLine($"Gene: {gene.Name}");
    Console.WriteLine($"  Geneset: {gene.GeneSet}");
    Console.WriteLine($"  Architecture: {gene.Architecture}");
}

Error Handling

The SDK uses standard .NET exception handling with RequestFailedException for API errors:

using Azure;

try
{
    var catlet = catletsClient.Get("invalid-id");
}
catch (RequestFailedException ex)
{
    Console.WriteLine($"API Error {ex.Status}: {ex.Message}");
    
    switch (ex.Status)
    {
        case 404:
            Console.WriteLine("Resource not found");
            break;
        case 401:
            Console.WriteLine("Authentication failed - check credentials");
            break;
        case 403:
            Console.WriteLine("Access denied - insufficient permissions");
            break;
        case 400:
            Console.WriteLine("Invalid request - check parameters");
            break;
        default:
            Console.WriteLine("Unexpected error occurred");
            break;
    }
}

Advanced Configuration

Manual Credential Configuration

// Manually specify credentials instead of auto-discovery
var credentials = new ClientCredentials(
    clientId: "your-client-id",
    privateKey: "your-private-key",
    identityEndpoint: new Uri("https://identity.eryph.local"),
    configuration: "default"
);

var options = new EryphComputeClientOptions(credentials);

Manual Endpoint Configuration

// Manually specify endpoints
var computeEndpoint = new Uri("https://api.eryph.local/compute");
var factory = new ComputeClientsFactory(options, computeEndpoint);

Working with Different Configurations

// Use specific configuration (default, zero, or local)
var credentials = lookup.FindCredentials("zero"); // For eryph-zero
var endpoint = endpointLookup.GetEndpoint("compute", "zero");

Complete Example

Here's a complete example that creates a catlet and waits for it to be ready:

using System;
using System.Threading.Tasks;
using Azure;
using Eryph.ClientRuntime.Configuration;
using Eryph.ComputeClient;
using Eryph.ConfigModel.Catlets;
using Eryph.ConfigModel.Json;

class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            // Setup client
            var credentials = new ClientCredentialsLookup().FindCredentials();
            var endpoint = new EndpointLookup().GetEndpoint("compute", credentials.Configuration);
            var options = new EryphComputeClientOptions(credentials);
            var factory = new ComputeClientsFactory(options, endpoint);
            
            // Create catlet configuration
            var config = new CatletConfig
            {
                Name = "demo-catlet",
                Project = "default",
                Parent = "dbosoft/ubuntu-22.04/latest",
                Cpu = new CatletCpuConfig { Count = 2 },
                Memory = new CatletMemoryConfig { Startup = 2048 },
                NetworkAdapters = new[]
                {
                    new CatletNetworkAdapterConfig
                    {
                        Name = "eth0",
                        MacAddress = "00:15:5d:00:00:01"
                    }
                }
            };
            
            // Create catlet
            var catletsClient = factory.CreateCatletsClient();
            var createRequest = new NewCatletRequest(
                CatletConfigJsonSerializer.SerializeToElement(config)
            );
            
            Console.WriteLine("Creating catlet...");
            var operation = catletsClient.Create(createRequest);
            
            // Wait for completion
            var completedOp = await WaitForOperationAsync(factory, operation.Id);
            
            // Get the created catlet ID from operation resources
            var catletResource = completedOp.Resources.FirstOrDefault(
                r => r.ResourceType == "Catlet"
            );
            
            if (catletResource != null)
            {
                var catlet = catletsClient.Get(catletResource.ResourceId);
                Console.WriteLine($"Catlet created successfully!");
                Console.WriteLine($"ID: {catlet.Id}");
                Console.WriteLine($"Name: {catlet.Name}");
                Console.WriteLine($"Status: {catlet.Status}");
                
                // Start the catlet
                Console.WriteLine("\nStarting catlet...");
                var startOp = catletsClient.Start(catlet.Id);
                await WaitForOperationAsync(factory, startOp.Id);
                
                // Get IP addresses
                var ips = catletsClient.GetIp(catlet.Id);
                Console.WriteLine("\nIP Addresses:");
                foreach (var network in ips.Value.Networks)
                {
                    foreach (var ip in network.IpAddresses)
                    {
                        Console.WriteLine($"  {network.Name}: {ip}");
                    }
                }
            }
        }
        catch (RequestFailedException ex)
        {
            Console.WriteLine($"API Error: {ex.Status} - {ex.Message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
    
    static async Task<Operation> WaitForOperationAsync(
        ComputeClientsFactory factory,
        string operationId)
    {
        var operationsClient = factory.CreateOperationsClient();
        
        while (true)
        {
            var operation = operationsClient.Get(operationId);
            
            if (operation.Status == OperationStatus.Completed)
                return operation;
                
            if (operation.Status == OperationStatus.Failed)
                throw new Exception($"Operation failed: {operation.StatusMessage}");
                
            await Task.Delay(1000);
        }
    }
}

Best Practices

  1. Always handle operations: Create, update, and delete operations are asynchronous. Always monitor them until completion.

  2. Use correlation IDs: Include correlation IDs in requests for better tracking and debugging:

    var request = new NewCatletRequest(config)
    {
        CorrelationId = Guid.NewGuid()
    };
    
  3. Check API compatibility: Verify the API version to ensure compatibility:

    var versionClient = factory.CreateVersionClient();
    var version = versionClient.Get();
    Console.WriteLine($"API Version: {version.Version}");
    
  4. Handle pagination: List operations return Pageable<T> collections that automatically handle pagination:

    var allCatlets = catletsClient.List();
    foreach (var catlet in allCatlets) // Automatically fetches next pages
    {
        // Process catlet
    }
    
  5. Use appropriate scopes: Some operations may require specific authentication scopes. The SDK handles this automatically when using credential discovery.

  6. Resource cleanup: Always clean up resources when done:

    // Stop and delete catlet when finished
    catletsClient.Stop(catletId, new StopCatletRequestBody 
    { 
        StopMode = CatletStopMode.PowerOff 
    });
    catletsClient.Delete(catletId);
    

Next Steps