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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.