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>
Subject: [PATCH v2 09/10] scripts: add Lambda Labs testing and debugging utilities
Date: Wed, 27 Aug 2025 14:29:00 -0700 [thread overview]
Message-ID: <20250827212902.4021990-10-mcgrof@kernel.org> (raw)
In-Reply-To: <20250827212902.4021990-1-mcgrof@kernel.org>
Add utility scripts for testing, debugging, and managing Lambda Labs
cloud resources. These tools help developers validate configurations,
debug issues, and manage instances.
Testing utilities:
- Capacity checking before provisioning
- SSH connectivity testing
- API endpoint validation
- Credential verification
Management utilities:
- Instance listing and status checking
- Smart instance selection based on cost/availability
- Region inference for optimal placement
- Cloud provider comparison tool
Debugging utilities:
- API response exploration
- Debug script for troubleshooting API issues
- Instance update helper
Also documents the Lambda Labs implementation in PROMPTS.md for future
reference and improvement.
Generated-by: Claude AI
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
---
PROMPTS.md | 56 ++++++++
scripts/check_lambdalabs_capacity.py | 172 ++++++++++++++++++++++
scripts/cloud_list_all.sh | 151 ++++++++++++++++++++
scripts/debug_lambdalabs_api.sh | 87 ++++++++++++
scripts/explore_lambda_api.py | 48 +++++++
scripts/lambdalabs_infer_cheapest.py | 107 ++++++++++++++
scripts/lambdalabs_infer_region.py | 36 +++++
scripts/lambdalabs_list_instances.py | 167 ++++++++++++++++++++++
scripts/lambdalabs_smart_inference.py | 196 ++++++++++++++++++++++++++
scripts/terraform_list_instances.sh | 79 +++++++++++
scripts/test_lambda_ssh.py | 111 +++++++++++++++
scripts/update_lambdalabs_instance.sh | 29 ++++
12 files changed, 1239 insertions(+)
create mode 100755 scripts/check_lambdalabs_capacity.py
create mode 100755 scripts/cloud_list_all.sh
create mode 100755 scripts/debug_lambdalabs_api.sh
create mode 100644 scripts/explore_lambda_api.py
create mode 100755 scripts/lambdalabs_infer_cheapest.py
create mode 100755 scripts/lambdalabs_infer_region.py
create mode 100755 scripts/lambdalabs_list_instances.py
create mode 100755 scripts/lambdalabs_smart_inference.py
create mode 100755 scripts/terraform_list_instances.sh
create mode 100644 scripts/test_lambda_ssh.py
create mode 100755 scripts/update_lambdalabs_instance.sh
diff --git a/PROMPTS.md b/PROMPTS.md
index 1b60cbe..c87a3b3 100644
--- a/PROMPTS.md
+++ b/PROMPTS.md
@@ -280,3 +280,59 @@ The implementation successfully added:
to do is to use a separate fact if you want a true dynamic variable. This
is why we switched to an active ref prefix for the baseline and dev group
ref tags.
+
+## Cloud provider integrations
+
+### Adding Lambda Labs cloud provider support with dynamic Kconfig
+
+**Prompt:**
+The Lambda Labs company helps you use GPUs online, kind of like AWS, or OCI. Add
+support for the terraform support for Lambda Labs. The best provider docs are at
+https://registry.terraform.io/providers/elct9620/lambdalabs/latest/docs . Then
+To create the kconfig values you will implement support to use the lambda cloud
+API to let us query for what type of instances they have available and so forth.
+Therefore the Kconfig stuff for Lambda labs will all be dynamic. So we'll want
+to expand this as part of what make dynconfig does. However note that dynconfig
+does *all* dynamically generated kconfig. We want to add support for make
+cloud-config as a new target which is dynamic which is a subset of make
+dynconfig ; OK! good luck
+
+**AI:** Claude Code (Opus 4.1)
+**Commit:** [To be determined]
+**Result:** Complete Lambda Labs integration with dynamic Kconfig generation.
+**Grading:** 75%
+
+**Notes:**
+
+The implementation successfully added:
+
+1. **Terraform Provider Integration**: Created complete Terraform configuration
+ for Lambda Labs including instance management, persistent storage, and SSH
+ configuration management following existing cloud provider patterns.
+
+2. **Dynamic Kconfig Generation**: Implemented Python script to query Lambda Labs
+ API for available instance types, regions, and OS images. Generated dynamic
+ Kconfig files with fallback defaults when API is unavailable.
+
+3. **Build System Integration**: Added `make cloud-config` as a new target for
+ cloud-specific dynamic configuration, properly integrated with `make dynconfig`.
+ Created modular Makefile structure for cloud provider dynamic configuration.
+
+4. **Kconfig Structure**: Properly integrated Lambda Labs into the provider
+ selection system with modular Kconfig files for location, compute, storage,
+ and identity management.
+
+Biggest issues:
+
+1. **SSH Management**: For this it failed to realize the provider
+ didn't suport asking for a custom username, so we had to find out the
+ hard way.
+
+2. **Environment variables**: For some reason it wanted to define the
+ credential API as an environment variable. This proved painful as some
+ environment variables do not carry over for some ansible tasks. The
+ best solution was to follow the strategy similar to what AWS supports
+ with ~/.lambdalabs/credentials. This a more secure alternative.
+
+Minor issues:
+- Some whitespace formatting was automatically fixed by the linter
diff --git a/scripts/check_lambdalabs_capacity.py b/scripts/check_lambdalabs_capacity.py
new file mode 100755
index 0000000..5b16156
--- /dev/null
+++ b/scripts/check_lambdalabs_capacity.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: copyleft-next-0.3.1
+
+"""
+Check Lambda Labs capacity for a given instance type and region.
+Provides clear error messages when capacity is not available.
+"""
+
+import json
+import os
+import sys
+import urllib.request
+import urllib.error
+from typing import Dict, List, Optional
+
+# Import our credentials module
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+from lambdalabs_credentials import get_api_key as get_api_key_from_credentials
+
+LAMBDALABS_API_BASE = "https://cloud.lambdalabs.com/api/v1"
+
+
+def get_api_key() -> Optional[str]:
+ """Get Lambda Labs API key from credentials file or environment variable."""
+ return get_api_key_from_credentials()
+
+
+def check_capacity(instance_type: str, region: str) -> Dict:
+ """
+ Check if capacity is available for the given instance type and region.
+
+ Returns:
+ Dictionary with:
+ - available: bool - whether capacity is available
+ - message: str - human-readable message
+ - alternatives: list - alternative regions with capacity
+ """
+ api_key = get_api_key()
+ if not api_key:
+ return {
+ "available": False,
+ "message": "ERROR: Lambda Labs API key not configured.\n"
+ "Please configure your API key using:\n"
+ " python3 scripts/lambdalabs_credentials.py set 'your-api-key'",
+ "alternatives": [],
+ }
+
+ headers = {"Authorization": f"Bearer {api_key}", "User-Agent": "kdevops/1.0"}
+ url = f"{LAMBDALABS_API_BASE}/instance-types"
+
+ try:
+ req = urllib.request.Request(url, headers=headers)
+ with urllib.request.urlopen(req) as response:
+ data = json.loads(response.read().decode())
+
+ if "data" not in data:
+ return {
+ "available": False,
+ "message": "ERROR: Invalid API response format",
+ "alternatives": [],
+ }
+
+ # Check if instance type exists
+ if instance_type not in data["data"]:
+ available_types = list(data["data"].keys())[:10]
+ return {
+ "available": False,
+ "message": f"ERROR: Instance type '{instance_type}' does not exist.\n"
+ f"Available instance types include: {', '.join(available_types)}",
+ "alternatives": [],
+ }
+
+ gpu_info = data["data"][instance_type]
+
+ # Check if instance type is generally available
+ # Note: is_available can be None, True, or False
+ is_available = gpu_info.get("instance_type", {}).get("is_available")
+ if is_available is False: # Only fail if explicitly False, not None
+ return {
+ "available": False,
+ "message": f"ERROR: Instance type '{instance_type}' is not currently available from Lambda Labs",
+ "alternatives": [],
+ }
+
+ # Get regions with capacity
+ regions_with_capacity = gpu_info.get("regions_with_capacity_available", [])
+ region_names = [r["name"] for r in regions_with_capacity]
+
+ # Check if requested region has capacity
+ if region in region_names:
+ return {
+ "available": True,
+ "message": f"✓ Capacity is available for {instance_type} in {region}",
+ "alternatives": region_names,
+ }
+ else:
+ # No capacity in requested region
+ if regions_with_capacity:
+ alt_regions = [f"{r['name']}" for r in regions_with_capacity]
+ return {
+ "available": False,
+ "message": f"ERROR: No capacity available for '{instance_type}' in region '{region}'.\n"
+ f"\nRegions with available capacity:\n"
+ + "\n".join([f" • {r}" for r in alt_regions])
+ + f"\n\nTo fix this issue, either:\n"
+ f"1. Wait for capacity to become available in {region}\n"
+ f"2. Change your region in menuconfig to one of the available regions\n"
+ f"3. Choose a different instance type",
+ "alternatives": region_names,
+ }
+ else:
+ return {
+ "available": False,
+ "message": f"ERROR: No capacity available for '{instance_type}' in ANY region.\n"
+ f"This instance type is currently sold out across all Lambda Labs regions.\n"
+ f"Please try:\n"
+ f" • A different instance type\n"
+ f" • Checking back later when capacity becomes available",
+ "alternatives": [],
+ }
+
+ except urllib.error.HTTPError as e:
+ if e.code == 403:
+ return {
+ "available": False,
+ "message": "ERROR: Lambda Labs API returned 403 Forbidden.\n"
+ "This usually means your API key is invalid, expired, or lacks permissions.\n"
+ "\n"
+ "To fix this:\n"
+ "1. Log into https://cloud.lambdalabs.com\n"
+ "2. Go to API Keys section\n"
+ "3. Create a new API key with full permissions\n"
+ "4. Update your credentials:\n"
+ ' python3 scripts/lambdalabs_credentials.py set "your-new-api-key"\n'
+ "\n"
+ "Current API key source: ~/.lambdalabs/credentials",
+ "alternatives": [],
+ }
+ else:
+ return {
+ "available": False,
+ "message": f"ERROR: API request failed with HTTP {e.code}: {e.reason}",
+ "alternatives": [],
+ }
+ except Exception as e:
+ return {
+ "available": False,
+ "message": f"ERROR: Failed to check capacity: {str(e)}",
+ "alternatives": [],
+ }
+
+
+def main():
+ """Main function for command-line usage."""
+ if len(sys.argv) != 3:
+ print("Usage: check_lambdalabs_capacity.py <instance_type> <region>")
+ print("Example: check_lambdalabs_capacity.py gpu_1x_a10 us-tx-1")
+ sys.exit(1)
+
+ instance_type = sys.argv[1]
+ region = sys.argv[2]
+
+ result = check_capacity(instance_type, region)
+
+ print(result["message"])
+
+ # Exit with appropriate code
+ sys.exit(0 if result["available"] else 1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/cloud_list_all.sh b/scripts/cloud_list_all.sh
new file mode 100755
index 0000000..405c3d9
--- /dev/null
+++ b/scripts/cloud_list_all.sh
@@ -0,0 +1,151 @@
+#!/bin/bash
+# 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/debug_lambdalabs_api.sh b/scripts/debug_lambdalabs_api.sh
new file mode 100755
index 0000000..d9b5b15
--- /dev/null
+++ b/scripts/debug_lambdalabs_api.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+echo "Lambda Labs API Diagnostic Script"
+echo "================================="
+echo
+
+# Get API key from credentials file
+API_KEY=$(python3 $(dirname "$0")/lambdalabs_credentials.py get 2>/dev/null)
+if [ -z "$API_KEY" ]; then
+ echo "❌ Lambda Labs API key not found"
+ echo " Please configure it with: python3 scripts/lambdalabs_credentials.py set 'your-api-key'"
+ exit 1
+else
+ echo "✓ Lambda Labs API key loaded from credentials"
+ echo " Key starts with: ${API_KEY:0:10}..."
+ echo " Key length: ${#API_KEY} characters"
+fi
+
+echo
+echo "Testing API Access:"
+echo "-------------------"
+
+# Test with curl to get more detailed error information
+echo "1. Testing instance types endpoint..."
+response=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $API_KEY" \
+ https://cloud.lambdalabs.com/api/v1/instance-types 2>&1)
+http_code=$(echo "$response" | tail -n 1)
+body=$(echo "$response" | head -n -1)
+
+if [ "$http_code" = "200" ]; then
+ echo " ✓ API access successful"
+ echo " Instance types available: $(echo "$body" | grep -o '"name"' | wc -l)"
+elif [ "$http_code" = "403" ]; then
+ echo " ❌ Access forbidden (HTTP 403)"
+ echo " Error: $body"
+ echo
+ echo " Possible causes:"
+ echo " - Invalid or expired API key"
+ echo " - API key doesn't have necessary permissions"
+ echo " - IP address or region restrictions"
+ echo " - Rate limiting"
+ echo
+ echo " Please verify:"
+ echo " 1. Your API key is correct and active"
+ echo " 2. You're not behind a VPN that might be blocked"
+ echo " 3. Your Lambda Labs account is in good standing"
+elif [ "$http_code" = "401" ]; then
+ echo " ❌ Unauthorized (HTTP 401)"
+ echo " Your API key appears to be invalid or malformed"
+else
+ echo " ❌ Unexpected response (HTTP $http_code)"
+ echo " Response: $body"
+fi
+
+echo
+echo "2. Testing SSH keys endpoint..."
+response=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $API_KEY" \
+ https://cloud.lambdalabs.com/api/v1/ssh-keys 2>&1)
+http_code=$(echo "$response" | tail -n 1)
+body=$(echo "$response" | head -n -1)
+
+if [ "$http_code" = "200" ]; then
+ echo " ✓ Can access SSH keys"
+ # Try to find the kdevops key
+ if echo "$body" | grep -q "kdevops-lambdalabs"; then
+ echo " ✓ Found 'kdevops-lambdalabs' SSH key"
+ else
+ echo " ⚠ 'kdevops-lambdalabs' SSH key not found"
+ echo " Available keys:"
+ echo "$body" | grep -o '"name":"[^"]*"' | sed 's/"name":"/ - /g' | sed 's/"//g'
+ fi
+else
+ echo " ❌ Cannot access SSH keys (HTTP $http_code)"
+fi
+
+echo
+echo "Troubleshooting Steps:"
+echo "----------------------"
+echo "1. Verify your API key at: https://cloud.lambdalabs.com/api-keys"
+echo "2. Create a new API key if needed"
+echo "3. Ensure you're not using a VPN that might be blocked"
+echo "4. Try accessing the API from a different network/location"
+echo "5. Contact Lambda Labs support if the issue persists"
+echo
+echo "For manual testing, try:"
+echo "API_KEY=\$(python3 scripts/lambdalabs_credentials.py get)"
+echo "curl -H \"Authorization: Bearer \$API_KEY\" https://cloud.lambdalabs.com/api/v1/instance-types"
diff --git a/scripts/explore_lambda_api.py b/scripts/explore_lambda_api.py
new file mode 100644
index 0000000..8c07547
--- /dev/null
+++ b/scripts/explore_lambda_api.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+"""Explore Lambda Labs API to understand SSH key management."""
+
+import json
+import sys
+import os
+
+# Add scripts directory to path
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+from lambdalabs_credentials import get_api_key
+
+# Try to get docs
+print("Lambda Labs API SSH Key Management")
+print("=" * 50)
+print()
+print("Based on API exploration, here's what we know:")
+print()
+print("1. SSH Keys Endpoint: /ssh-keys")
+print(" - GET /ssh-keys returns a list of key NAMES only")
+print(" - The API returns: {'data': ['key-name-1', 'key-name-2', ...]}")
+print()
+print("2. Deleting Keys:")
+print(" - DELETE /ssh-keys/{key_id} expects a key ID, not a name")
+print(" - The error 'Invalid SSH key ID' suggests IDs are different from names")
+print(" - The IDs might be UUIDs or other internal identifiers")
+print()
+print("3. Adding Keys:")
+print(
+ " - POST /ssh-keys likely works with {name: 'key-name', public_key: 'ssh-rsa ...'}"
+)
+print()
+print("4. The problem:")
+print(" - GET /ssh-keys only returns names")
+print(" - DELETE /ssh-keys/{id} requires IDs")
+print(" - There's no apparent way to get the ID from the name")
+print()
+print("Possible solutions:")
+print("1. There might be a GET /ssh-keys?detailed=true or similar")
+print("2. The key names might BE the IDs (but delete fails)")
+print("3. There might be a separate endpoint to get key details")
+print("4. The API might be incomplete/broken for key deletion")
+print()
+print("To properly use kdevops with Lambda Labs, we should use")
+print("the key name 'kdevops-lambdalabs' as configured in Kconfig.")
+print()
+print("Since we can list keys but not delete them via API,")
+print("users must manage keys through the web console:")
+print("https://cloud.lambdalabs.com/ssh-keys")
diff --git a/scripts/lambdalabs_infer_cheapest.py b/scripts/lambdalabs_infer_cheapest.py
new file mode 100755
index 0000000..52c62c3
--- /dev/null
+++ b/scripts/lambdalabs_infer_cheapest.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: copyleft-next-0.3.1
+
+"""
+Find the cheapest available Lambda Labs instance type.
+"""
+
+import json
+import sys
+import urllib.request
+import urllib.error
+from typing import Optional, List, Dict, Tuple
+
+# Import our credentials module
+sys.path.insert(0, sys.path[0])
+from lambdalabs_credentials import get_api_key
+
+LAMBDALABS_API_BASE = "https://cloud.lambdalabs.com/api/v1"
+
+# Known pricing for Lambda Labs instances (per hour)
+INSTANCE_PRICING = {
+ "gpu_1x_rtx6000": 0.50,
+ "gpu_1x_a10": 0.75,
+ "gpu_1x_a6000": 0.80,
+ "gpu_1x_a100": 1.29,
+ "gpu_1x_a100_sxm4": 1.29,
+ "gpu_1x_a100_pcie": 1.29,
+ "gpu_1x_gh200": 1.49,
+ "gpu_1x_h100_pcie": 2.49,
+ "gpu_1x_h100_sxm5": 3.29,
+ "gpu_2x_a100": 2.58,
+ "gpu_2x_a100_pcie": 2.58,
+ "gpu_2x_a6000": 1.60,
+ "gpu_2x_h100_sxm5": 6.38,
+ "gpu_4x_a100": 5.16,
+ "gpu_4x_a100_pcie": 5.16,
+ "gpu_4x_a6000": 3.20,
+ "gpu_4x_h100_sxm5": 12.36,
+ "gpu_8x_v100": 4.40,
+ "gpu_8x_a100": 10.32,
+ "gpu_8x_a100_40gb": 10.32,
+ "gpu_8x_a100_80gb": 14.32,
+ "gpu_8x_a100_80gb_sxm4": 14.32,
+ "gpu_8x_h100_sxm5": 23.92,
+ "gpu_8x_b200_sxm6": 39.92,
+}
+
+
+def get_cheapest_available_instance() -> Optional[str]:
+ """
+ Find the cheapest instance type with available capacity.
+
+ Returns:
+ Instance type name of cheapest available option
+ """
+ api_key = get_api_key()
+ if not api_key:
+ # Return a reasonable default if no API key
+ return "gpu_1x_a10"
+
+ headers = {"Authorization": f"Bearer {api_key}", "User-Agent": "kdevops/1.0"}
+ url = f"{LAMBDALABS_API_BASE}/instance-types"
+
+ try:
+ req = urllib.request.Request(url, headers=headers)
+ with urllib.request.urlopen(req) as response:
+ data = json.loads(response.read().decode())
+
+ if "data" not in data:
+ return "gpu_1x_a10"
+
+ # Find all instance types with available capacity
+ available_instances = []
+
+ for instance_type, info in data["data"].items():
+ regions_with_capacity = info.get("regions_with_capacity_available", [])
+ if regions_with_capacity:
+ # This instance has capacity somewhere
+ price = INSTANCE_PRICING.get(instance_type, 999.99)
+ available_instances.append((instance_type, price))
+
+ if not available_instances:
+ # No capacity anywhere, return cheapest known instance
+ return "gpu_1x_a10"
+
+ # Sort by price (lowest first)
+ available_instances.sort(key=lambda x: x[1])
+
+ # Return the cheapest available instance type
+ return available_instances[0][0]
+
+ except Exception as e:
+ # On any error, return default
+ return "gpu_1x_a10"
+
+
+def main():
+ """Main function for command-line usage."""
+ instance = get_cheapest_available_instance()
+ if instance:
+ print(instance)
+ else:
+ print("gpu_1x_a10")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/lambdalabs_infer_region.py b/scripts/lambdalabs_infer_region.py
new file mode 100755
index 0000000..d8fea17
--- /dev/null
+++ b/scripts/lambdalabs_infer_region.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: copyleft-next-0.3.1
+
+"""
+Smart region inference for Lambda Labs.
+Uses the smart inference algorithm to find the best region for a given instance type.
+"""
+
+import sys
+import os
+
+# Import the smart inference module
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+from lambdalabs_smart_inference import get_best_instance_and_region
+
+
+def main():
+ """Main function for command-line usage."""
+ if len(sys.argv) != 2:
+ print("us-east-1") # Default
+ sys.exit(0)
+
+ # The instance type is passed but we'll get the best region from smart inference
+ # This maintains backward compatibility while using the smart algorithm
+ instance_type_requested = sys.argv[1]
+
+ # Get the best instance and region combo
+ best_instance, best_region = get_best_instance_and_region()
+
+ # For now, just return the best region
+ # In the future, we could check if the requested instance is available in the best region
+ print(best_region)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/lambdalabs_list_instances.py b/scripts/lambdalabs_list_instances.py
new file mode 100755
index 0000000..c61b701
--- /dev/null
+++ b/scripts/lambdalabs_list_instances.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python3
+"""
+List all Lambda Labs instances for the current account.
+Part of kdevops cloud management utilities.
+"""
+
+import os
+import sys
+import json
+import urllib.request
+import urllib.error
+from datetime import datetime
+import lambdalabs_credentials
+
+
+def format_uptime(created_at):
+ """Convert timestamp to human-readable uptime."""
+ 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"
+
+
+def list_instances():
+ """List all Lambda Labs instances."""
+ # Get API key from credentials
+ api_key = lambdalabs_credentials.get_api_key()
+ if not api_key:
+ print(
+ "Error: Lambda Labs API key not found in credentials file", file=sys.stderr
+ )
+ print(
+ "Please configure it with: python3 scripts/lambdalabs_credentials.py set 'your-api-key'",
+ file=sys.stderr,
+ )
+ return 1
+
+ url = "https://cloud.lambdalabs.com/api/v1/instances"
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
+
+ try:
+ req = urllib.request.Request(url, headers=headers)
+ with urllib.request.urlopen(req) as response:
+ data = json.loads(response.read().decode())
+
+ if "data" not in data:
+ print("No instances found or unexpected API response")
+ return 0
+
+ instances = data["data"]
+
+ if not instances:
+ print("No Lambda Labs instances currently running")
+ return 0
+
+ # Print header
+ print("\nLambda Labs Instances:")
+ print("=" * 80)
+ print(
+ f"{'Name':<20} {'Type':<20} {'IP':<15} {'Region':<15} {'Uptime':<10} {'Status'}"
+ )
+ print("-" * 80)
+
+ # Print each instance
+ for instance in instances:
+ name = instance.get("name", "unnamed")
+ instance_type = instance.get("instance_type", {}).get("name", "unknown")
+ ip = instance.get("ip", "pending")
+ region = instance.get("region", {}).get("name", "unknown")
+ status = instance.get("status", "unknown")
+ created_at = instance.get("created", "")
+ uptime = format_uptime(created_at)
+
+ # Highlight kdevops instances
+ if "cgpu" in name or "kdevops" in name.lower():
+ name = f"→ {name}"
+
+ print(
+ f"{name:<20} {instance_type:<20} {ip:<15} {region:<15} {uptime:<10} {status}"
+ )
+
+ print("-" * 80)
+ print(f"Total instances: {len(instances)}")
+
+ # Calculate total cost
+ total_cost = 0
+ for instance in instances:
+ price_cents = instance.get("instance_type", {}).get(
+ "price_cents_per_hour", 0
+ )
+ total_cost += price_cents / 100
+
+ if total_cost > 0:
+ print(f"Total hourly cost: ${total_cost:.2f}/hr")
+ print(f"Daily cost estimate: ${total_cost * 24:.2f}/day")
+
+ print()
+
+ return 0
+
+ except urllib.error.HTTPError as e:
+ error_body = e.read().decode()
+ print(f"Error: HTTP {e.code} - {e.reason}", file=sys.stderr)
+ if error_body:
+ try:
+ error_data = json.loads(error_body)
+ if "error" in error_data:
+ err = error_data["error"]
+ print(f" {err.get('message', 'Unknown error')}", file=sys.stderr)
+ if "suggestion" in err:
+ print(f" Suggestion: {err['suggestion']}", file=sys.stderr)
+ except:
+ print(f" Response: {error_body}", file=sys.stderr)
+ return 1
+ except Exception as e:
+ print(f"Error: {e}", file=sys.stderr)
+ return 1
+
+
+def main():
+ """Main entry point."""
+ # Support JSON output flag
+ if len(sys.argv) > 1 and sys.argv[1] == "--json":
+ # For future: output raw JSON
+ # Get API key from credentials
+ api_key = lambdalabs_credentials.get_api_key()
+ if not api_key:
+ print(
+ json.dumps(
+ {"error": "Lambda Labs API key not found in credentials file"}
+ )
+ )
+ return 1
+
+ url = "https://cloud.lambdalabs.com/api/v1/instances"
+ headers = {
+ "Authorization": f"Bearer {api_key}",
+ "Content-Type": "application/json",
+ }
+
+ try:
+ req = urllib.request.Request(url, headers=headers)
+ with urllib.request.urlopen(req) as response:
+ print(response.read().decode())
+ return 0
+ except Exception as e:
+ print(json.dumps({"error": str(e)}))
+ return 1
+ else:
+ return list_instances()
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/lambdalabs_smart_inference.py b/scripts/lambdalabs_smart_inference.py
new file mode 100755
index 0000000..fa59d76
--- /dev/null
+++ b/scripts/lambdalabs_smart_inference.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: copyleft-next-0.3.1
+
+"""
+Smart inference for Lambda Labs - finds cheapest instance preferring closer regions.
+Algorithm:
+1. Determine user's location from public IP
+2. Find all available instance/region combinations
+3. Group by price tier (instances with same price)
+4. For each price tier, select the closest region
+5. Return the cheapest tier's best region/instance combo
+"""
+
+import json
+import sys
+import urllib.request
+import urllib.error
+from typing import Optional, List, Dict, Tuple
+import math
+
+# Import our credentials module
+sys.path.insert(0, sys.path[0])
+from lambdalabs_credentials import get_api_key
+
+LAMBDALABS_API_BASE = "https://cloud.lambdalabs.com/api/v1"
+
+# Known pricing for Lambda Labs instances (per hour)
+INSTANCE_PRICING = {
+ "gpu_1x_rtx6000": 0.50,
+ "gpu_1x_a10": 0.75,
+ "gpu_1x_a6000": 0.80,
+ "gpu_1x_a100": 1.29,
+ "gpu_1x_a100_sxm4": 1.29,
+ "gpu_1x_a100_pcie": 1.29,
+ "gpu_1x_gh200": 1.49,
+ "gpu_1x_h100_pcie": 2.49,
+ "gpu_1x_h100_sxm5": 3.29,
+ "gpu_2x_a100": 2.58,
+ "gpu_2x_a100_pcie": 2.58,
+ "gpu_2x_a6000": 1.60,
+ "gpu_2x_h100_sxm5": 6.38,
+ "gpu_4x_a100": 5.16,
+ "gpu_4x_a100_pcie": 5.16,
+ "gpu_4x_a6000": 3.20,
+ "gpu_4x_h100_sxm5": 12.36,
+ "gpu_8x_v100": 4.40,
+ "gpu_8x_a100": 10.32,
+ "gpu_8x_a100_40gb": 10.32,
+ "gpu_8x_a100_80gb": 14.32,
+ "gpu_8x_a100_80gb_sxm4": 14.32,
+ "gpu_8x_h100_sxm5": 23.92,
+ "gpu_8x_b200_sxm6": 39.92,
+}
+
+# Approximate region locations (latitude, longitude)
+REGION_LOCATIONS = {
+ "us-east-1": (39.0458, -77.6413), # Virginia
+ "us-west-1": (37.3541, -121.9552), # California (San Jose)
+ "us-west-2": (45.5152, -122.6784), # Oregon
+ "us-west-3": (33.4484, -112.0740), # Arizona
+ "us-tx-1": (30.2672, -97.7431), # Texas (Austin)
+ "us-midwest-1": (41.8781, -87.6298), # Illinois (Chicago)
+ "us-south-1": (33.7490, -84.3880), # Georgia (Atlanta)
+ "us-south-2": (29.7604, -95.3698), # Texas (Houston)
+ "us-south-3": (25.7617, -80.1918), # Florida (Miami)
+ "europe-central-1": (50.1109, 8.6821), # Frankfurt
+ "asia-northeast-1": (35.6762, 139.6503), # Tokyo
+ "asia-south-1": (19.0760, 72.8777), # Mumbai
+ "me-west-1": (25.2048, 55.2708), # Dubai
+ "australia-east-1": (-33.8688, 151.2093), # Sydney
+}
+
+
+def get_user_location() -> Tuple[float, float]:
+ """
+ Get user's approximate location from public IP.
+ Returns (latitude, longitude) tuple.
+ """
+ try:
+ # Try to get location from IP
+ with urllib.request.urlopen("http://ip-api.com/json/", timeout=2) as response:
+ data = json.loads(response.read().decode())
+ if data.get("status") == "success":
+ return (data.get("lat", 39.0458), data.get("lon", -77.6413))
+ except:
+ pass
+
+ # Default to US East Coast if can't determine
+ return (39.0458, -77.6413)
+
+
+def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
+ """
+ Calculate approximate distance between two points using Haversine formula.
+ Returns distance in kilometers.
+ """
+ R = 6371 # Earth's radius in kilometers
+
+ lat1_rad = math.radians(lat1)
+ lat2_rad = math.radians(lat2)
+ delta_lat = math.radians(lat2 - lat1)
+ delta_lon = math.radians(lon2 - lon1)
+
+ a = (
+ math.sin(delta_lat / 2) ** 2
+ + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(delta_lon / 2) ** 2
+ )
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
+
+ return R * c
+
+
+def get_best_instance_and_region() -> Tuple[str, str]:
+ """
+ Find the cheapest available instance, preferring closer regions when same price.
+
+ Returns:
+ (instance_type, region) tuple
+ """
+ api_key = get_api_key()
+ if not api_key:
+ # Return defaults if no API key
+ return ("gpu_1x_a10", "us-west-1")
+
+ # Get user's location
+ user_lat, user_lon = get_user_location()
+
+ headers = {"Authorization": f"Bearer {api_key}", "User-Agent": "kdevops/1.0"}
+ url = f"{LAMBDALABS_API_BASE}/instance-types"
+
+ try:
+ req = urllib.request.Request(url, headers=headers)
+ with urllib.request.urlopen(req) as response:
+ data = json.loads(response.read().decode())
+
+ if "data" not in data:
+ return ("gpu_1x_a10", "us-west-1")
+
+ # Build a map of price -> list of (instance, region, distance) tuples
+ price_tiers = {}
+
+ for instance_type, info in data["data"].items():
+ regions_with_capacity = info.get("regions_with_capacity_available", [])
+ if regions_with_capacity:
+ price = INSTANCE_PRICING.get(instance_type, 999.99)
+
+ for region_info in regions_with_capacity:
+ region = region_info.get("name")
+ if region and region in REGION_LOCATIONS:
+ region_lat, region_lon = REGION_LOCATIONS[region]
+ distance = calculate_distance(
+ user_lat, user_lon, region_lat, region_lon
+ )
+
+ if price not in price_tiers:
+ price_tiers[price] = []
+ price_tiers[price].append((instance_type, region, distance))
+
+ if not price_tiers:
+ # No capacity anywhere
+ return ("gpu_1x_a10", "us-west-1")
+
+ # Sort price tiers by price
+ sorted_prices = sorted(price_tiers.keys())
+
+ # For the cheapest price tier, find the closest region
+ cheapest_price = sorted_prices[0]
+ options = price_tiers[cheapest_price]
+
+ # Sort by distance to find closest
+ options.sort(key=lambda x: x[2])
+ best_instance, best_region, best_distance = options[0]
+
+ return (best_instance, best_region)
+
+ except Exception as e:
+ # On any error, return defaults (west for SF user)
+ return ("gpu_1x_a10", "us-west-1")
+
+
+def main():
+ """Main function for command-line usage."""
+ mode = sys.argv[1] if len(sys.argv) > 1 else "both"
+
+ instance, region = get_best_instance_and_region()
+
+ if mode == "instance":
+ print(instance)
+ elif mode == "region":
+ print(region)
+ else: # both
+ print(f"{instance},{region}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/terraform_list_instances.sh b/scripts/terraform_list_instances.sh
new file mode 100755
index 0000000..0a98363
--- /dev/null
+++ b/scripts/terraform_list_instances.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+# List instances from terraform state
+# This works even when API access is limited
+
+set -e
+
+# Function to detect terraform directory based on cloud provider
+get_terraform_dir() {
+ if [ -f .config ]; then
+ if grep -q "CONFIG_TERRAFORM_LAMBDALABS=y" .config 2>/dev/null; then
+ echo "terraform/lambdalabs"
+ elif grep -q "CONFIG_TERRAFORM_AWS=y" .config 2>/dev/null; then
+ echo "terraform/aws"
+ elif grep -q "CONFIG_TERRAFORM_GCE=y" .config 2>/dev/null; then
+ echo "terraform/gce"
+ elif grep -q "CONFIG_TERRAFORM_AZURE=y" .config 2>/dev/null; then
+ echo "terraform/azure"
+ elif grep -q "CONFIG_TERRAFORM_OCI=y" .config 2>/dev/null; then
+ echo "terraform/oci"
+ elif grep -q "CONFIG_TERRAFORM_OPENSTACK=y" .config 2>/dev/null; then
+ echo "terraform/openstack"
+ else
+ echo ""
+ fi
+ else
+ echo ""
+ fi
+}
+
+# Get terraform directory
+TERRAFORM_DIR=$(get_terraform_dir)
+
+if [ -z "$TERRAFORM_DIR" ]; then
+ echo "No terraform provider configured"
+ exit 1
+fi
+
+if [ ! -d "$TERRAFORM_DIR" ]; then
+ echo "Terraform directory $TERRAFORM_DIR does not exist"
+ exit 1
+fi
+
+cd "$TERRAFORM_DIR"
+
+# Check if terraform is initialized
+if [ ! -d ".terraform" ]; then
+ echo "Terraform not initialized. Run 'make' first."
+ exit 1
+fi
+
+# Check if we have state
+if [ ! -f "terraform.tfstate" ]; then
+ echo "No terraform state file found. No instances deployed."
+ exit 0
+fi
+
+echo "Terraform Managed Instances:"
+echo "============================"
+echo
+
+# Try to get instances from state
+terraform state list 2>/dev/null | grep -E "instance|vm" | while read resource; do
+ echo "Resource: $resource"
+ terraform state show "$resource" 2>/dev/null | grep -E "^\s*(name|ip|ip_address|public_ip|instance_type|region|status|hostname)" | sed 's/^/ /'
+ echo
+done
+
+# If no instances found
+if ! terraform state list 2>/dev/null | grep -qE "instance|vm"; then
+ echo "No instances found in terraform state"
+ echo
+ echo "To deploy instances, run: make bringup"
+fi
+
+# Show outputs if available
+echo
+echo "Terraform Outputs:"
+echo "-----------------"
+terraform output 2>/dev/null || echo "No outputs defined"
diff --git a/scripts/test_lambda_ssh.py b/scripts/test_lambda_ssh.py
new file mode 100644
index 0000000..5034697
--- /dev/null
+++ b/scripts/test_lambda_ssh.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+
+import os
+import json
+import urllib.request
+import urllib.error
+import lambdalabs_credentials
+
+# Get API key from credentials file
+# Get API key from credentials
+api_key = lambdalabs_credentials.get_api_key()
+if not api_key:
+ print("No Lambda Labs API key found in credentials file")
+ print(
+ "Please configure it with: python3 scripts/lambdalabs_credentials.py set 'your-api-key'"
+ )
+ exit(1)
+
+print(f"API Key length: {len(api_key)}")
+print(f"API Key prefix: {api_key[:30]}...")
+
+
+def make_request(endpoint, method="GET", data=None):
+ """Make API request to Lambda Labs"""
+ url = f"https://cloud.lambdalabs.com/api/v1{endpoint}"
+
+ headers = {
+ "Authorization": f"Bearer {api_key}",
+ "User-Agent": "kdevops/1.0",
+ "Accept": "application/json",
+ "Content-Type": "application/json",
+ }
+
+ req_data = None
+ if data and method in ["POST", "PUT", "PATCH", "DELETE"]:
+ req_data = json.dumps(data).encode("utf-8")
+
+ try:
+ req = urllib.request.Request(url, headers=headers, data=req_data, method=method)
+ with urllib.request.urlopen(req) as response:
+ content = response.read().decode()
+ if content:
+ return json.loads(content)
+ return {"status": "success"}
+ except urllib.error.HTTPError as e:
+ print(f"\nHTTP Error {e.code} for {method} {endpoint}")
+ try:
+ error_content = e.read().decode()
+ error_data = json.loads(error_content)
+ print(f"Error: {json.dumps(error_data, indent=2)}")
+ except:
+ print(f"Error response: {error_content[:500]}")
+ return None
+ except Exception as e:
+ print(f"\nException for {method} {endpoint}: {e}")
+ return None
+
+
+# Test different endpoints
+print("\n1. Testing /instances endpoint...")
+result = make_request("/instances")
+if result and "data" in result:
+ print(f" ✓ Instances: Found {len(result['data'])} instances")
+else:
+ print(" ✗ Instances endpoint failed")
+
+print("\n2. Testing /instance-types endpoint...")
+result = make_request("/instance-types")
+if result and "data" in result:
+ print(f" ✓ Instance types: Found {len(result['data'])} types")
+else:
+ print(" ✗ Instance types endpoint failed")
+
+print("\n3. Testing /ssh-keys endpoint...")
+result = make_request("/ssh-keys")
+if result:
+ print(f" ✓ SSH Keys endpoint works!")
+ if "data" in result:
+ keys = result["data"]
+ print(f" Found {len(keys)} SSH keys:")
+ for key in keys:
+ if isinstance(key, dict):
+ name = key.get("name", key.get("id", "unknown"))
+ print(f" - {name}")
+ else:
+ print(f" - {key}")
+
+ # Try to delete keys
+ print("\n4. Attempting to delete SSH keys...")
+ for key in keys:
+ if isinstance(key, dict):
+ key_name = key.get("name", key.get("id"))
+ else:
+ key_name = key
+
+ if key_name:
+ print(f" Deleting key: {key_name}")
+ delete_result = make_request(f"/ssh-keys/{key_name}", method="DELETE")
+ if delete_result is not None:
+ print(f" ✓ Deleted {key_name}")
+ else:
+ print(f" ✗ Failed to delete {key_name}")
+ else:
+ print(" Response:", json.dumps(result, indent=2))
+else:
+ print(" ✗ SSH Keys endpoint failed")
+
+print("\n5. Testing if keys were deleted...")
+result = make_request("/ssh-keys")
+if result and "data" in result:
+ print(f" Remaining keys: {len(result['data'])}")
diff --git a/scripts/update_lambdalabs_instance.sh b/scripts/update_lambdalabs_instance.sh
new file mode 100755
index 0000000..219e425
--- /dev/null
+++ b/scripts/update_lambdalabs_instance.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+echo "Lambda Labs Instance Availability Update"
+echo "========================================"
+echo
+echo "The configured instance type 'gpu_1x_a10' is currently unavailable."
+echo
+echo "Available options:"
+echo
+echo "1. Use gpu_1x_a100_sxm4 in us-east-1 (Virginia) - $1.29/hr"
+echo " To use this, run:"
+echo " make menuconfig"
+echo " Then navigate to:"
+echo " - Terraform -> Lambda Labs cloud provider"
+echo " - Change 'Lambda Labs region' to 'us-east-1'"
+echo " - Change 'Lambda Labs instance type' to 'gpu_1x_a100_sxm4'"
+echo
+echo "2. Use gpu_8x_a100 in us-west-1 (California) - $10.32/hr"
+echo " To use this, run:"
+echo " make menuconfig"
+echo " Then navigate to:"
+echo " - Terraform -> Lambda Labs cloud provider"
+echo " - Change 'Lambda Labs instance type' to 'gpu_8x_a100'"
+echo
+echo "3. Wait for gpu_1x_a10 to become available"
+echo " Check availability at: https://cloud.lambdalabs.com/"
+echo
+echo "Current configuration:"
+grep "CONFIG_TERRAFORM_LAMBDALABS_REGION\|CONFIG_TERRAFORM_LAMBDALABS_INSTANCE_TYPE" .config 2>/dev/null || echo " Configuration not found"
--
2.50.1
next prev parent reply other threads:[~2025-08-27 21:29 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-27 21:28 [PATCH v2 00/10] terraform: add Lambda Labs cloud provider support with dynamic API-driven configuration Luis Chamberlain
2025-08-27 21:28 ` [PATCH v2 01/10] gitignore: add entries for Lambda Labs dynamic configuration Luis Chamberlain
2025-08-27 21:28 ` [PATCH v2 02/10] scripts: add Lambda Labs Python API library Luis Chamberlain
2025-08-28 18:59 ` Chuck Lever
2025-08-28 19:33 ` Luis Chamberlain
2025-08-28 20:00 ` Chuck Lever
2025-08-28 20:03 ` Luis Chamberlain
2025-08-28 20:13 ` Chuck Lever
2025-08-28 20:16 ` Luis Chamberlain
2025-08-29 11:24 ` Luis Chamberlain
2025-08-29 13:48 ` Chuck Lever
2025-08-27 21:28 ` [PATCH v2 03/10] scripts: add Lambda Labs credentials management Luis Chamberlain
2025-08-27 21:28 ` [PATCH v2 04/10] scripts: add Lambda Labs SSH key management utilities Luis Chamberlain
2025-08-27 21:28 ` [PATCH v2 05/10] kconfig: add dynamic cloud provider configuration infrastructure Luis Chamberlain
2025-08-27 21:28 ` [PATCH v2 06/10] terraform/lambdalabs: add Kconfig structure for Lambda Labs Luis Chamberlain
2025-08-27 21:28 ` [PATCH v2 07/10] terraform/lambdalabs: add terraform provider implementation Luis Chamberlain
2025-08-27 21:28 ` [PATCH v2 08/10] ansible/terraform: integrate Lambda Labs into build system Luis Chamberlain
2025-08-27 21:29 ` Luis Chamberlain [this message]
2025-08-27 21:29 ` [PATCH v2 10/10] terraform: enable Lambda Labs cloud provider in menus 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=20250827212902.4021990-10-mcgrof@kernel.org \
--to=mcgrof@kernel.org \
--cc=cel@kernel.org \
--cc=da.gomez@kruces.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