public inbox for kdevops@lists.linux.dev
 help / color / mirror / Atom feed
From: Luis Chamberlain <mcgrof@kernel.org>
To: Chuck Lever <cel@kernel.org>, Daniel Gomez <da.gomez@kruces.com>,
	kdevops@lists.linux.dev
Cc: Luis Chamberlain <mcgrof@kernel.org>, Your Name <email@example.com>
Subject: [PATCH v3 03/10] scripts: add Lambda Labs testing and debugging utilities
Date: Sat, 30 Aug 2025 20:59:57 -0700	[thread overview]
Message-ID: <20250831040004.2159779-4-mcgrof@kernel.org> (raw)
In-Reply-To: <20250831040004.2159779-1-mcgrof@kernel.org>

Add comprehensive CLI tools for Lambda Labs development and testing:
- lambda-cli: Full-featured CLI tool for Lambda Labs operations
  - Instance type listing and filtering
  - Region management with availability info
  - Pricing information and cost analysis
  - Smart instance/region selection algorithms
  - Availability checking for instance/region combinations
  - Kconfig generation for development workflow
- cloud_list_all.sh: Multi-provider instance listing utility
- docs/lambda-cli.1: Complete man page documentation

The lambda-cli provides AWS-style command interface for:
- Debugging API connectivity and authentication
- Testing dynamic configuration generation
- Manual instance and region selection
- Development workflow automation
- Cost analysis and optimization

These tools enable efficient development, testing, and troubleshooting
of Lambda Labs integration.

Generated-by: Claude AI
Signed-off-by: Your Name <email@example.com>
---
 docs/lambda-cli.1         | 245 +++++++++++++++
 scripts/cloud_list_all.sh | 152 +++++++++
 scripts/lambda-cli        | 639 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 1036 insertions(+)
 create mode 100644 docs/lambda-cli.1
 create mode 100755 scripts/cloud_list_all.sh
 create mode 100755 scripts/lambda-cli

