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
next prev 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