From 241757136c82b1bb6c616ff3cadc71bff08210e0 Mon Sep 17 00:00:00 2001 From: Honglin Cao Date: Fri, 30 Jan 2026 16:59:25 -0500 Subject: [PATCH 1/5] add script for interacting with user vault --- scripts/user_vault/README.md | 169 ++++++++++++++++++++++++++ scripts/user_vault/get_vault_items.py | 111 +++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 scripts/user_vault/README.md create mode 100644 scripts/user_vault/get_vault_items.py diff --git a/scripts/user_vault/README.md b/scripts/user_vault/README.md new file mode 100644 index 0000000..181b84c --- /dev/null +++ b/scripts/user_vault/README.md @@ -0,0 +1,169 @@ +# User Vault Scripts + +Scripts for managing user vault (secrets) in CentML Platform. + +## Overview + +The CentML User Vault is a secure storage system for sensitive information that can be used across your deployments. This includes environment variables, API tokens, SSH keys, and certificates. These scripts allow you to view and manage your vault items from the command line. + +## Prerequisites + +### 1. Install the centml package + +From the repository root directory: + +```bash +pip install -e ./ +``` + +Or install directly from GitHub: + +```bash +pip install git+https://github.com/CentML/centml-python-client.git@main +``` + +### 2. Authenticate with CentML + +Login to your CentML account: + +```bash +centml login +``` + +This will open a browser window for authentication. Once completed, your credentials will be stored locally. + +## Available Scripts + +### get_vault_items.py + +Retrieves and displays all items stored in your CentML vault. + +#### Supported Vault Types + +| Type | Description | Example Use Case | +|------|-------------|------------------| +| `env_vars` | Environment variables | Database URLs, API endpoints | +| `ssh_keys` | SSH keys | Git repository access | +| `bearer_tokens` | Bearer tokens | Service authentication | +| `access_tokens` | Access tokens | HuggingFace tokens, Weights & Biases API keys | +| `certificates` | Certificates | TLS/SSL certificates | + +#### Usage + +Run the script from the `scripts/user_vault` directory: + +```bash +cd scripts/user_vault +python get_vault_items.py [OPTIONS] +``` + +#### Command Line Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--type TYPE` | Filter results by vault type (see supported types above) | Show all types | +| `--search QUERY` | Filter items by key name (case-sensitive substring match) | No filter | +| `--show-values` | Display the actual secret values | Keys only | +| `--help` | Show help message and exit | - | + +#### Examples + +**List all vault items (keys only):** + +```bash +python get_vault_items.py +``` + +**List only environment variables:** + +```bash +python get_vault_items.py --type env_vars +``` + +**List only access tokens (e.g., HuggingFace tokens):** + +```bash +python get_vault_items.py --type access_tokens +``` + +**Search for items containing "HF" in the key name:** + +```bash +python get_vault_items.py --search HF +``` + +**Show all items with their values:** + +```bash +python get_vault_items.py --show-values +``` + +**Combine multiple options:** + +```bash +python get_vault_items.py --type env_vars --show-values --search DATABASE +``` + +#### Example Output + +Without `--show-values`: + +``` +Found 5 vault item(s) + +================================================== +Type: access_tokens (2 item(s)) +================================================== + HF_TOKEN + WANDB_API_KEY + +================================================== +Type: env_vars (3 item(s)) +================================================== + API_KEY + DATABASE_URL + MY_SECRET +``` + +With `--show-values`: + +``` +Found 5 vault item(s) + +================================================== +Type: access_tokens (2 item(s)) +================================================== + HF_TOKEN: hf_xxxxxxxxxxxxxxxxxxxx + WANDB_API_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +================================================== +Type: env_vars (3 item(s)) +================================================== + API_KEY: sk-xxxxxxxxxxxxxxxx + DATABASE_URL: postgresql://user:pass@host:5432/db + MY_SECRET: my-secret-value +``` + +## Troubleshooting + +### Authentication Error + +If you see an authentication error, try logging in again: + +```bash +centml login +``` + +### Module Not Found + +If you see `ModuleNotFoundError`, ensure you have installed the centml package: + +```bash +pip install -e ./ +``` + +### No Items Found + +If the script returns "No vault items found", verify that: +1. You are logged into the correct CentML account +2. You have created vault items in the CentML web UI or via API diff --git a/scripts/user_vault/get_vault_items.py b/scripts/user_vault/get_vault_items.py new file mode 100644 index 0000000..98c906b --- /dev/null +++ b/scripts/user_vault/get_vault_items.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +Script to retrieve all items from a user's vault (secrets). + +This script allows you to view all secrets stored in your CentML vault, +including environment variables, SSH keys, bearer tokens, access tokens, +and certificates. +""" + +from typing import Optional + +import click + +from centml.sdk.api import get_centml_client +from platform_api_python_client import UserVaultType + + +def get_vault_items( + vault_type: Optional[UserVaultType] = None, + search_query: Optional[str] = None, +): + """Retrieve items from user's vault.""" + with get_centml_client() as client: + response = client._api.get_all_user_vault_items_endpoint_user_vault_get( + type=vault_type, + search_query=search_query, + ) + return response.results + + +def display_vault_items(items, show_values: bool = False): + """Display vault items grouped by type.""" + if not items: + click.echo("No vault items found.") + return + + # Group items by type + grouped = {} + for item in items: + vault_type = item.type + if vault_type not in grouped: + grouped[vault_type] = [] + grouped[vault_type].append(item) + + click.echo(f"\nFound {len(items)} vault item(s)\n") + + for vault_type, type_items in sorted(grouped.items(), key=lambda x: x[0]): + click.echo(f"{'='*50}") + click.echo(f"Type: {vault_type} ({len(type_items)} item(s))") + click.echo(f"{'='*50}") + + for item in sorted(type_items, key=lambda x: x.key): + if show_values and item.value is not None: + click.echo(f" {item.key}: {item.value}") + else: + click.echo(f" {item.key}") + + click.echo("") + + +@click.command() +@click.option( + "--type", + "vault_type", + type=click.Choice([t.value for t in UserVaultType], case_sensitive=False), + help="Filter by vault type (env_vars, ssh_keys, bearer_tokens, access_tokens, certificates)", +) +@click.option( + "--search", + "search_query", + type=str, + help="Search query to filter items by key", +) +@click.option( + "--show-values", + is_flag=True, + default=False, + help="Show vault item values", +) +def main(vault_type: Optional[str], search_query: Optional[str], show_values: bool): + """Retrieve all items from user's vault (secrets). + + This script uses the centml CLI authentication, + so make sure you are logged in to centml CLI before running this script. + + \b + Examples: + # Get all vault items + python get_vault_items.py + + # Get only environment variables + python get_vault_items.py --type env_vars + + # Search for items containing 'HF' + python get_vault_items.py --search HF + + # Show values + python get_vault_items.py --show-values + """ + type_enum = UserVaultType(vault_type) if vault_type else None + + items = get_vault_items( + vault_type=type_enum, + search_query=search_query, + ) + + display_vault_items(items, show_values=show_values) + + +if __name__ == "__main__": + main() From c2c084442b6ba3ff63aa326ccf574eed23a237cb Mon Sep 17 00:00:00 2001 From: Honglin Cao Date: Fri, 30 Jan 2026 17:05:29 -0500 Subject: [PATCH 2/5] ruff format --- scripts/user_vault/get_vault_items.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/user_vault/get_vault_items.py b/scripts/user_vault/get_vault_items.py index 98c906b..eee23c5 100644 --- a/scripts/user_vault/get_vault_items.py +++ b/scripts/user_vault/get_vault_items.py @@ -45,9 +45,9 @@ def display_vault_items(items, show_values: bool = False): click.echo(f"\nFound {len(items)} vault item(s)\n") for vault_type, type_items in sorted(grouped.items(), key=lambda x: x[0]): - click.echo(f"{'='*50}") + click.echo(f"{'=' * 50}") click.echo(f"Type: {vault_type} ({len(type_items)} item(s))") - click.echo(f"{'='*50}") + click.echo(f"{'=' * 50}") for item in sorted(type_items, key=lambda x: x.key): if show_values and item.value is not None: From d13bdf563f057b77feff5aadde75461a52d436f8 Mon Sep 17 00:00:00 2001 From: Honglin Cao Date: Fri, 30 Jan 2026 17:06:27 -0500 Subject: [PATCH 3/5] ruff format login.py --- centml/cli/login.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/centml/cli/login.py b/centml/cli/login.py index dd4c7ed..b74c0f0 100644 --- a/centml/cli/login.py +++ b/centml/cli/login.py @@ -26,7 +26,11 @@ def generate_pkce_pair(): verifier = secrets.token_urlsafe(64) - challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=") + challenge = ( + base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()) + .decode() + .rstrip("=") + ) return verifier, challenge @@ -59,9 +63,7 @@ def do_GET(self):

You can now close this tab and continue in the CLI.

- """.encode( - "utf-8" - ) + """.encode("utf-8") ) def log_message(self, format, *args): @@ -102,14 +104,18 @@ def login(token_file): else: click.echo("Logging into CentML...") - choice = click.confirm("Do you want to log in with your browser now?", default=True) + choice = click.confirm( + "Do you want to log in with your browser now?", default=True + ) if choice: try: # PKCE Flow code_verifier, code_challenge = generate_pkce_pair() auth_url = build_auth_url(CLIENT_ID, REDIRECT_URI, code_challenge) click.echo("A browser window will open for you to authenticate.") - click.echo("If it doesn't open automatically, you can copy and paste this URL:") + click.echo( + "If it doesn't open automatically, you can copy and paste this URL:" + ) click.echo(f" {auth_url}\n") webbrowser.open(auth_url) click.echo("Waiting for authentication...") @@ -121,9 +127,13 @@ def login(token_file): click.echo("Login failed. Please try again.") else: cred = { - key: response_dict[key] for key in ("access_token", "refresh_token") if key in response_dict + key: response_dict[key] + for key in ("access_token", "refresh_token") + if key in response_dict } - os.makedirs(os.path.dirname(settings.CENTML_CRED_FILE_PATH), exist_ok=True) + os.makedirs( + os.path.dirname(settings.CENTML_CRED_FILE_PATH), exist_ok=True + ) with open(settings.CENTML_CRED_FILE_PATH, "w") as f: json.dump(cred, f) click.echo("✅ Login successful") From 952b261606fecfceee40dd7392366dcf1bcdea82 Mon Sep 17 00:00:00 2001 From: Honglin Cao Date: Fri, 30 Jan 2026 17:07:12 -0500 Subject: [PATCH 4/5] black format --- centml/cli/cluster.py | 1 - centml/cli/login.py | 29 +++++++---------------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/centml/cli/cluster.py b/centml/cli/cluster.py index 7e8a16c..0fd8a09 100644 --- a/centml/cli/cluster.py +++ b/centml/cli/cluster.py @@ -6,7 +6,6 @@ from centml.sdk import DeploymentType, DeploymentStatus, ServiceStatus, ApiException, HardwareInstanceResponse from centml.sdk.api import get_centml_client - # convert deployment type enum to a user friendly name depl_type_to_name_map = { DeploymentType.INFERENCE: "inference", diff --git a/centml/cli/login.py b/centml/cli/login.py index b74c0f0..49a5955 100644 --- a/centml/cli/login.py +++ b/centml/cli/login.py @@ -14,7 +14,6 @@ from centml.sdk import auth from centml.sdk.config import settings - CLIENT_ID = settings.CENTML_WORKOS_CLIENT_ID SERVER_HOST = "127.0.0.1" SERVER_PORT = 57983 @@ -26,11 +25,7 @@ def generate_pkce_pair(): verifier = secrets.token_urlsafe(64) - challenge = ( - base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()) - .decode() - .rstrip("=") - ) + challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=") return verifier, challenge @@ -55,16 +50,14 @@ def do_GET(self): self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() - self.wfile.write( - """ + self.wfile.write("""

Succesfully logged into CentML CLI

You can now close this tab and continue in the CLI.

- """.encode("utf-8") - ) + """.encode("utf-8")) def log_message(self, format, *args): # Override this to suppress logging @@ -104,18 +97,14 @@ def login(token_file): else: click.echo("Logging into CentML...") - choice = click.confirm( - "Do you want to log in with your browser now?", default=True - ) + choice = click.confirm("Do you want to log in with your browser now?", default=True) if choice: try: # PKCE Flow code_verifier, code_challenge = generate_pkce_pair() auth_url = build_auth_url(CLIENT_ID, REDIRECT_URI, code_challenge) click.echo("A browser window will open for you to authenticate.") - click.echo( - "If it doesn't open automatically, you can copy and paste this URL:" - ) + click.echo("If it doesn't open automatically, you can copy and paste this URL:") click.echo(f" {auth_url}\n") webbrowser.open(auth_url) click.echo("Waiting for authentication...") @@ -127,13 +116,9 @@ def login(token_file): click.echo("Login failed. Please try again.") else: cred = { - key: response_dict[key] - for key in ("access_token", "refresh_token") - if key in response_dict + key: response_dict[key] for key in ("access_token", "refresh_token") if key in response_dict } - os.makedirs( - os.path.dirname(settings.CENTML_CRED_FILE_PATH), exist_ok=True - ) + os.makedirs(os.path.dirname(settings.CENTML_CRED_FILE_PATH), exist_ok=True) with open(settings.CENTML_CRED_FILE_PATH, "w") as f: json.dump(cred, f) click.echo("✅ Login successful") From 5f0210c6efabc2d4a154b631a5abc6aedff968e8 Mon Sep 17 00:00:00 2001 From: Honglin Cao Date: Fri, 30 Jan 2026 17:08:12 -0500 Subject: [PATCH 5/5] format --- examples/sdk/create_cserve.py | 4 +--- examples/sdk/create_inference.py | 1 + examples/sdk/get_deployment_usage.py | 12 ++++++------ scripts/data_collection.py | 9 +++++---- scripts/timer.py | 1 + scripts/user_vault/get_vault_items.py | 27 +++++---------------------- 6 files changed, 19 insertions(+), 35 deletions(-) diff --git a/examples/sdk/create_cserve.py b/examples/sdk/create_cserve.py index 086fe4d..133d978 100644 --- a/examples/sdk/create_cserve.py +++ b/examples/sdk/create_cserve.py @@ -36,9 +36,7 @@ def get_default_cserve_config(cclient, name, model): def main(): with get_centml_client() as cclient: ### Get the configurations for the Qwen model - qwen_config = get_fastest_cserve_config( - cclient, name="qwen-fastest", model="Qwen/Qwen2-VL-7B-Instruct" - ) + qwen_config = get_fastest_cserve_config(cclient, name="qwen-fastest", model="Qwen/Qwen2-VL-7B-Instruct") # qwen_config = get_default_cserve_config(cclient, name="qwen-default", model="Qwen/Qwen2-VL-7B-Instruct") ### Modify the recipe if necessary diff --git a/examples/sdk/create_inference.py b/examples/sdk/create_inference.py index 5531c72..5bbe365 100644 --- a/examples/sdk/create_inference.py +++ b/examples/sdk/create_inference.py @@ -38,5 +38,6 @@ def main(): cclient.delete(deployment.id) ''' + if __name__ == "__main__": main() diff --git a/examples/sdk/get_deployment_usage.py b/examples/sdk/get_deployment_usage.py index 58700df..2de80d1 100644 --- a/examples/sdk/get_deployment_usage.py +++ b/examples/sdk/get_deployment_usage.py @@ -3,7 +3,6 @@ from centml.sdk.api import get_centml_client from centml.sdk import Metric - HOUR_IN_SECONDS = 60 * 60 DAY_IN_SECONDS = 24 * HOUR_IN_SECONDS MAX_DATA_POINTS = 10_000 @@ -17,22 +16,23 @@ def get_step_size(start_time_in_seconds: int, end_time_in_seconds: int) -> int: # 2 days to 7 days: 5m elif time_delta_in_seconds <= 7 * DAY_IN_SECONDS: return 5 * 60 - # 7 days to 14 days: 10m + # 7 days to 14 days: 10m elif time_delta_in_seconds <= 14 * DAY_IN_SECONDS: return 10 * 60 - # 14 days to 30 days: 30m + # 14 days to 30 days: 30m elif time_delta_in_seconds <= 30 * DAY_IN_SECONDS: return 30 * 60 - # 30 days to 60 days: 1 hour + # 30 days to 60 days: 1 hour elif time_delta_in_seconds <= 60 * DAY_IN_SECONDS: return HOUR_IN_SECONDS - # 60 days to 90 days: 2 hours + # 60 days to 90 days: 2 hours elif time_delta_in_seconds <= 90 * DAY_IN_SECONDS: return 2 * HOUR_IN_SECONDS - # 90+ days: 3 hours (This catches all ranges greater than 90 days) + # 90+ days: 3 hours (This catches all ranges greater than 90 days) else: return 3 * HOUR_IN_SECONDS + def main(): with get_centml_client() as cclient: start_time = 1752084581 diff --git a/scripts/data_collection.py b/scripts/data_collection.py index 39c4531..4acdf87 100644 --- a/scripts/data_collection.py +++ b/scripts/data_collection.py @@ -7,11 +7,10 @@ AutoModelForCausalLM, AutoTokenizer, AutoModelForImageClassification, - AutoModelForObjectDetection + AutoModelForObjectDetection, ) - from centml.compiler.prediction.kdtree import KDTreeWithValues from centml.compiler.prediction.profiler import Profiler from scripts.timer import timed @@ -26,8 +25,8 @@ # Different HuggingFace Models + Different Input Sizes llm_tests = [ ("google/gemma-7b", (1, 128)), - ("microsoft/phi-2", (1,512)), - ("microsoft/phi-2", (2,512)), + ("microsoft/phi-2", (1, 512)), + ("microsoft/phi-2", (2, 512)), ("facebook/bart-large", (1, 1024)), ("facebook/bart-large", (2, 512)), ("gpt2-xl", (1, 1024)), @@ -212,6 +211,7 @@ def image_classification_test(model_name, batch_size, custom_backend): gc.collect() torch.cuda.empty_cache() + def object_detection_test(model_name, batch_size, custom_backend): global cuda_kernel_time global actual_time @@ -240,6 +240,7 @@ def object_detection_test(model_name, batch_size, custom_backend): gc.collect() torch.cuda.empty_cache() + # for model_name, input_size in large_llm_tests: # llm_test(model_name, input_size, custom_backend) diff --git a/scripts/timer.py b/scripts/timer.py index 375e28f..6019c9f 100644 --- a/scripts/timer.py +++ b/scripts/timer.py @@ -1,5 +1,6 @@ import torch + def timed(fn): start = torch.cuda.Event(enable_timing=True) end = torch.cuda.Event(enable_timing=True) diff --git a/scripts/user_vault/get_vault_items.py b/scripts/user_vault/get_vault_items.py index eee23c5..a36585c 100644 --- a/scripts/user_vault/get_vault_items.py +++ b/scripts/user_vault/get_vault_items.py @@ -15,15 +15,11 @@ from platform_api_python_client import UserVaultType -def get_vault_items( - vault_type: Optional[UserVaultType] = None, - search_query: Optional[str] = None, -): +def get_vault_items(vault_type: Optional[UserVaultType] = None, search_query: Optional[str] = None): """Retrieve items from user's vault.""" with get_centml_client() as client: response = client._api.get_all_user_vault_items_endpoint_user_vault_get( - type=vault_type, - search_query=search_query, + type=vault_type, search_query=search_query ) return response.results @@ -65,18 +61,8 @@ def display_vault_items(items, show_values: bool = False): type=click.Choice([t.value for t in UserVaultType], case_sensitive=False), help="Filter by vault type (env_vars, ssh_keys, bearer_tokens, access_tokens, certificates)", ) -@click.option( - "--search", - "search_query", - type=str, - help="Search query to filter items by key", -) -@click.option( - "--show-values", - is_flag=True, - default=False, - help="Show vault item values", -) +@click.option("--search", "search_query", type=str, help="Search query to filter items by key") +@click.option("--show-values", is_flag=True, default=False, help="Show vault item values") def main(vault_type: Optional[str], search_query: Optional[str], show_values: bool): """Retrieve all items from user's vault (secrets). @@ -99,10 +85,7 @@ def main(vault_type: Optional[str], search_query: Optional[str], show_values: bo """ type_enum = UserVaultType(vault_type) if vault_type else None - items = get_vault_items( - vault_type=type_enum, - search_query=search_query, - ) + items = get_vault_items(vault_type=type_enum, search_query=search_query) display_vault_items(items, show_values=show_values)