44import re
55import sys
66from pathlib import Path
7- from typing import Any , Dict
7+ from typing import Any , Dict , Set
88
99from eval_protocol .auth import get_fireworks_api_key
10- from eval_protocol .platform_api import create_or_update_fireworks_secret
10+ from eval_protocol .platform_api import create_or_update_fireworks_secret , get_fireworks_secret
1111
1212from eval_protocol .evaluation import create_evaluation
1313from .utils import (
@@ -167,13 +167,119 @@ def _mask_secret_value(value: str) -> str:
167167 return "<masked>"
168168
169169
170+ def _check_existing_secrets (
171+ account_id : str ,
172+ secret_keys : list [str ],
173+ ) -> Set [str ]:
174+ """
175+ Check which secrets already exist on Fireworks.
176+ Returns a set of key names that already exist.
177+ """
178+ existing : Set [str ] = set ()
179+ for key in secret_keys :
180+ try :
181+ secret = get_fireworks_secret (account_id = account_id , key_name = key )
182+ if secret is not None :
183+ existing .add (key )
184+ except Exception :
185+ # If we can't check, assume it doesn't exist
186+ pass
187+ return existing
188+
189+
190+ def _confirm_overwrite_secrets (
191+ secrets_to_overwrite : list [str ],
192+ non_interactive : bool ,
193+ ) -> bool :
194+ """
195+ Prompt user to confirm overwriting existing secrets with double confirmation.
196+ Returns True if user confirms both prompts, False otherwise.
197+ """
198+ if not secrets_to_overwrite :
199+ return True
200+
201+ if non_interactive :
202+ return True
203+
204+ if not sys .stdin .isatty ():
205+ return True
206+
207+ print (
208+ f"\n ⚠️ The following { len (secrets_to_overwrite )} secret(s) already exist on Fireworks and will be overwritten:"
209+ )
210+ for key in secrets_to_overwrite :
211+ print (f" • { key } " )
212+
213+ print ("\n " + "=" * 70 )
214+ print ("⚠️ WARNING: Overwriting secrets may affect running jobs!" )
215+ print ("=" * 70 )
216+ print ("Make sure any new or existing evaluator jobs will work with the new" )
217+ print ("secret values before proceeding. Existing RFT jobs and evaluators that" )
218+ print ("depend on these secrets may fail if the new values are incompatible." )
219+ print ("=" * 70 )
220+
221+ try :
222+ import questionary
223+
224+ custom_style = _get_questionary_style ()
225+
226+ # First confirmation
227+ confirm1 = questionary .confirm (
228+ "Do you want to overwrite these existing secrets?" ,
229+ default = False ,
230+ style = custom_style ,
231+ ).ask ()
232+
233+ if confirm1 is None or not confirm1 :
234+ print ("\n Secret overwrite cancelled." )
235+ return False
236+
237+ # Second confirmation
238+ confirm2 = questionary .confirm (
239+ "Are you SURE? This may break existing jobs using these secrets." ,
240+ default = False ,
241+ style = custom_style ,
242+ ).ask ()
243+
244+ if confirm2 is None or not confirm2 :
245+ print ("\n Secret overwrite cancelled." )
246+ return False
247+
248+ return True
249+
250+ except ImportError :
251+ # Fallback to simple text-based confirmation
252+ try :
253+ print ("\n First confirmation:" )
254+ response1 = input ("Type 'yes' to confirm overwrite: " ).strip ().lower ()
255+ if response1 != "yes" :
256+ print ("Secret overwrite cancelled." )
257+ return False
258+
259+ print ("\n Second confirmation:" )
260+ response2 = input ("Type 'yes' again to confirm (this may break existing jobs): " ).strip ().lower ()
261+ if response2 != "yes" :
262+ print ("Secret overwrite cancelled." )
263+ return False
264+
265+ return True
266+ except KeyboardInterrupt :
267+ print ("\n Secret upload cancelled." )
268+ return False
269+ except KeyboardInterrupt :
270+ print ("\n \n Secret upload cancelled." )
271+ return False
272+
273+
170274def _prompt_select_secrets (
171275 secrets : Dict [str , str ],
172276 secrets_from_env_file : Dict [str , str ],
277+ existing_secrets : Set [str ],
173278 non_interactive : bool ,
174279) -> Dict [str , str ]:
175280 """
176281 Prompt user to select which environment variables to upload as secrets.
282+ Existing secrets are shown but deselected by default.
177283 Returns the selected secrets.
178284 """
179285 if not secrets :
@@ -192,17 +298,23 @@ def _prompt_select_secrets(
192298 custom_style = _get_questionary_style ()
193299
194300 # Build choices with source info and masked values
301+ # Existing secrets are unchecked by default
195302 choices = []
196303 for key , value in secrets .items ():
197304 source = ".env" if key in secrets_from_env_file else "env"
198305 masked = _mask_secret_value (value )
199- label = f"{ key } ({ source } : { masked } )"
200- choices .append (questionary .Choice (title = label , value = key , checked = True ))
306+ is_existing = key in existing_secrets
307+ status = " [exists]" if is_existing else ""
308+ label = f"{ key } { status } ({ source } : { masked } )"
309+ # Existing secrets are unchecked by default
310+ choices .append (questionary .Choice (title = label , value = key , checked = not is_existing ))
201311
202312 if len (choices ) == 0 :
203313 return {}
204314
205315 print ("\n Found environment variables to upload as Fireworks secrets:" )
316+ if existing_secrets :
317+ print ("(Secrets marked [exists] are deselected by default to avoid overwriting)" )
206318 selected_keys = questionary .checkbox (
207319 "Select secrets to upload:" ,
208320 choices = choices ,
@@ -220,7 +332,7 @@ def _prompt_select_secrets(
220332
221333 except ImportError :
222334 # Fallback to simple text-based selection
223- return _prompt_select_secrets_fallback (secrets , secrets_from_env_file )
335+ return _prompt_select_secrets_fallback (secrets , secrets_from_env_file , existing_secrets )
224336 except KeyboardInterrupt :
225337 print ("\n \n Secret upload cancelled." )
226338 return {}
@@ -229,6 +341,7 @@ def _prompt_select_secrets(
229341def _prompt_select_secrets_fallback (
230342 secrets : Dict [str , str ],
231343 secrets_from_env_file : Dict [str , str ],
344+ existing_secrets : Set [str ],
232345) -> Dict [str , str ]:
233346 """Fallback prompt selection for when questionary is not available."""
234347 print ("\n " + "=" * 60 )
@@ -237,12 +350,20 @@ def _prompt_select_secrets_fallback(
237350 print ("\n Tip: Install questionary for better UX: pip install questionary\n " )
238351
239352 secret_list = list (secrets .items ())
353+ new_secret_indices = []
240354 for idx , (key , value ) in enumerate (secret_list , 1 ):
241355 source = ".env" if key in secrets_from_env_file else "env"
242356 masked = _mask_secret_value (value )
243- print (f" [{ idx } ] { key } ({ source } : { masked } )" )
357+ is_existing = key in existing_secrets
358+ status = " [exists]" if is_existing else ""
359+ print (f" [{ idx } ] { key } { status } ({ source } : { masked } )" )
360+ if not is_existing :
361+ new_secret_indices .append (idx )
244362
245363 print ("\n " + "=" * 60 )
364+ if existing_secrets :
365+ print ("Secrets marked [exists] already exist on Fireworks." )
366+ print (f"Default selection (new secrets only): { ',' .join (str (i ) for i in new_secret_indices ) or 'none' } " )
246367 print ("Enter numbers to select (comma-separated), 'all' for all, or 'none' to skip:" )
247368
248369 try :
@@ -251,7 +372,15 @@ def _prompt_select_secrets_fallback(
251372 print ("\n Secret upload cancelled." )
252373 return {}
253374
254- if not choice or choice == "none" :
375+ if not choice :
376+ # Default: select only new secrets
377+ selected = {}
378+ for idx in new_secret_indices :
379+ key , value = secret_list [idx - 1 ]
380+ selected [key ] = value
381+ return selected
382+
383+ if choice == "none" :
255384 return {}
256385
257386 if choice == "all" :
@@ -280,8 +409,10 @@ def upload_secrets_to_fireworks(
280409
281410 This function:
282411 1. Loads secrets from the specified .env file (or default .env in root)
283- 2. Prompts user to select which secrets to upload (unless non_interactive)
284- 3. Creates/updates the selected secrets on Fireworks
412+ 2. Checks which secrets already exist on Fireworks
413+ 3. Prompts user to select which secrets to upload (existing secrets are deselected by default)
414+ 4. Confirms before overwriting any existing secrets
415+ 5. Creates/updates the selected secrets on Fireworks
285416
286417 Args:
287418 root: The project root directory
@@ -307,32 +438,56 @@ def upload_secrets_to_fireworks(
307438 secrets_from_file ["FIREWORKS_API_KEY" ] = fw_api_key_value
308439
309440 if fw_account_id and secrets_from_file :
441+ print (f"\n 🔐 Managing secrets for Fireworks account: { fw_account_id } " )
310442 if secrets_from_env_file and os .path .exists (env_file_path ):
311443 print (f"Loading secrets from: { env_file_path } " )
312444
445+ # Check which secrets already exist on Fireworks
446+ print ("Checking existing secrets on Fireworks..." )
447+ existing_secrets = _check_existing_secrets (
448+ account_id = fw_account_id ,
449+ secret_keys = list (secrets_from_file .keys ()),
450+ )
451+ if existing_secrets :
452+ print (f"Found { len (existing_secrets )} existing secret(s): { ', ' .join (sorted (existing_secrets ))} " )
453+
313454 # Prompt user to select which secrets to upload
455+ # Existing secrets are deselected by default
314456 selected_secrets = _prompt_select_secrets (
315457 secrets_from_file ,
316458 secrets_from_env_file ,
459+ existing_secrets ,
317460 non_interactive ,
318461 )
319462
320463 if selected_secrets :
464+ # Check if any selected secrets already exist and need confirmation
465+ secrets_to_overwrite = [k for k in selected_secrets .keys () if k in existing_secrets ]
466+ if secrets_to_overwrite :
467+ if not _confirm_overwrite_secrets (secrets_to_overwrite , non_interactive ):
468+ # User declined to overwrite - remove existing secrets from selection
469+ selected_secrets = {k : v for k , v in selected_secrets .items () if k not in existing_secrets }
470+ if not selected_secrets :
471+ print ("No new secrets to upload." )
472+ return
473+ print (f"\n Proceeding with { len (selected_secrets )} new secret(s) only..." )
474+
321475 print (f"\n Uploading { len (selected_secrets )} selected secret(s) to Fireworks..." )
322476 for secret_name , secret_value in selected_secrets .items ():
323477 source = ".env" if secret_name in secrets_from_env_file else "environment"
324- print (
325- f"Ensuring { secret_name } is registered as a secret on Fireworks for rollout... "
326- f"({ source } : { _mask_secret_value (secret_value )} )"
327- )
478+ is_overwrite = secret_name in existing_secrets
479+ action = "Overwriting" if is_overwrite else "Creating"
480+ print (f"{ action } { secret_name } on Fireworks... ({ source } : { _mask_secret_value (secret_value )} )" )
328481 if create_or_update_fireworks_secret (
329482 account_id = fw_account_id ,
330483 key_name = secret_name ,
331484 secret_value = secret_value ,
332485 ):
333- print (f"✓ { secret_name } secret created/ updated on Fireworks." )
486+ print (f"✓ { secret_name } secret { ' updated' if is_overwrite else 'created' } on Fireworks." )
334487 else :
335- print (f"Warning: Failed to create/update { secret_name } secret on Fireworks." )
488+ print (
489+ f"Warning: Failed to { 'update' if is_overwrite else 'create' } { secret_name } secret on Fireworks."
490+ )
336491 else :
337492 print ("No secrets selected for upload." )
338493 else :
0 commit comments