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 05/10] kconfig: add dynamic cloud provider configuration infrastructure
Date: Wed, 27 Aug 2025 14:28:56 -0700 [thread overview]
Message-ID: <20250827212902.4021990-6-mcgrof@kernel.org> (raw)
In-Reply-To: <20250827212902.4021990-1-mcgrof@kernel.org>
Introduce a new dynamic configuration system that queries cloud provider
APIs to generate real-time Kconfig options. This allows configuration
menus to show current availability and capacity directly from the cloud.
The system adds:
- Python script to query APIs and generate Kconfig files
- New 'make cloud-config' target for cloud-specific updates
- Integration with existing 'make dynconfig' infrastructure
- CLOUD_INITIALIZED marker to detect cloud setup state
- Automatic fallback to static defaults when API unavailable
This sets up the framework for API-driven configuration but doesn't
enable any specific providers yet. The architecture supports future
extension to AWS, Azure, GCE, and other cloud providers.
Generated-by: Claude AI
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
---
kconfigs/Kconfig.bringup | 5 +
scripts/dynamic-cloud-kconfig.Makefile | 44 +++++
scripts/dynamic-kconfig.Makefile | 2 +
scripts/generate_cloud_configs.py | 223 +++++++++++++++++++++++++
scripts/lambdalabs_ssh_keys.py | 3 +-
5 files changed, 276 insertions(+), 1 deletion(-)
create mode 100644 scripts/dynamic-cloud-kconfig.Makefile
create mode 100755 scripts/generate_cloud_configs.py
diff --git a/kconfigs/Kconfig.bringup b/kconfigs/Kconfig.bringup
index 8caf07b..b64ba50 100644
--- a/kconfigs/Kconfig.bringup
+++ b/kconfigs/Kconfig.bringup
@@ -9,8 +9,13 @@ config KDEVOPS_ENABLE_NIXOS
bool
output yaml
+config CLOUD_INITIALIZED
+ bool
+ default $(shell, test -f .cloud.initialized && echo y || echo n) = "y"
+
choice
prompt "Node bring up method"
+ default TERRAFORM if CLOUD_INITIALIZED
default GUESTFS
config GUESTFS
diff --git a/scripts/dynamic-cloud-kconfig.Makefile b/scripts/dynamic-cloud-kconfig.Makefile
new file mode 100644
index 0000000..cc0a6b8
--- /dev/null
+++ b/scripts/dynamic-cloud-kconfig.Makefile
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: copyleft-next-0.3.1
+# Dynamic cloud provider Kconfig generation
+
+DYNAMIC_CLOUD_KCONFIG :=
+DYNAMIC_CLOUD_KCONFIG_ARGS :=
+
+# Lambda Labs dynamic configuration
+LAMBDALABS_KCONFIG_DIR := terraform/lambdalabs/kconfigs
+LAMBDALABS_KCONFIG_COMPUTE := $(LAMBDALABS_KCONFIG_DIR)/Kconfig.compute.generated
+LAMBDALABS_KCONFIG_LOCATION := $(LAMBDALABS_KCONFIG_DIR)/Kconfig.location.generated
+LAMBDALABS_KCONFIG_IMAGES := $(LAMBDALABS_KCONFIG_DIR)/Kconfig.images.generated
+
+LAMBDALABS_KCONFIGS := $(LAMBDALABS_KCONFIG_COMPUTE) $(LAMBDALABS_KCONFIG_LOCATION) $(LAMBDALABS_KCONFIG_IMAGES)
+
+# Individual Lambda Labs targets are now handled by generate_cloud_configs.py
+cloud-config-lambdalabs:
+ $(Q)python3 scripts/generate_cloud_configs.py
+
+# Clean Lambda Labs generated files
+clean-cloud-config-lambdalabs:
+ $(Q)rm -f $(LAMBDALABS_KCONFIGS)
+
+DYNAMIC_CLOUD_KCONFIG += cloud-config-lambdalabs
+
+cloud-config-help:
+ @echo "Cloud-specific dynamic kconfig targets:"
+ @echo "cloud-config - generates all cloud provider dynamic kconfig content"
+ @echo "cloud-config-lambdalabs - generates Lambda Labs dynamic kconfig content"
+ @echo "clean-cloud-config - removes all generated cloud kconfig files"
+ @echo "cloud-list-all - list all cloud instances for configured provider"
+
+HELP_TARGETS += cloud-config-help
+
+cloud-config:
+ $(Q)python3 scripts/generate_cloud_configs.py
+
+clean-cloud-config: clean-cloud-config-lambdalabs
+ $(Q)echo "Cleaned all cloud provider dynamic Kconfig files."
+
+cloud-list-all:
+ $(Q)chmod +x scripts/cloud_list_all.sh
+ $(Q)scripts/cloud_list_all.sh
+
+PHONY += cloud-config cloud-config-lambdalabs clean-cloud-config clean-cloud-config-lambdalabs cloud-config-help cloud-list-all
diff --git a/scripts/dynamic-kconfig.Makefile b/scripts/dynamic-kconfig.Makefile
index b6c0e43..bab83e3 100644
--- a/scripts/dynamic-kconfig.Makefile
+++ b/scripts/dynamic-kconfig.Makefile
@@ -6,6 +6,7 @@ DYNAMIC_KCONFIG_PCIE_ARGS :=
HELP_TARGETS += dynamic-kconfig-help
include $(TOPDIR)/scripts/dynamic-pci-kconfig.Makefile
+include $(TOPDIR)/scripts/dynamic-cloud-kconfig.Makefile
ANSIBLE_EXTRA_ARGS += $(DYNAMIC_KCONFIG_PCIE_ARGS)
@@ -19,5 +20,6 @@ PHONY += dynamic-kconfig-help
dynconfig:
$(Q)$(MAKE) dynconfig-pci
+ $(Q)$(MAKE) cloud-config
PHONY += dynconfig
diff --git a/scripts/generate_cloud_configs.py b/scripts/generate_cloud_configs.py
new file mode 100755
index 0000000..294a1d9
--- /dev/null
+++ b/scripts/generate_cloud_configs.py
@@ -0,0 +1,223 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: copyleft-next-0.3.1
+
+"""
+Generate dynamic cloud configurations for all supported providers.
+Provides a summary of available options and pricing.
+"""
+
+import os
+import sys
+import subprocess
+import json
+import urllib.request
+import urllib.error
+from typing import Dict, List, Optional, Tuple
+
+# 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
+
+
+def get_lambdalabs_summary() -> Tuple[bool, str]:
+ """
+ Get a summary of Lambda Labs configurations.
+ Returns (success, summary_string)
+ """
+ api_key = get_api_key_from_credentials()
+ if not api_key:
+ return False, "Lambda Labs: API key not set - using defaults"
+
+ try:
+ # Get instance types with capacity
+ headers = {"Authorization": f"Bearer {api_key}", "User-Agent": "kdevops/1.0"}
+ req = urllib.request.Request(
+ "https://cloud.lambdalabs.com/api/v1/instance-types", headers=headers
+ )
+
+ with urllib.request.urlopen(req) as response:
+ data = json.loads(response.read().decode())
+
+ if "data" not in data:
+ return False, "Lambda Labs: Invalid API response"
+
+ # Get pricing data
+ pricing = {
+ "gpu_1x_gh200": 1.49,
+ "gpu_1x_h100_sxm": 3.29,
+ "gpu_1x_h100_pcie": 2.49,
+ "gpu_1x_a100": 1.29,
+ "gpu_1x_a100_sxm": 1.29,
+ "gpu_1x_a100_pcie": 1.29,
+ "gpu_1x_a10": 0.75,
+ "gpu_1x_a6000": 0.80,
+ "gpu_1x_rtx6000": 0.50,
+ "gpu_1x_quadro_rtx_6000": 0.50,
+ "gpu_2x_h100_sxm": 6.38,
+ "gpu_2x_a100": 2.58,
+ "gpu_2x_a100_pcie": 2.58,
+ "gpu_2x_a6000": 1.60,
+ "gpu_4x_h100_sxm": 12.36,
+ "gpu_4x_a100": 5.16,
+ "gpu_4x_a100_pcie": 5.16,
+ "gpu_4x_a6000": 3.20,
+ "gpu_8x_b200_sxm": 39.92,
+ "gpu_8x_h100_sxm": 23.92,
+ "gpu_8x_a100_80gb": 14.32,
+ "gpu_8x_a100_80gb_sxm": 14.32,
+ "gpu_8x_a100": 10.32,
+ "gpu_8x_a100_40gb": 10.32,
+ "gpu_8x_v100": 4.40,
+ }
+
+ # Count available instances and get price range
+ available_count = 0
+ total_count = len(data["data"])
+ available_prices = []
+ all_regions = set()
+
+ for instance_type, info in data["data"].items():
+ regions = info.get("regions_with_capacity_available", [])
+ if regions:
+ available_count += 1
+ if instance_type in pricing:
+ available_prices.append(pricing[instance_type])
+ for r in regions:
+ all_regions.add(r["name"])
+
+ # Format summary
+ if available_prices:
+ min_price = min(available_prices)
+ max_price = max(available_prices)
+ price_range = f"${min_price:.2f}-${max_price:.2f}/hr"
+ else:
+ price_range = "pricing varies"
+
+ region_count = len(all_regions)
+
+ return (
+ True,
+ f"Lambda Labs: {available_count}/{total_count} GPU types available, {region_count} regions, {price_range}",
+ )
+
+ except urllib.error.HTTPError as e:
+ if e.code == 403:
+ return False, "Lambda Labs: API key invalid - using defaults"
+ else:
+ return False, f"Lambda Labs: API error {e.code}"
+ except Exception as e:
+ return False, f"Lambda Labs: Error - {str(e)}"
+
+
+def generate_lambdalabs_configs(output_dir: str) -> bool:
+ """Generate Lambda Labs Kconfig files."""
+ try:
+ # Run the lambdalabs_api.py script
+ result = subprocess.run(
+ [sys.executable, "scripts/lambdalabs_api.py", "all", output_dir],
+ capture_output=True,
+ text=True,
+ )
+
+ if result.returncode != 0:
+ print(
+ f" ⚠ Error generating Lambda Labs configs: {result.stderr}",
+ file=sys.stderr,
+ )
+ return False
+
+ return True
+ except Exception as e:
+ print(f" ⚠ Error: {e}", file=sys.stderr)
+ return False
+
+
+def generate_aws_configs(output_dir: str) -> bool:
+ """
+ Generate AWS Kconfig files (placeholder for future implementation).
+ """
+ # For now, just return True as AWS uses static configs
+ return True
+
+
+def generate_azure_configs(output_dir: str) -> bool:
+ """
+ Generate Azure Kconfig files (placeholder for future implementation).
+ """
+ # For now, just return True as Azure uses static configs
+ return True
+
+
+def generate_gce_configs(output_dir: str) -> bool:
+ """
+ Generate GCE Kconfig files (placeholder for future implementation).
+ """
+ # For now, just return True as GCE uses static configs
+ return True
+
+
+def main():
+ """Main function to generate all cloud configurations."""
+ print("Generating dynamic cloud configurations based on latest data...")
+ print()
+
+ # Create .cloud.initialized marker file to signal cloud support is configured
+ # This will be used by Kconfig to set intelligent defaults
+ try:
+ with open(".cloud.initialized", "w") as f:
+ f.write("# This file indicates cloud support has been initialized\n")
+ f.write("# Created by 'make cloud-config'\n")
+ f.write("# Kconfig will use this to set cloud-related defaults\n")
+ except Exception as e:
+ print(f" ⚠ Warning: Could not create .cloud.initialized: {e}", file=sys.stderr)
+
+ # Get summaries for each provider
+ providers = []
+
+ # Lambda Labs
+ success, summary = get_lambdalabs_summary()
+ providers.append(("Lambda Labs", success, summary))
+
+ # Future providers (placeholders for when we add dynamic support)
+ # When these providers get dynamic config support, they would show:
+ # providers.append(("AWS", True, "AWS: 100+ instance types, 26 regions, $0.01-$40.00/hr"))
+ # providers.append(("Azure", True, "Azure: 200+ VM sizes, 60+ regions, $0.01-$50.00/hr"))
+ # providers.append(("GCE", True, "GCE: 50+ machine types, 35 regions, $0.01-$30.00/hr"))
+
+ # Print summaries
+ for provider, success, summary in providers:
+ if success:
+ print(f" ✓ {summary}")
+ else:
+ print(f" ⚠ {summary}")
+
+ print()
+
+ # Generate configurations for each provider
+ configs_generated = []
+
+ # Lambda Labs
+ print(" • Generating Lambda Labs configurations...")
+ if generate_lambdalabs_configs("terraform/lambdalabs/kconfigs"):
+ configs_generated.append("Lambda Labs")
+ print(" ✓ Instance types, regions, and capacity information updated")
+ else:
+ print(" ⚠ Using default configurations")
+
+ # Future providers would go here
+ # print(" • AWS configurations (static)...")
+ # configs_generated.append("AWS")
+
+ print()
+
+ if configs_generated:
+ print(f"✓ Cloud configurations ready for: {', '.join(configs_generated)}")
+ print(" Run 'make menuconfig' to select your cloud provider and options")
+ else:
+ print("⚠ No dynamic configurations were generated, using defaults")
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/lambdalabs_ssh_keys.py b/scripts/lambdalabs_ssh_keys.py
index d4caede..2fa9880 100755
--- a/scripts/lambdalabs_ssh_keys.py
+++ b/scripts/lambdalabs_ssh_keys.py
@@ -270,7 +270,8 @@ def main():
print("Error: Lambda Labs API key not found", file=sys.stderr)
print("Please configure your API key:", file=sys.stderr)
print(
- " python3 scripts/lambdalabs_credentials.py set 'your-api-key'", file=sys.stderr
+ " python3 scripts/lambdalabs_credentials.py set 'your-api-key'",
+ file=sys.stderr,
)
sys.exit(1)
--
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 ` Luis Chamberlain [this message]
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 ` [PATCH v2 09/10] scripts: add Lambda Labs testing and debugging utilities Luis Chamberlain
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-6-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