Skip to content

Commit 88be5d0

Browse files
Enable user to update existing credentials
1 parent 15229b3 commit 88be5d0

2 files changed

Lines changed: 163 additions & 117 deletions

File tree

sre_agent/cli/commands/config.py

Lines changed: 39 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -195,51 +195,36 @@ def setup(config_path: Optional[str], full: bool):
195195
console.print(" exec -l $SHELL")
196196
return
197197

198-
# Clear existing credentials, kubectl context, and environment variables to avoid confusion
199-
if platform == "aws":
200-
import os
201-
202-
credentials_file = os.path.expanduser("~/.aws/credentials")
203-
if os.path.exists(credentials_file):
204-
try:
205-
os.remove(credentials_file)
206-
console.print("[dim]Cleared existing AWS credentials file[/dim]")
207-
except:
208-
pass
209-
210-
# Clear kubectl context to force reconfiguration
211-
try:
212-
subprocess.run(
213-
["kubectl", "config", "unset", "current-context"],
214-
capture_output=True,
215-
text=True,
216-
timeout=10,
217-
)
218-
console.print("[dim]Cleared existing kubectl context[/dim]")
219-
except:
220-
pass
221-
222-
# Clear existing .env file to force fresh setup
223-
env_file = Path.cwd() / ".env"
224-
if env_file.exists():
225-
try:
226-
env_file.unlink()
227-
console.print("[dim]Cleared existing .env file[/dim]")
228-
except:
229-
pass
230-
231-
console.print(f"\nSetting up {platform.upper()} credentials...")
232-
233-
if platform == "aws" and detector.setup_aws_credentials():
234-
primary_platform = "aws"
235-
elif platform == "gcp" and detector.setup_gcp_credentials():
236-
primary_platform = "gcp"
198+
# Preserve existing configurations for incremental updates
199+
200+
# Check if credentials need setup
201+
if platform in configured_cloud_platforms:
202+
console.print(f"\n[green]{platform.upper()} is already configured.[/green]")
203+
reconfigure = Confirm.ask(f"Reconfigure {platform.upper()} credentials?", default=False)
204+
205+
if reconfigure:
206+
console.print(f"\nReconfiguring {platform.upper()} credentials...")
207+
if platform == "aws" and detector.setup_aws_credentials():
208+
primary_platform = "aws"
209+
elif platform == "gcp" and detector.setup_gcp_credentials():
210+
primary_platform = "gcp"
211+
else:
212+
console.print(f"[red]❌ {platform.upper()} configuration failed.[/red]")
213+
return
214+
else:
215+
primary_platform = platform
237216
else:
238-
console.print(
239-
f"[red]❌ {platform.upper()} configuration failed. SRE Agent requires cloud credentials to work.[/red]"
240-
)
241-
console.print("Please try again or check your cloud CLI installation.")
242-
return
217+
console.print(f"\nSetting up {platform.upper()} credentials...")
218+
if platform == "aws" and detector.setup_aws_credentials():
219+
primary_platform = "aws"
220+
elif platform == "gcp" and detector.setup_gcp_credentials():
221+
primary_platform = "gcp"
222+
else:
223+
console.print(
224+
f"[red]❌ {platform.upper()} configuration failed. SRE Agent requires cloud credentials to work.[/red]"
225+
)
226+
console.print("Please try again or check your cloud CLI installation.")
227+
return
243228

244229
# Step 2: Configure kubectl access after cloud platform is set up (required)
245230
if primary_platform and has_kubectl and not kubectl_configured:
@@ -408,21 +393,18 @@ def setup(config_path: Optional[str], full: bool):
408393
"SRE Agent services need these variables to function properly."
409394
)
410395