diff --git a/docs/lambda-cli.1 b/docs/lambda-cli.1
new file mode 100644
index 0000000..dbb513e
--- /dev/null
+++ b/docs/lambda-cli.1
@@ -0,0 +1,245 @@
+.\" Manpage for lambda-cli
+.\" Contact mcgrof@kernel.org to correct errors or typos.
+.TH LAMBDA-CLI 1 "August 2025" "kdevops 5.0.2" "Lambda Labs CLI Manual"
+.SH NAME
+lambda-cli \- Lambda Labs cloud management CLI for kdevops
+.SH SYNOPSIS
+.B lambda-cli
+[\fB\-h\fR]
+[\fB\-\-output\fR \fIFORMAT\fR]
+\fICOMMAND\fR
+[\fIARGS\fR]
+.SH DESCRIPTION
+.B lambda-cli
+is a structured command-line interface tool for managing Lambda Labs cloud
+resources within the kdevops framework. It provides access to Lambda Labs
+cloud provider functionality for dynamic configuration generation, resource
+management, and cost optimization.
+
+The tool mimics AWS CLI patterns to provide a consistent and scalable
+interface that can be extended for other cloud providers.
+.SH OPTIONS
+.TP
+.BR \-h ", " \-\-help
+Show help message and exit
+.TP
+.BR \-o " " \fIFORMAT\fR ", " \-\-output " " \fIFORMAT\fR
+Output format. Valid options are:
+.RS
+.TP
+.B json
+Machine-readable JSON format (default for scripting)
+.TP
+.B text
+Human-readable table format (default for interactive use)
+.RE
+.SH COMMANDS
+.SS instance-types
+Manage Lambda Labs instance types
+.TP
+.B instance-types list
+[\fB\-\-available\-only\fR]
+[\fB\-\-region\fR \fIREGION\fR]
+.RS
+List all instance types. Use \fB\-\-available\-only\fR to show only instances
+with current capacity. Use \fB\-\-region\fR to filter by specific region.
+.RE
+.TP
+.B instance-types get-cheapest
+[\fB\-\-region\fR \fIREGION\fR]
+[\fB\-\-min\-gpus\fR \fIN\fR]
+.RS
+Find the cheapest available instance. Optionally filter by region or
+minimum number of GPUs required.
+.RE
+.SS regions
+Manage Lambda Labs regions
+.TP
+.B regions list
+[\fB\-\-with\-availability\fR]
+.RS
+List all Lambda Labs regions. Use \fB\-\-with\-availability\fR to include
+the count of available instance types in each region.
+.RE
+.SS pricing
+Get pricing information for Lambda Labs instances
+.TP
+.B pricing list
+[\fB\-\-instance\-type\fR \fITYPE\fR]
+.RS
+List pricing for all instance types, or for a specific instance type.
+Shows hourly, daily, and monthly costs.
+.RE
+.SS smart-select
+Intelligent instance and region selection
+.TP
+.B smart-select
+[\fB\-\-mode\fR \fIMODE\fR]
+.RS
+Automatically select optimal instance and region configuration.
+.RE
+.RS
+.TP
+\fIMODE\fR options:
+.TP
+.B cheapest
+Select the globally cheapest available instance and best region (default)
+.TP
+.B closest
+Select based on geographic proximity (not yet implemented)
+.TP
+.B balanced
+Balance between cost and proximity
+.RE
+.SS generate-kconfig
+Generate dynamic Kconfig files for Lambda Labs
+.TP
+.B generate-kconfig
+[\fB\-\-output\-dir\fR \fIDIR\fR]
+.RS
+Generate Kconfig.compute.generated and Kconfig.location.generated files
+based on current Lambda Labs API data. Default output directory is
+terraform/lambdalabs/kconfigs.
+.RE
+.SH ENVIRONMENT
+.TP
+.B LAMBDALABS_API_KEY
+Lambda Labs API key for authentication. If not set, the tool will attempt
+to read credentials from ~/.lambdalabs/credentials.
+.SH FILES
+.TP
+.I ~/.lambdalabs/credentials
+Lambda Labs credentials file containing API key
+.TP
+.I terraform/lambdalabs/kconfigs/Kconfig.compute.generated
+Dynamically generated Kconfig file for instance types
+.TP
+.I terraform/lambdalabs/kconfigs/Kconfig.location.generated
+Dynamically generated Kconfig file for regions
+.SH EXAMPLES
+.SS Basic Usage
+.TP
+List all available instances:
+.B lambda-cli instance-types list --available-only
+.TP
+Get pricing information:
+.B lambda-cli pricing list
+.TP
+Find cheapest instance:
+.B lambda-cli instance-types get-cheapest
+.SS JSON Output
+.TP
+Get regions in JSON format:
+.B lambda-cli --output json regions list
+.TP
+Smart selection with JSON output:
+.B lambda-cli -o json smart-select --mode cheapest
+.SS Filtering
+.TP
+List instances available in specific region:
+.B lambda-cli instance-types list --region us-west-1
+.TP
+Find cheapest instance with at least 2 GPUs:
+.B lambda-cli instance-types get-cheapest --min-gpus 2
+.SS Kconfig Generation
+.TP
+Generate dynamic Kconfig files:
+.B lambda-cli generate-kconfig
+.TP
+Generate to custom directory:
+.B lambda-cli generate-kconfig --output-dir /tmp/kconfigs
+.SH INTEGRATION WITH KDEVOPS
+.SS Makefile Integration
+The lambda-cli tool can be integrated into kdevops Makefiles:
+.PP
+.RS
+.nf
+LAMBDA_CLI := $(TOPDIR_PATH)/scripts/lambda-cli
+
+lambda-list-instances:
+    @$(LAMBDA_CLI) instance-types list --available-only
+
+lambda-smart-select:
+    @$(LAMBDA_CLI) smart-select --mode cheapest
+.fi
+.RE
+.SS Kconfig Integration
+Use lambda-cli in Kconfig shell commands:
+.PP
+.RS
+.nf
+config TERRAFORM_LAMBDALABS_REGION
+    string
+    default $(shell, scripts/lambda-cli smart-select \\
+             --mode cheapest -o json | \\
+             python3 -c "import sys, json; \\
+             print(json.load(sys.stdin).get('region'))")
+.fi
+.RE
+.SS Ansible Integration
+Call lambda-cli from Ansible playbooks:
+.PP
+.RS
+.nf
+- name: Get cheapest Lambda Labs instance
+  command: scripts/lambda-cli instance-types \\
+           get-cheapest --output json
+  register: cheapest_instance
+  delegate_to: localhost
+.fi
+.RE
+.SH EXIT STATUS
+.TP
+.B 0
+Successful execution
+.TP
+.B 1
+General error (invalid arguments, API failure, etc.)
+.SH DIAGNOSTICS
+The lambda-cli tool provides detailed error messages when operations fail.
+Common issues include:
+.TP
+.B "No API key found"
+Set LAMBDALABS_API_KEY environment variable or configure ~/.lambdalabs/credentials
+.TP
+.B "No available instances matching criteria"
+No instances have current capacity matching the specified filters
+.TP
+.B "API request failed"
+Network error or invalid API key
+.SH NOTES
+.SS Caching
+The underlying Lambda Labs API library may cache responses for performance.
+Cache duration is typically 15 minutes for pricing data.
+.SS Fallback Behavior
+When API access fails, lambda-cli will attempt to use sensible defaults:
+.RS
+.IP \(bu 2
+Default instance type: gpu_1x_a10
+.IP \(bu 2
+Default region: us-west-1
+.IP \(bu 2
+Static Kconfig with minimal options
+.RE
+.SS Rate Limiting
+Be aware of Lambda Labs API rate limits when using lambda-cli in automated
+scripts. Consider adding delays between requests in tight loops.
+.SH SEE ALSO
+.BR opentofu (1),
+.PP
+Full documentation at: <https://github.com/linux-kdevops/kdevops>
+.br
+Lambda Labs documentation: <https://docs.lambdalabs.com/cloud/api>
+.SH BUGS
+Report bugs to: <https://github.com/linux-kdevops/kdevops/issues>
+.SH AUTHOR
+Written by the kdevops contributors.
+.PP
+Lambda-cli tool generated by Claude AI.
+.SH COPYRIGHT
+Copyright \(co 2025 Luis Chamberlain <mcgrof@kernel.org>
+.br
+License: MIT
+.br
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
diff --git a/scripts/cloud_list_all.sh b/scripts/cloud_list_all.sh
new file mode 100755
index 0000000..90bdd2d
--- /dev/null
+++ b/scripts/cloud_list_all.sh
@@ -0,0 +1,152 @@
+#!/bin/bash
+# SPDX-License-Identifier: MIT
+# List all cloud instances across supported providers
+# Currently supports: Lambda Labs
+
+set -e
+
+PROVIDER=""
+
+# Detect which cloud provider is configured
+if [ -f .config ]; then
+    if grep -q "CONFIG_TERRAFORM_LAMBDALABS=y" .config 2>/dev/null; then
+        PROVIDER="lambdalabs"
+    elif grep -q "CONFIG_TERRAFORM_AWS=y" .config 2>/dev/null; then
+        PROVIDER="aws"
+    elif grep -q "CONFIG_TERRAFORM_GCE=y" .config 2>/dev/null; then
+        PROVIDER="gce"
+    elif grep -q "CONFIG_TERRAFORM_AZURE=y" .config 2>/dev/null; then
+        PROVIDER="azure"
+    elif grep -q "CONFIG_TERRAFORM_OCI=y" .config 2>/dev/null; then
+        PROVIDER="oci"
+    fi
+fi
+
+if [ -z "$PROVIDER" ]; then
+    echo "No cloud provider configured or .config file not found"
+    exit 1
+fi
+
+echo "Cloud Provider: $PROVIDER"
+echo
+
+case "$PROVIDER" in
+    lambdalabs)
+        # Get API key from credentials file
+        API_KEY=$(python3 $(dirname "$0")/lambdalabs_credentials.py get 2>/dev/null)
+        if [ -z "$API_KEY" ]; then
+            echo "Error: Lambda Labs API key not found"
+            echo "Please configure it with: python3 scripts/lambdalabs_credentials.py set 'your-api-key'"
+            exit 1
+        fi
+
+        # Try to list instances using curl
+        echo "Fetching Lambda Labs instances..."
+        response=$(curl -s -H "Authorization: Bearer $API_KEY" \
+            https://cloud.lambdalabs.com/api/v1/instances 2>&1)
+
+        # Check if we got an error
+        if echo "$response" | grep -q '"error"'; then
+            echo "Error accessing Lambda Labs API:"
+            echo "$response" | python3 -c "
+import sys, json
+try:
+    data = json.load(sys.stdin)
+    if 'error' in data:
+        err = data['error']
+        print(f\"  {err.get('message', 'Unknown error')}\")
+        if 'suggestion' in err:
+            print(f\"  Suggestion: {err['suggestion']}\")
+except:
+    print('  Unable to parse error response')
+"
+            exit 1
+        fi
+
+        # Parse and display instances
+        echo "$response" | python3 -c '
+import sys, json
+from datetime import datetime
+
+def format_uptime(created_at):
+    try:
+        created = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
+        now = datetime.now(created.tzinfo)
+        delta = now - created
+
+        days = delta.days
+        hours, remainder = divmod(delta.seconds, 3600)
+        minutes, _ = divmod(remainder, 60)
+
+        if days > 0:
+            return f"{days}d {hours}h {minutes}m"
+        elif hours > 0:
+            return f"{hours}h {minutes}m"
+        else:
+            return f"{minutes}m"
+    except:
+        return "unknown"
+
+data = json.load(sys.stdin)
+instances = data.get("data", [])
+
+if not instances:
+    print("No Lambda Labs instances currently running")
+else:
+    print("Lambda Labs Instances:")
+    print("=" * 80)
+    headers = f"{'Name':<20} {'Type':<20} {'IP':<15} {'Region':<15} {'Status':<10}"
+    print(headers)
+    print("-" * 80)
+
+    total_cost = 0
+    for inst in instances:
+        name = inst.get("name", "unnamed")
+        inst_type = inst.get("instance_type", {}).get("name", "unknown")
+        ip = inst.get("ip", "pending")
+        region = inst.get("region", {}).get("name", "unknown")
+        status = inst.get("status", "unknown")
+
+        # Highlight kdevops instances
+        if "cgpu" in name or "kdevops" in name.lower():
+            name = f"→ {name}"
+
+        row = f"{name:<20} {inst_type:<20} {ip:<15} {region:<15} {status:<10}"
+        print(row)
+
+        price_cents = inst.get("instance_type", {}).get("price_cents_per_hour", 0)
+        total_cost += price_cents / 100
+
+    print("-" * 80)
+    print(f"Total instances: {len(instances)}")
+    if total_cost > 0:
+        print(f"Total hourly cost: ${total_cost:.2f}/hr")
+        print(f"Daily cost estimate: ${total_cost * 24:.2f}/day")
+'
+        ;;
+
+    aws)
+        echo "AWS cloud listing not yet implemented"
+        echo "You can use: aws ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,PublicIpAddress,State.Name,Tags[?Key==\`Name\`]|[0].Value]' --output table"
+        ;;
+
+    gce)
+        echo "Google Cloud listing not yet implemented"
+        echo "You can use: gcloud compute instances list"
+        ;;
+
+    azure)
+        echo "Azure cloud listing not yet implemented"
+        echo "You can use: az vm list --output table"
+        ;;
+
+    oci)
+        echo "Oracle Cloud listing not yet implemented"
+        echo "You can use: oci compute instance list --compartment-id <compartment-ocid>"
+        ;;
+
+    *)
+        echo "Cloud provider '$PROVIDER' not supported for listing"
+        exit 1
+        ;;
+esac
diff --git a/scripts/lambda-cli b/scripts/lambda-cli
new file mode 100755
index 0000000..c4cf149
--- /dev/null
+++ b/scripts/lambda-cli
@@ -0,0 +1,639 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+"""
+Lambda Labs CLI tool for kdevops
+
+A structured CLI tool that mimics AWS CLI patterns, providing access to
+Lambda Labs cloud provider functionality for dynamic configuration generation
+and resource management.
+"""
+
+import argparse
+import json
+import sys
+import os
+from typing import Dict, List, Any, Optional, Tuple
+from pathlib import Path
+
+# Import the existing Lambda Labs API functions
+try:
+    from lambdalabs_api import (
+        get_api_key,
+        get_instance_types_with_capacity,
+        get_regions,
+        get_instance_pricing,
+        generate_instance_types_kconfig,
+        generate_regions_kconfig,
+        generate_instance_type_mappings,
+    )
+except ImportError:
+    # Try to import from scripts directory if not in path
+    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+    from lambdalabs_api import (
+        get_api_key,
+        get_instance_types_with_capacity,
+        get_regions,
+        get_instance_pricing,
+        generate_instance_types_kconfig,
+        generate_regions_kconfig,
+        generate_instance_type_mappings,
+    )
+
+
+class LambdaCLI:
+    """Lambda Labs CLI interface"""
+
+    def __init__(self, output_format: str = "json"):
+        """
+        Initialize the CLI with specified output format
+
+        Args:
+            output_format: 'json' or 'text' for output formatting
+        """
+        self.output_format = output_format
+        self.api_key = get_api_key()
+
+    def output(self, data: Any, headers: Optional[List[str]] = None):
+        """
+        Output data in the specified format
+
+        Args:
+            data: Data to output (dict, list, or primitive)
+            headers: Column headers for text format (optional)
+        """
+        if self.output_format == "json":
+            print(json.dumps(data, indent=2))
+        else:
+            # Human-readable text format
+            if isinstance(data, list):
+                if data and isinstance(data[0], dict):
+                    # Table format for list of dicts
+                    if not headers:
+                        headers = list(data[0].keys()) if data else []
+
+                    if headers:
+                        # Calculate column widths
+                        widths = {h: len(h) for h in headers}
+                        for item in data:
+                            for h in headers:
+                                val = str(item.get(h, ""))
+                                widths[h] = max(widths[h], len(val))
+
+                        # Print header
+                        header_line = " | ".join(h.ljust(widths[h]) for h in headers)
+                        print(header_line)
+                        print("-" * len(header_line))
+
+                        # Print rows
+                        for item in data:
+                            row = " | ".join(
+                                str(item.get(h, "")).ljust(widths[h]) for h in headers
+                            )
+                            print(row)
+                else:
+                    # Simple list
+                    for item in data:
+                        print(item)
+            elif isinstance(data, dict):
+                # Key-value format
+                max_key_len = max(len(k) for k in data.keys()) if data else 0
+                for key, value in data.items():
+                    print(f"{key.ljust(max_key_len)} : {value}")
+            else:
+                # Simple value
+                print(data)
+
+    def list_instance_types(
+        self, available_only: bool = False, region: Optional[str] = None
+    ) -> List[Dict[str, Any]]:
+        """
+        List instance types
+
+        Args:
+            available_only: Only show available instances
+            region: Filter by specific region
+
+        Returns:
+            List of instance type information
+        """
+        if not self.api_key:
+            return [
+                {
+                    "error": "No API key found. Please set LAMBDALABS_API_KEY or configure credentials."
+                }
+            ]
+
+        instances, capacity_map = get_instance_types_with_capacity(self.api_key)
+        pricing = get_instance_pricing()
+
+        result = []
+        for name, info in instances.items():
+            available_regions = capacity_map.get(name, [])
+
+            # Apply filters
+            if available_only and not available_regions:
+                continue
+
+            if region and region not in available_regions:
+                continue
+
+            # Get price from pricing data
+            price_per_hour = pricing.get(name, 0.0)
+
+            item = {
+                "name": name,
+                "price_per_hour": f"${price_per_hour:.2f}",
+                "specs": info.get("specs_overview", ""),
+                "available_regions": len(available_regions),
+            }
+            if region:
+                item["available_in_region"] = region in available_regions
+            result.append(item)
+
+        # Sort by price
+        result.sort(key=lambda x: float(x["price_per_hour"].replace("$", "")))
+
+        return result
+
+    def list_regions(self, with_availability: bool = False) -> List[Dict[str, Any]]:
+        """
+        List regions
+
+        Args:
+            with_availability: Include availability information
+
+        Returns:
+            List of region information
+        """
+        if not self.api_key:
+            return [
+                {
+                    "error": "No API key found. Please set LAMBDALABS_API_KEY or configure credentials."
+                }
+            ]
+
+        regions = get_regions(self.api_key)
+
+        result = []
+        for region in regions:
+            item = {
+                "name": region["name"],
+                "description": region.get("description", ""),
+            }
+
+            if with_availability:
+                # Count available instance types in this region
+                _, capacity_map = get_instance_types_with_capacity(self.api_key)
+                available_count = sum(
+                    1
+                    for instance, regions_list in capacity_map.items()
+                    if region["name"] in regions_list
+                )
+                item["available_instances"] = available_count
+
+            result.append(item)
+
+        return result
+
+    def get_cheapest_instance(
+        self, region: Optional[str] = None, min_gpus: int = 1
+    ) -> Dict[str, Any]:
+        """
+        Find the cheapest available instance
+
+        Args:
+            region: Specific region to search in
+            min_gpus: Minimum number of GPUs required
+
+        Returns:
+            Cheapest instance information
+        """
+        if not self.api_key:
+            return {
+                "error": "No API key found. Please set LAMBDALABS_API_KEY or configure credentials."
+            }
+
+        instances, capacity_map = get_instance_types_with_capacity(self.api_key)
+        pricing = get_instance_pricing()
+
+        # Find available instances with pricing
+        available = []
+        for name, info in instances.items():
+            available_regions = capacity_map.get(name, [])
+            if not available_regions:
+                continue
+
+            if region and region not in available_regions:
+                continue
+
+            # Filter by GPU count
+            if min_gpus > 1:
+                parts = name.split("_")
+                if len(parts) >= 2 and "x" in parts[1]:
+                    gpu_count = int(parts[1].replace("x", ""))
+                    if gpu_count < min_gpus:
+                        continue
+
+            price = pricing.get(name, float("inf"))
+            available.append(
+                {
+                    "name": name,
+                    "price": price,
+                    "specs": info.get("specs_overview", ""),
+                    "available_regions": available_regions,
+                }
+            )
+
+        if not available:
+            return {"error": "No available instances matching criteria"}
+
+        # Sort by price and get cheapest
+        cheapest = min(available, key=lambda x: x["price"])
+
+        return {
+            "name": cheapest["name"],
+            "price_per_hour": f"${cheapest['price']:.2f}",
+            "specs": cheapest["specs"],
+            "available_regions": cheapest["available_regions"],
+        }
+
+    def get_pricing(self, instance_type: Optional[str] = None) -> List[Dict[str, Any]]:
+        """
+        Get pricing information
+
+        Args:
+            instance_type: Specific instance type to get pricing for
+
+        Returns:
+            Pricing information
+        """
+        if not self.api_key:
+            return [
+                {
+                    "error": "No API key found. Please set LAMBDALABS_API_KEY or configure credentials."
+                }
+            ]
+
+        instances, _ = get_instance_types_with_capacity(self.api_key)
+        pricing = get_instance_pricing()
+
+        result = []
+        for name, info in instances.items():
+            if instance_type and name != instance_type:
+                continue
+
+            price = pricing.get(name, 0.0)
+            result.append(
+                {
+                    "instance_type": name,
+                    "price_per_hour": f"${price:.2f}",
+                    "price_per_day": f"${price * 24:.2f}",
+                    "price_per_month": f"${price * 24 * 30:.2f}",
+                    "specs": info.get("specs_overview", ""),
+                }
+            )
+
+        # Sort by price
+        result.sort(key=lambda x: float(x["price_per_hour"].replace("$", "")))
+
+        return result
+
+    def smart_select(self, mode: str = "cheapest") -> Dict[str, Any]:
+        """
+        Smart selection of instance and region
+
+        Args:
+            mode: Selection mode ('cheapest', 'closest', 'balanced')
+
+        Returns:
+            Selected configuration
+        """
+        if mode == "cheapest":
+            # Find cheapest instance globally
+            cheapest = self.get_cheapest_instance()
+            if "error" in cheapest:
+                return cheapest
+
+            # Select closest region with this instance
+            available_regions = cheapest.get("available_regions", [])
+            if not available_regions:
+                return {"error": "No regions available for cheapest instance"}
+
+            # For now, just pick the first available region
+            # In a full implementation, we'd determine closest based on user location
+            selected_region = available_regions[0]
+
+            return {
+                "instance_type": cheapest["name"],
+                "region": selected_region,
+                "price_per_hour": cheapest["price_per_hour"],
+                "selection_mode": "cheapest_global",
+            }
+
+        elif mode == "closest":
+            # This would require geolocation logic
+            # For now, return a placeholder
+            return {
+                "error": "Closest region selection not yet implemented",
+                "hint": "Use --mode cheapest for automatic selection",
+            }
+
+        elif mode == "balanced":
+            # Balance between price and proximity
+            # This is a simplified implementation
+            cheapest = self.get_cheapest_instance()
+            if "error" in cheapest:
+                return cheapest
+
+            return {
+                "instance_type": cheapest["name"],
+                "region": cheapest.get("available_regions", ["us-west-1"])[0],
+                "price_per_hour": cheapest["price_per_hour"],
+                "selection_mode": "balanced",
+            }
+
+        else:
+            return {"error": f"Unknown selection mode: {mode}"}
+
+    def check_availability(self, instance_type: str, region: str) -> Dict[str, Any]:
+        """
+        Check if an instance type is available in a specific region.
+
+        Args:
+            instance_type: Instance type to check
+            region: Region to check
+
+        Returns:
+            Availability status
+        """
+        if not self.api_key:
+            return {
+                "error": "No API key found. Please set LAMBDALABS_API_KEY or configure credentials."
+            }
+
+        instances, capacity_map = get_instance_types_with_capacity(self.api_key)
+
+        if instance_type not in instances:
+            return {
+                "available": False,
+                "error": f"Instance type {instance_type} not found",
+            }
+
+        available_regions = capacity_map.get(instance_type, [])
+
+        if not available_regions:
+            return {
+                "available": False,
+                "error": f"Instance type {instance_type} has no available capacity in any region",
+            }
+
+        if region not in available_regions:
+            return {
+                "available": False,
+                "error": f"Instance type {instance_type} not available in {region}",
+                "available_regions": available_regions,
+            }
+
+        return {
+            "available": True,
+            "instance_type": instance_type,
+            "region": region,
+            "message": f"Instance {instance_type} is available in {region}",
+        }
+
+    def generate_kconfig(self, output_dir: str = "terraform/lambdalabs/kconfigs"):
+        """
+        Generate Kconfig files for Lambda Labs
+
+        Args:
+            output_dir: Directory to write Kconfig files to
+
+        Returns:
+            Status information
+        """
+        if not self.api_key:
+            return {
+                "error": "No API key found. Please set LAMBDALABS_API_KEY or configure credentials."
+            }
+
+        os.makedirs(output_dir, exist_ok=True)
+
+        # Generate compute Kconfig
+        compute_kconfig = generate_instance_types_kconfig(self.api_key)
+        compute_path = os.path.join(output_dir, "Kconfig.compute.generated")
+        with open(compute_path, "w") as f:
+            f.write(compute_kconfig)
+
+        # Generate location Kconfig
+        location_kconfig = generate_regions_kconfig(self.api_key)
+        location_path = os.path.join(output_dir, "Kconfig.location.generated")
+        with open(location_path, "w") as f:
+            f.write(location_kconfig)
+
+        # Generate instance type mappings
+        mappings = generate_instance_type_mappings(self.api_key)
+        mappings_path = os.path.join(output_dir, "Kconfig.compute.mappings")
+        with open(mappings_path, "w") as f:
+            f.write(mappings)
+
+        return {
+            "status": "success",
+            "files_generated": [compute_path, location_path, mappings_path],
+            "message": "Kconfig files generated successfully",
+        }
+
+
+def main():
+    """Main CLI entry point"""
+    parser = argparse.ArgumentParser(
+        prog="lambda-cli",
+        description="Lambda Labs CLI for kdevops",
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        epilog="""
+Examples:
+  # List all instance types
+  lambda-cli.py instance-types list
+
+  # List available instances only
+  lambda-cli.py instance-types list --available-only
+
+  # Get cheapest instance
+  lambda-cli.py instance-types get-cheapest
+
+  # List regions with availability info
+  lambda-cli.py regions list --with-availability
+
+  # Get pricing information
+  lambda-cli.py pricing list
+
+  # Smart selection
+  lambda-cli.py smart-select --mode cheapest
+
+  # Generate Kconfig files
+  lambda-cli.py generate-kconfig
+
+  # JSON output
+  lambda-cli.py instance-types list --output json
+        """,
+    )
+
+    # Global options
+    parser.add_argument(
+        "--output",
+        "-o",
+        choices=["json", "text"],
+        default="text",
+        help="Output format (default: text)",
+    )
+
+    # Subparsers for different commands
+    subparsers = parser.add_subparsers(dest="command", help="Available commands")
+
+    # Instance types commands
+    instance_parser = subparsers.add_parser(
+        "instance-types", help="Manage instance types"
+    )
+    instance_subparsers = instance_parser.add_subparsers(dest="subcommand")
+
+    # instance-types list
+    list_instances = instance_subparsers.add_parser("list", help="List instance types")
+    list_instances.add_argument(
+        "--available-only", action="store_true", help="Show only available instances"
+    )
+    list_instances.add_argument("--region", help="Filter by region")
+
+    # instance-types get-cheapest
+    cheapest_parser = instance_subparsers.add_parser(
+        "get-cheapest", help="Find cheapest instance"
+    )
+    cheapest_parser.add_argument("--region", help="Specific region")
+    cheapest_parser.add_argument(
+        "--min-gpus", type=int, default=1, help="Minimum number of GPUs"
+    )
+
+    # Regions commands
+    region_parser = subparsers.add_parser("regions", help="Manage regions")
+    region_subparsers = region_parser.add_subparsers(dest="subcommand")
+
+    # regions list
+    list_regions = region_subparsers.add_parser("list", help="List regions")
+    list_regions.add_argument(
+        "--with-availability",
+        action="store_true",
+        help="Include availability information",
+    )
+
+    # Pricing commands
+    pricing_parser = subparsers.add_parser("pricing", help="Get pricing information")
+    pricing_subparsers = pricing_parser.add_subparsers(dest="subcommand")
+
+    # pricing list
+    list_pricing = pricing_subparsers.add_parser("list", help="List pricing")
+    list_pricing.add_argument("--instance-type", help="Specific instance type")
+
+    # Smart selection
+    smart_parser = subparsers.add_parser(
+        "smart-select", help="Smart instance/region selection"
+    )
+    smart_parser.add_argument(
+        "--mode",
+        choices=["cheapest", "closest", "balanced"],
+        default="cheapest",
+        help="Selection mode",
+    )
+
+    # Check availability
+    check_parser = subparsers.add_parser(
+        "check-availability", help="Check instance availability"
+    )
+    check_parser.add_argument("instance_type", help="Instance type to check")
+    check_parser.add_argument("region", help="Region to check")
+
+    # Generate Kconfig
+    kconfig_parser = subparsers.add_parser(
+        "generate-kconfig", help="Generate Kconfig files"
+    )
+    kconfig_parser.add_argument(
+        "--output-dir",
+        default="terraform/lambdalabs/kconfigs",
+        help="Output directory for Kconfig files",
+    )
+
+    # Parse arguments
+    args = parser.parse_args()
+
+    # Initialize CLI
+    cli = LambdaCLI(output_format=args.output)
+
+    # Handle commands
+    try:
+        if args.command == "instance-types":
+            if args.subcommand == "list":
+                result = cli.list_instance_types(
+                    available_only=args.available_only, region=args.region
+                )
+                headers = ["name", "price_per_hour", "specs", "available_regions"]
+                if args.region:
+                    headers.append("available_in_region")
+                cli.output(result, headers=headers)
+
+            elif args.subcommand == "get-cheapest":
+                result = cli.get_cheapest_instance(
+                    region=args.region, min_gpus=args.min_gpus
+                )
+                cli.output(result)
+
+            else:
+                parser.error(f"Unknown subcommand: {args.subcommand}")
+
+        elif args.command == "regions":
+            if args.subcommand == "list":
+                result = cli.list_regions(with_availability=args.with_availability)
+                headers = ["name", "description"]
+                if args.with_availability:
+                    headers.append("available_instances")
+                cli.output(result, headers=headers)
+
+            else:
+                parser.error(f"Unknown subcommand: {args.subcommand}")
+
+        elif args.command == "pricing":
+            if args.subcommand == "list":
+                result = cli.get_pricing(instance_type=args.instance_type)
+                headers = [
+                    "instance_type",
+                    "price_per_hour",
+                    "price_per_day",
+                    "price_per_month",
+                ]
+                cli.output(result, headers=headers)
+
+            else:
+                parser.error(f"Unknown subcommand: {args.subcommand}")
+
+        elif args.command == "smart-select":
+            result = cli.smart_select(mode=args.mode)
+            cli.output(result)
+
+        elif args.command == "check-availability":
+            result = cli.check_availability(args.instance_type, args.region)
+            cli.output(result)
+
+        elif args.command == "generate-kconfig":
+            result = cli.generate_kconfig(output_dir=args.output_dir)
+            cli.output(result)
+
+        else:
+            parser.print_help()
+            sys.exit(1)
+
+    except Exception as e:
+        if args.output == "json":
+            print(json.dumps({"error": str(e)}, indent=2))
+        else:
+            print(f"Error: {e}", file=sys.stderr)
+        sys.exit(1)
+
+
+if __name__ == "__main__":
+    main()
-- 
2.50.1


  parent reply	other threads:[~2025-08-31  4:00 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-31  3:59 [PATCH v3 00/10] terraform: add Lambda Labs cloud provider support Luis Chamberlain
2025-08-31  3:59 ` [PATCH v3 01/10] gitignore: add entries for Lambda Labs dynamic configuration Luis Chamberlain
2025-08-31  3:59 ` [PATCH v3 02/10] scripts: add Lambda Labs Python API library Luis Chamberlain
2025-08-31  3:59 ` Luis Chamberlain [this message]
2025-08-31  3:59 ` [PATCH v3 04/10] scripts: add Lambda Labs credentials management Luis Chamberlain
2025-08-31  3:59 ` [PATCH v3 05/10] scripts: add Lambda Labs SSH key management utilities Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 06/10] kconfig: add dynamic cloud provider configuration infrastructure Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 07/10] terraform/lambdalabs: add Kconfig structure for Lambda Labs Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 08/10] terraform/lambdalabs: add terraform provider implementation Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 09/10] ansible/terraform: integrate Lambda Labs into build system Luis Chamberlain
2025-08-31  4:00 ` [PATCH v3 10/10] kconfigs: enable Lambda Labs cloud provider in menus Luis Chamberlain
2025-09-01  1:10 ` [PATCH v3 00/10] terraform: add Lambda Labs cloud provider support Luis Chamberlain

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250831040004.2159779-4-mcgrof@kernel.org \
    --to=mcgrof@kernel.org \
    --cc=cel@kernel.org \
    --cc=da.gomez@kruces.com \
    --cc=email@example.com \
    --cc=kdevops@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox