22
33import os
44import time
5+ import json
56import configparser
67from pathlib import Path
78from fastapi import APIRouter , Depends , HTTPException , Query , UploadFile , File , Request
2728 ProviderTemplateFileUploadResponse ,
2829 ResumeFromCheckpointRequest ,
2930)
30- from transformerlab .shared .models .models import ProviderType , TeamComputeProvider
31- from transformerlab .compute_providers .base import ComputeProvider
31+ from transformerlab .shared .models .models import ProviderType
3232from transformerlab .compute_providers .models import (
3333 ClusterConfig ,
3434 ClusterStatus ,
4545 read_github_pat_from_workspace ,
4646 generate_github_clone_setup ,
4747)
48+ from transformerlab .shared .secret_utils import load_team_secrets , replace_secrets_in_dict , replace_secret_placeholders
4849from typing import Any
4950
5051router = APIRouter (prefix = "/compute_provider" , tags = ["compute_provider" ])
@@ -59,17 +60,6 @@ def _sanitize_cluster_basename(base_name: Optional[str]) -> str:
5960 return normalized or "remote-template"
6061
6162
62- def _get_provider_instances (providers : list [TeamComputeProvider ]) -> Dict [str , ComputeProvider ]:
63- """Instantiate providers safely."""
64- instances : Dict [str , ComputeProvider ] = {}
65- for provider in providers :
66- try :
67- instances [provider .id ] = get_provider_instance (provider )
68- except Exception as exc :
69- print (f"Failed to instantiate provider { provider .id } : { exc } " )
70- return instances
71-
72-
7363@router .post ("/{provider_id}/task/{task_id}/file-upload" , response_model = ProviderTemplateFileUploadResponse )
7464async def upload_task_file_for_provider (
7565 provider_id : str ,
@@ -232,7 +222,6 @@ async def get_usage_report(
232222 Aggregates usage data by user, provider, and resources.
233223 Only accessible to team owners.
234224 """
235- import json
236225 from datetime import datetime
237226 from lab import Experiment
238227
@@ -562,46 +551,6 @@ async def check_provider(
562551# ============================================================================
563552
564553
565- @router .post ("/{provider_id}/clusters/{cluster_name}/launch" )
566- async def launch_cluster (
567- provider_id : str ,
568- cluster_name : str ,
569- config : ClusterConfig ,
570- user_and_team = Depends (get_user_and_team ),
571- session : AsyncSession = Depends (get_async_session ),
572- ):
573- """
574- Launch/provision a new cluster using the specified provider.
575- Requires X-Team-Id header and team membership.
576- """
577- team_id = user_and_team ["team_id" ]
578-
579- provider = await get_team_provider (session , team_id , provider_id )
580- if not provider :
581- raise HTTPException (status_code = 404 , detail = "Provider not found" )
582-
583- try :
584- # Get provider instance
585- provider_instance = get_provider_instance (provider )
586-
587- if not config .cluster_name :
588- config .cluster_name = cluster_name
589- config .provider_name = provider .name
590- config .provider_id = provider .id
591-
592- # Launch cluster
593- result = provider_instance .launch_cluster (cluster_name , config )
594-
595- return {
596- "status" : "success" ,
597- "message" : f"Cluster '{ cluster_name } ' launch initiated" ,
598- "cluster_name" : cluster_name ,
599- "result" : result ,
600- }
601- except Exception :
602- raise HTTPException (status_code = 500 , detail = "Failed to launch cluster" )
603-
604-
605554def _get_aws_credentials_from_file (profile_name : str = "transformerlab-s3" ) -> Tuple [Optional [str ], Optional [str ]]:
606555 """
607556 Read AWS credentials from ~/.aws/credentials file for the specified profile.
@@ -802,6 +751,9 @@ async def _launch_sweep_jobs(
802751
803752 provider_display_name = request .provider_name or provider .name
804753
754+ # Load team secrets for template replacement
755+ team_secrets = await load_team_secrets ()
756+
805757 # Generate all parameter combinations
806758 param_names = list (sweep_config .keys ())
807759 param_values = [sweep_config [name ] for name in param_names ]
@@ -832,6 +784,11 @@ async def _launch_sweep_jobs(
832784
833785 # Prepare environment variables
834786 env_vars = request .env_vars .copy () if request .env_vars else {}
787+
788+ # Replace {{secret.<name>}} patterns in env_vars
789+ if env_vars and team_secrets :
790+ env_vars = replace_secrets_in_dict (env_vars , team_secrets )
791+
835792 env_vars ["_TFL_JOB_ID" ] = str (child_job_id )
836793 env_vars ["_TFL_EXPERIMENT_ID" ] = request .experiment_id
837794
@@ -872,11 +829,25 @@ async def _launch_sweep_jobs(
872829 )
873830 setup_commands .append (github_setup )
874831
832+ # Add user-provided setup if any (replace secrets in setup)
875833 if request .setup :
876- setup_commands .append (request .setup )
834+ setup_with_secrets = (
835+ replace_secret_placeholders (request .setup , team_secrets ) if team_secrets else request .setup
836+ )
837+ setup_commands .append (setup_with_secrets )
877838
878839 final_setup = ";" .join (setup_commands ) if setup_commands else None
879840
841+ # Replace secrets in command
842+ command_with_secrets = (
843+ replace_secret_placeholders (request .command , team_secrets ) if team_secrets else request .command
844+ )
845+
846+ # Replace secrets in parameters if present
847+ parameters_with_secrets = merged_params
848+ if merged_params and team_secrets :
849+ parameters_with_secrets = replace_secrets_in_dict (merged_params , team_secrets )
850+
880851 # Store child job data
881852 child_job_data = {
882853 "parent_sweep_job_id" : str (parent_job_id ),
@@ -886,7 +857,7 @@ async def _launch_sweep_jobs(
886857 "task_name" : f"{ request .task_name or 'Task' } (Sweep { i + 1 } /{ total_configs } )"
887858 if request .task_name
888859 else None ,
889- "command" : request . command ,
860+ "command" : command_with_secrets ,
890861 "cluster_name" : formatted_cluster_name ,
891862 "subtype" : request .subtype ,
892863 "cpus" : request .cpus ,
@@ -897,7 +868,7 @@ async def _launch_sweep_jobs(
897868 "setup" : final_setup ,
898869 "env_vars" : env_vars if env_vars else None ,
899870 "file_mounts" : request .file_mounts or None ,
900- "parameters" : merged_params or None ,
871+ "parameters" : parameters_with_secrets or None ,
901872 "provider_id" : provider .id ,
902873 "provider_type" : provider .type ,
903874 "provider_name" : provider_display_name ,
@@ -923,7 +894,7 @@ async def _launch_sweep_jobs(
923894 cluster_name = formatted_cluster_name ,
924895 provider_name = provider_display_name ,
925896 provider_id = provider .id ,
926- command = request . command ,
897+ command = command_with_secrets ,
927898 setup = final_setup ,
928899 env_vars = env_vars ,
929900 cpus = request .cpus ,
@@ -1105,9 +1076,16 @@ async def launch_template_on_provider(
11051076
11061077 provider_display_name = request .provider_name or provider .name
11071078
1079+ # Load team secrets for template replacement
1080+ team_secrets = await load_team_secrets ()
1081+
11081082 # Prepare environment variables - start with a copy of requested env_vars
11091083 env_vars = request .env_vars .copy () if request .env_vars else {}
11101084
1085+ # Replace {{secret.<name>}} patterns in env_vars
1086+ if env_vars and team_secrets :
1087+ env_vars = replace_secrets_in_dict (env_vars , team_secrets )
1088+
11111089 # Get AWS credentials from stored credentials file (transformerlab-s3 profile)
11121090 aws_profile = "transformerlab-s3"
11131091 if os .getenv ("TFL_API_STORAGE_URI" ):
@@ -1132,9 +1110,10 @@ async def launch_template_on_provider(
11321110 )
11331111 setup_commands .append (github_setup )
11341112
1135- # Add user-provided setup if any
1113+ # Add user-provided setup if any (replace secrets in setup)
11361114 if request .setup :
1137- setup_commands .append (request .setup )
1115+ setup_with_secrets = replace_secret_placeholders (request .setup , team_secrets ) if team_secrets else request .setup
1116+ setup_commands .append (setup_with_secrets )
11381117
11391118 final_setup = ";" .join (setup_commands ) if setup_commands else None
11401119
@@ -1159,9 +1138,21 @@ async def launch_template_on_provider(
11591138 # env_vars["AWS_ACCESS_KEY_ID"] = aws_access_key_id
11601139 # env_vars["AWS_SECRET_ACCESS_KEY"] = aws_secret_access_key
11611140
1141+ # Replace secrets in command
1142+ command_with_secrets = (
1143+ replace_secret_placeholders (request .command , team_secrets ) if team_secrets else request .command
1144+ )
1145+
1146+ # Replace secrets in parameters if present
1147+ parameters_with_secrets = None
1148+ if request .parameters and team_secrets :
1149+ parameters_with_secrets = replace_secrets_in_dict (request .parameters , team_secrets )
1150+ else :
1151+ parameters_with_secrets = request .parameters
1152+
11621153 job_data = {
11631154 "task_name" : request .task_name ,
1164- "command" : request . command ,
1155+ "command" : command_with_secrets ,
11651156 "cluster_name" : formatted_cluster_name ,
11661157 "subtype" : request .subtype ,
11671158 "interactive_type" : request .interactive_type ,
@@ -1173,7 +1164,7 @@ async def launch_template_on_provider(
11731164 "setup" : final_setup ,
11741165 "env_vars" : env_vars if env_vars else None ,
11751166 "file_mounts" : request .file_mounts or None ,
1176- "parameters" : request . parameters or None ,
1167+ "parameters" : parameters_with_secrets or None ,
11771168 "provider_id" : provider .id ,
11781169 "provider_type" : provider .type ,
11791170 "provider_name" : provider_display_name ,
@@ -1197,7 +1188,7 @@ async def launch_template_on_provider(
11971188 cluster_name = formatted_cluster_name ,
11981189 provider_name = provider_display_name ,
11991190 provider_id = provider .id ,
1200- command = request . command ,
1191+ command = command_with_secrets ,
12011192 setup = final_setup ,
12021193 env_vars = env_vars ,
12031194 cpus = request .cpus ,
0 commit comments