411-
if not env_setup.interactive_setup():
412-
console.print(
413-
"[red]❌ Environment variable setup failed or was cancelled.[/red]"
414-
)
415-
console.print(
416-
"SRE Agent requires environment variables to work properly."
417-
)
418-
console.print(
419-
"You can run setup again later with: [cyan]sre-agent config setup[/cyan]"
420-
)
421-
return
422-
else:
396+
# Always run interactive setup to allow updating existing variables
397+
if not env_setup.interactive_setup():
398+
console.print(
399+
"[red]❌ Environment variable setup failed or was cancelled.[/red]"
400+
)
401+
console.print(
402+
"SRE Agent requires environment variables to work properly."
403+
)
423404
console.print(
424-
"[green]✅ All required environment variables are configured![/green]"
405+
"You can run setup again later with: [cyan]sre-agent config setup[/cyan]"
425406
)
407+
return
426408
else:
427409
console.print(
428410
"[yellow]⚠️ Skipping environment setup - no cloud platform configured.[/yellow]"

sre_agent/cli/utils/env_setup.py

Lines changed: 124 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -438,11 +438,7 @@ def interactive_setup(self) -> bool:
438438
required_vars = self.get_required_env_vars()
439439
existing_vars = self.load_existing_env()
440440
missing_required, missing_optional = self.check_missing_env_vars()
441-
442-
if not missing_required and not missing_optional:
443-
console.print("[green]✅ All environment variables are already configured![/green]")
444-
return True
445-
441+
446442
# Show what we need to configure
447443
if missing_required:
448444
console.print(f"[yellow]Missing {len(missing_required)} required variables:[/yellow]")
@@ -526,55 +522,101 @@ def interactive_setup(self) -> bool:
526522
console.print(f"[green]Auto-detected GKE cluster: {auto_cluster}[/green]")
527523
updated_vars['TARGET_GKE_CLUSTER_NAME'] = auto_cluster
528524

529-
# Configure missing required variables
530-
for var_name in missing_required:
531-
if var_name in updated_vars:
532-
continue # Already auto-detected
533-
525+
# Configure all variables (allow updating existing ones)
526+
changes_made = False
527+
all_required_vars = [var for var in required_vars.keys() if required_vars[var]['required']]
528+
529+
for var_name in all_required_vars:
534530
config = required_vars[var_name]
535531

536-
# Special handling for PROVIDER - show as a choice menu
532+
# Special handling for PROVIDER - show as a choice menu with current value
537533
if var_name == "PROVIDER":
534+
current_provider = existing_vars.get("PROVIDER", "")
535+
538536
console.print(f"\n[cyan]LLM Provider Selection[/cyan]")
539-
console.print("Which LLM provider would you like to use?")
540-
console.print(" 1. Anthropic (Claude)")
541-
console.print(" 2. Google (Gemini)")
537+
if current_provider:
538+
console.print(f"Current: {current_provider}")
539+
console.print("Which LLM provider would you like to use?")
540+
console.print(" 1. Anthropic (Claude)")
541+
console.print(" 2. Google (Gemini)")
542+
console.print(" 3. Keep current")
543+
544+
choice = Prompt.ask("Choose provider", choices=["1", "2", "3"], default="3")
545+
else:
546+
console.print("Which LLM provider would you like to use?")
547+
console.print(" 1. Anthropic (Claude)")
548+
console.print(" 2. Google (Gemini)")
549+
550+
choice = Prompt.ask("Choose provider", choices=["1", "2"], default="1")
542551

543-
choice = Prompt.ask("Choose provider", choices=["1", "2"], default="1")
544552
if choice == "1":
545-
updated_vars["PROVIDER"] = "anthropic"
546-
console.print("[green]Selected: Anthropic (Claude)[/green]")
547-
else:
548-
updated_vars["PROVIDER"] = "google"
549-
console.print("[green]Selected: Google (Gemini)[/green]")
553+
new_provider = "anthropic"
554+
elif choice == "2":
555+
new_provider = "google"
556+
else: # choice == "3" (keep current)
557+
new_provider = current_provider
558+
559+
if new_provider != current_provider:
560+
changes_made = True
561+
console.print(f"[green]Selected: {new_provider}[/green]")
562+
elif current_provider:
563+
console.print(f"[green]Keeping: {current_provider}[/green]")
564+
565+
updated_vars["PROVIDER"] = new_provider
550566
continue
551567

568+
# Get current value or provide defaults
569+
current_value = existing_vars.get(var_name, "")
570+
if not current_value:
571+
# Provide defaults for first-time setup
572+
if var_name == "MODEL":
573+
if updated_vars.get("PROVIDER") == "anthropic":
574+
current_value = "claude-3-7-sonnet-latest"
575+
elif updated_vars.get("PROVIDER") == "google":
576+
current_value = "gemini-1.5-pro"
577+
elif var_name == "MAX_TOKENS":
578+
current_value = "4000"
579+
elif var_name == "PROJECT_ROOT":
580+
current_value = "src" if self.minimal else "."
581+
elif var_name == "GITHUB_ORGANISATION" and self.minimal:
582+
current_value = "fuzzylabs"
583+
elif var_name == "GITHUB_REPO_NAME" and self.minimal:
584+
current_value = "microservices-demo"
585+
elif var_name == "DEV_BEARER_TOKEN":
586+
current_value = "dev_token_" + str(hash("sre-agent"))[:8]
587+
552588
console.print(f"\n[cyan]{var_name}[/cyan] ({config['description']})")
553589

554-
# Provide defaults for some variables
555-
default_value = ""
556-
if var_name == "MODEL":
557-
if updated_vars.get("PROVIDER") == "anthropic":
558-
default_value = "claude-3-5-sonnet-20241022"
559-
elif updated_vars.get("PROVIDER") == "google":
560-
default_value = "gemini-1.5-pro"
561-
elif var_name == "MAX_TOKENS":
562-
default_value = "4000"
563-
elif var_name == "PROJECT_ROOT":
564-
default_value = "src" if self.minimal else "."
565-
elif var_name == "GITHUB_ORGANISATION" and self.minimal:
566-
default_value = "fuzzylabs"
567-
elif var_name == "GITHUB_REPO_NAME" and self.minimal:
568-
default_value = "microservices-demo"
569-
elif var_name == "DEV_BEARER_TOKEN":
570-
default_value = "dev_token_" + str(hash("sre-agent"))[:8]
590+
# Smart prompt text based on whether value exists
591+
if current_value:
592+
# Show current value (masked if sensitive)
593+
if config['sensitive']:
594+
if len(current_value) > 6:
595+
display_value = f"{current_value[:3]}...{current_value[-3:]}"
596+
else:
597+
display_value = "*" * len(current_value)
598+
else:
599+
display_value = current_value
600+
601+
console.print(f"Current: {display_value}")
602+
prompt_text = f"Enter {var_name} or press Enter to keep current"
603+
else:
604+
prompt_text = f"Enter {var_name}"
571605

572606
if config['sensitive']:
573-
value = Prompt.ask(f"Enter {var_name}", default=default_value, password=True)
607+
# Don't use default for password fields to avoid showing sensitive data
608+
value = Prompt.ask(prompt_text, password=True)
609+
if not value: # User pressed Enter without typing
610+
value = current_value
574611
else:
575-
value = Prompt.ask(f"Enter {var_name}", default=default_value)
576-
if value:
612+
value = Prompt.ask(prompt_text, default=current_value)
613+
614+
# Track changes
615+
if value != current_value:
616+
changes_made = True
577617
updated_vars[var_name] = value
618+
elif current_value:
619+
updated_vars[var_name] = current_value
578620
elif config['required']:
579621
# For required variables, empty values are not allowed
580622
console.print(f"[red]❌ {var_name} is required and cannot be empty[/red]")
@@ -587,19 +629,37 @@ def interactive_setup(self) -> bool:
587629
if selected_provider == "google":
588630
api_key_var = "GEMINI_API_KEY"
589631

590-
if api_key_var not in updated_vars or not updated_vars[api_key_var]:
591-
console.print(f"\n[cyan]{api_key_var}[/cyan] (Required for {selected_provider} provider)")
592-
if selected_provider == "anthropic":
593-
console.print("Get your API key from: https://console.anthropic.com/")
594-
elif selected_provider == "google":
595-
console.print("Get your API key from: https://aistudio.google.com/app/apikey")
596-
597-
api_key = Prompt.ask(f"Enter {api_key_var}", password=True)
598-
if api_key:
599-
updated_vars[api_key_var] = api_key
600-
else:
601-
console.print(f"[red]❌ {api_key_var} is required for the selected provider[/red]")
602-
return False
632+
# Handle API key with smart prompting
633+
current_api_key = existing_vars.get(api_key_var, "")
634+
635+
console.print(f"\n[cyan]{api_key_var}[/cyan] (Required for {selected_provider} provider)")
636+
if selected_provider == "anthropic":
637+
console.print("Get your API key from: https://console.anthropic.com/")
638+
elif selected_provider == "google":
639+
console.print("Get your API key from: https://aistudio.google.com/app/apikey")
640+
641+
if current_api_key:
642+
# Show masked current value
643+
masked_key = f"{current_api_key[:3]}...{current_api_key[-3:]}" if len(current_api_key) > 6 else "*" * len(current_api_key)
644+
console.print(f"Current: {masked_key}")
645+
prompt_text = f"Enter {api_key_var} or press Enter to keep current"
646+
else:
647+
prompt_text = f"Enter {api_key_var}"
648+
649+
# Don't use default for password fields to avoid showing sensitive data
650+
api_key = Prompt.ask(prompt_text, password=True)
651+
if not api_key: # User pressed Enter without typing
652+
api_key = current_api_key
653+
654+
# Track changes for API key
655+
if api_key != current_api_key:
656+
changes_made = True
657+
updated_vars[api_key_var] = api_key
658+
elif current_api_key:
659+
updated_vars[api_key_var] = current_api_key
660+
elif not api_key:
661+
console.print(f"[red]❌ {api_key_var} is required for the selected provider[/red]")
662+
return False
603663

604664
# Ask about optional variables (skip API keys and Slack vars)
605665
optional_vars_to_configure = [
@@ -644,14 +704,18 @@ def interactive_setup(self) -> bool:
644704
elif selected_provider == "google" and "ANTHROPIC_API_KEY" not in updated_vars:
645705
updated_vars["ANTHROPIC_API_KEY"] = ""
646706

647-
# Save to .env file
648-
try:
649-
self.save_env_file(updated_vars)
650-
console.print(f"\n[green]✅ Environment variables saved to {self.env_file}[/green]")
707+
# Save to .env file only if changes were made
708+
if changes_made:
709+
try:
710+
self.save_env_file(updated_vars)
711+
console.print(f"\n[green]✅ Environment variables updated and saved to {self.env_file}[/green]")
712+
return True
713+
except Exception as e:
714+
console.print(f"[red]❌ Failed to save .env file: {e}[/red]")
715+
return False
716+
else:
717+
console.print(f"\n[green]✅ No changes made - {self.env_file} unchanged[/green]")
651718
return True
652-
except Exception as e:
653-
console.print(f"[red]❌ Failed to save .env file: {e}[/red]")
654-
return False
655719

656720
def save_env_file(self, env_vars: Dict[str, str]) -> None:
657721
"""Save environment variables to .env file."""

0 commit comments

Comments
 (0)