dayhoff-tools 1.0.8__py3-none-any.whl → 1.0.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,14 +2,19 @@
2
2
 
3
3
  This module provides commands for authenticating with GCP and AWS from within
4
4
  development containers. It handles both immediate shell environment configuration
5
- via the --export flag and persistent configuration via shell RC files.
5
+ via the --export flag (deprecated for GCP) and persistent configuration via
6
+ shell RC files (AWS only) or gcloud config settings (GCP).
6
7
 
7
8
  The implementation focuses on:
8
9
  1. Unifying cloud authentication with the `dh` CLI tool
9
- 2. Maintaining persistence across shell sessions via RC file modifications
10
+ 2. Maintaining persistence across shell sessions via RC file modifications (AWS)
11
+ or gcloud config (GCP).
10
12
  3. Providing similar capabilities to the shell scripts it replaces
13
+ 4. For GCP, leveraging `gcloud config` and Application Default Credentials (ADC)
14
+ updates for a streamlined, keyless, no-`eval` workflow.
11
15
  """
12
16
 
17
+ import json
13
18
  import os
14
19
  import re
15
20
  import shutil
@@ -128,6 +133,129 @@ def _get_env_var(variable: str) -> Optional[str]:
128
133
 
129
134
 
130
135
  # --- GCP Functions ---
136
+
137
+ # New approach: Use gcloud config settings instead of environment variables
138
+ # for impersonation and project settings. This avoids modifying RC files
139
+ # and the need for `eval "$(dh gcp use-... --export)"`.
140
+ # ADC is updated during the initial `dh gcp login` for the user.
141
+ # Subsequent ADC updates for impersonation or user mode must be done manually
142
+ # if required by libraries, as the underlying gcloud commands can force interaction.
143
+
144
+
145
+ def _get_short_name(account: str) -> str:
146
+ """Extracts a short name ('dma', 'devcon') from a GCP account email.
147
+
148
+ Args:
149
+ account: The full account string (e.g., 'dma@dayhofflabs.com',
150
+ 'devcon@...', 'None', 'Not authenticated').
151
+
152
+ Returns:
153
+ The short name or the original string if not a recognized email pattern.
154
+ """
155
+ if account == GCP_DEVCON_SA:
156
+ return "devcon"
157
+ if "@" in account:
158
+ # Attempt to get the part before @, common for user accounts
159
+ user_part = account.split("@")[0]
160
+ # You might want more specific logic here if user formats vary
161
+ # For now, assume simple user name like 'dma'
162
+ return user_part
163
+ # Handle special strings like 'None', 'Not authenticated', etc.
164
+ return account
165
+
166
+
167
+ def _gcloud_set_config(key: str, value: str) -> Tuple[int, str, str]:
168
+ """Set a gcloud configuration value using `gcloud config set`.
169
+
170
+ Args:
171
+ key: The configuration key (e.g., 'project', 'auth/impersonate_service_account').
172
+ value: The value to set for the key.
173
+
174
+ Returns:
175
+ Tuple of (return_code, stdout_str, stderr_str) from _run_command.
176
+ """
177
+ gcloud_path = _find_executable("gcloud")
178
+ cmd = [gcloud_path, "config", "set", key, value, "--quiet"]
179
+ return _run_command(cmd, capture=True, check=False, suppress_output=True)
180
+
181
+
182
+ def _gcloud_unset_config(key: str) -> Tuple[int, str, str]:
183
+ """Unset a gcloud configuration value using `gcloud config unset`.
184
+
185
+ Args:
186
+ key: The configuration key to unset.
187
+
188
+ Returns:
189
+ Tuple of (return_code, stdout_str, stderr_str) from _run_command.
190
+ """
191
+ gcloud_path = _find_executable("gcloud")
192
+ cmd = [gcloud_path, "config", "unset", key, "--quiet"]
193
+ return _run_command(cmd, capture=True, check=False, suppress_output=True)
194
+
195
+
196
+ def _get_adc_status() -> str:
197
+ """Check the status and type of Application Default Credentials (ADC).
198
+
199
+ Attempts to determine the effective credential source, including the
200
+ default GCE metadata server fallback if no explicit config is found.
201
+
202
+ Returns:
203
+ A short string describing the ADC principal ('dma', 'devcon',
204
+ 'default VM service account', 'Other/External', 'Not configured', etc.).
205
+ """
206
+ adc_file = (
207
+ Path.home() / ".config" / "gcloud" / "application_default_credentials.json"
208
+ )
209
+
210
+ # Check environment variable first (highest precedence after explicit file)
211
+ if _get_env_var("GOOGLE_APPLICATION_CREDENTIALS"):
212
+ # We don't know *what* key it points to without reading/parsing it.
213
+ return "Keyfile (GOOGLE_APPLICATION_CREDENTIALS)"
214
+
215
+ # Check explicit ADC JSON file
216
+ if adc_file.is_file():
217
+ try:
218
+ with open(adc_file, "r") as f:
219
+ adc_data = json.load(f)
220
+ cred_type = adc_data.get("type")
221
+
222
+ if cred_type == "authorized_user":
223
+ return "dma" # Assuming 'dma' is the likely user
224
+ elif cred_type == "impersonated_service_account":
225
+ sa_url = adc_data.get("service_account_impersonation_url", "")
226
+ sa_match = re.search(r"serviceAccounts/([^:]+)", sa_url)
227
+ if sa_match and sa_match.group(1) == GCP_DEVCON_SA:
228
+ return "devcon"
229
+ elif sa_match:
230
+ return f"Other SA ({_get_short_name(sa_match.group(1))})"
231
+ else:
232
+ return "devcon (?)" # Likely devcon but failed parse
233
+ elif cred_type == "external_account":
234
+ return "Other/External"
235
+ elif cred_type == "service_account":
236
+ # This type in the file usually means it was created pointing
237
+ # to a specific key file, but not via the env var.
238
+ # Hard to know details without parsing more.
239
+ return "Keyfile (from ADC json)"
240
+ else:
241
+ return f"Unknown ({cred_type})"
242
+
243
+ except json.JSONDecodeError:
244
+ return "Invalid format"
245
+ except (IOError, PermissionError, Exception) as e:
246
+ print(f"Warning: Could not read ADC file {adc_file}: {e}", file=sys.stderr)
247
+ return "Error reading"
248
+
249
+ # If no env var and no JSON file, check for GCE default SA fallback
250
+ # We infer this by checking the *CLI*'s current user status
251
+ cli_user = _get_current_gcp_user() # Reuse existing helper
252
+ if cli_user == "default VM service account":
253
+ return "default VM service account" # ADC likely uses this via metadata
254
+
255
+ # If none of the above, ADC is likely unconfigured for this environment
256
+ return "Not configured"
257
+
258
+
131
259
  def _is_gcp_user_authenticated() -> bool:
132
260
  """Check if the current gcloud user authentication is valid and non-interactive.
133
261
 
@@ -151,7 +279,7 @@ def _is_gcp_user_authenticated() -> bool:
151
279
 
152
280
 
153
281
  def _get_current_gcp_user() -> str:
154
- """Get the currently authenticated GCP user."""
282
+ """Get the currently authenticated GCP user or indicate default VM SA."""
155
283
  gcloud_path = _find_executable("gcloud")
156
284
  cmd = [
157
285
  gcloud_path,
@@ -165,74 +293,134 @@ def _get_current_gcp_user() -> str:
165
293
  account = stdout.strip()
166
294
  if account:
167
295
  if "compute@developer.gserviceaccount.com" in account:
168
- return "Not authenticated (using VM service account)"
296
+ # Return a more user-friendly string for the default VM SA case
297
+ return "default VM service account"
169
298
  return account
170
299
  return "Not authenticated"
171
300
 
172
301
 
173
302
  def _get_current_gcp_impersonation() -> str:
174
- """Get the current impersonated service account, if any."""
175
- sa = _get_env_var("CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT")
303
+ """Get the current impersonated service account from gcloud config."""
304
+ gcloud_path = _find_executable("gcloud")
305
+ cmd = [
306
+ gcloud_path,
307
+ "config",
308
+ "get-value",
309
+ "auth/impersonate_service_account",
310
+ "--quiet",
311
+ ]
312
+ returncode, stdout, _ = _run_command(cmd, capture=True, check=False)
313
+ sa = stdout.strip() if returncode == 0 else ""
176
314
  return sa if sa else "None"
177
315
 
178
316
 
179
317
  def _run_gcloud_login() -> None:
180
- """Run the gcloud auth login command."""
318
+ """Run the gcloud auth login command, updating ADC using device flow.
319
+
320
+ Always uses --update-adc to ensure libraries using ADC work immediately for the user.
321
+ Uses --no-launch-browser for headless environments.
322
+ """
181
323
  gcloud_path = _find_executable("gcloud")
182
- print(f"{BLUE}Authenticating with Google Cloud...{NC}")
183
- _run_command([gcloud_path, "auth", "login"])
184
- print(f"{GREEN}Authentication complete.{NC}")
324
+ print(f"{BLUE}Authenticating with Google Cloud (will update ADC)...{NC}")
325
+
326
+ # Directly use device flow as remote browser consistently failed
327
+ cmd = [gcloud_path, "auth", "login", "--update-adc", "--no-launch-browser"]
328
+
329
+ print(f"{YELLOW}Initiating device flow login... Follow the instructions below.{NC}")
330
+ # Remove capture=True, rely on direct output and return code
331
+ returncode, _, _ = _run_command(
332
+ cmd,
333
+ capture=False, # Changed from True
334
+ check=False,
335
+ suppress_output=False,
336
+ )
337
+
338
+ if returncode != 0:
339
+ # stderr is not captured, provide a generic error
340
+ print(
341
+ f"{RED}Login command failed (return code: {returncode}). Please check gcloud output above.{NC}",
342
+ file=sys.stderr,
343
+ )
344
+ sys.exit(1)
345
+
346
+ print(f"{GREEN}User authentication complete. ADC updated for user account.{NC}")
185
347
 
186
348
 
187
349
  def _test_gcp_credentials(user: str, impersonation_sa: str) -> None:
188
- """Test GCP credentials with and without impersonation."""
350
+ """Test GCP credentials. Only prints output on failure (to stderr)."""
189
351
  gcloud_path = _find_executable("gcloud")
190
-
191
- print(f"\n{BLUE}Testing credentials...{NC}")
352
+ user_short = _get_short_name(user)
353
+ impersonation_short = _get_short_name(impersonation_sa)
192
354
 
193
355
  if user != "Not authenticated" and "Not authenticated" not in user:
356
+ cmd = [
357
+ gcloud_path,
358
+ "compute",
359
+ "zones",
360
+ "list",
361
+ "--limit=1",
362
+ f"--project={GCP_PROJECT_ID}",
363
+ ]
364
+
194
365
  if impersonation_sa != "None":
195
- # Test user account first by temporarily unsetting impersonation
196
- orig_impersonation = _get_env_var(
197
- "CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT"
366
+ orig_sa = impersonation_sa
367
+ unset_rc, _, unset_err = _gcloud_unset_config(
368
+ "auth/impersonate_service_account"
198
369
  )
199
- if orig_impersonation:
200
- del os.environ["CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT"]
201
-
202
- print(f"First with user account {user}:")
203
- cmd = [gcloud_path, "compute", "zones", "list", "--limit=1"]
204
- returncode, _, _ = _run_command(cmd, suppress_output=True, check=False)
205
-
206
- if returncode == 0:
207
- print(f"{GREEN}✓ User has direct GCP access{NC}")
208
- else:
209
- print(f"{YELLOW}✗ User lacks direct GCP access{NC}")
370
+ if unset_rc != 0:
371
+ # Failure to unset is an error state
372
+ print(
373
+ f"{RED}✗ Test Error: Failed to temporarily disable impersonation: {unset_err}{NC}",
374
+ file=sys.stderr,
375
+ )
210
376
 
211
- # Restore impersonation and test with it
212
- if orig_impersonation:
213
- os.environ["CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT"] = (
214
- orig_impersonation
377
+ user_returncode, _, _ = _run_command(cmd, suppress_output=True, check=False)
378
+ if user_returncode != 0:
379
+ # Failure to access as user
380
+ print(
381
+ f"{RED}✗ Test Failure: Cannot access resources directly as user '{user_short}'. Check roles/project.{NC}",
382
+ file=sys.stderr,
215
383
  )
216
384
 
217
- print(f"Then impersonating {impersonation_sa}:")
218
- returncode, _, _ = _run_command(cmd, suppress_output=True, check=False)
385
+ set_rc, _, set_err = _gcloud_set_config(
386
+ "auth/impersonate_service_account", orig_sa
387
+ )
388
+ if set_rc != 0:
389
+ # Failure to restore is an error state
390
+ print(
391
+ f"{RED}✗ Test Error: Failed to restore impersonation config for {impersonation_short}: {set_err}{NC}",
392
+ file=sys.stderr,
393
+ )
219
394
 
220
- if returncode == 0:
221
- print(f"{GREEN}✓ Successfully using devcon service account{NC}")
222
- else:
395
+ impersonation_returncode, _, _ = _run_command(
396
+ cmd, suppress_output=True, check=False
397
+ )
398
+ if impersonation_returncode != 0:
399
+ # Failure to access while impersonating
223
400
  print(
224
- f"{RED}Failed to access GCP resources with impersonation. Check permissions.{NC}"
401
+ f"{RED} Test Failure: Cannot access resources impersonating '{impersonation_short}'. Check permissions/config.{NC}",
402
+ file=sys.stderr,
225
403
  )
404
+
226
405
  else:
227
- # Test user account directly (no impersonation)
228
- print(f"Using user account {user} (no impersonation):")
229
- cmd = [gcloud_path, "compute", "zones", "list", "--limit=1"]
406
+ # Test user account directly (no impersonation config)
230
407
  returncode, _, _ = _run_command(cmd, suppress_output=True, check=False)
231
-
232
- if returncode == 0:
233
- print(f"{GREEN}✓ Successfully using personal account{NC}")
234
- else:
235
- print(f"{RED}Failed to access GCP resources. Check permissions.{NC}")
408
+ if returncode != 0:
409
+ # Failure to access as user
410
+ print(
411
+ f"{RED}✗ Test Failure: Cannot access resources directly as user '{user_short}'. Check roles/project.{NC}",
412
+ file=sys.stderr,
413
+ )
414
+ # Success: No output
415
+ # else:
416
+ # print(f"{GREEN}✓ Direct access as user {user_short}: OK{NC}")
417
+ # Correctly indented pass statement if no action needed on success
418
+ pass
419
+ else:
420
+ # If user isn't authenticated at all, maybe print a warning?
421
+ # print(f"{YELLOW}User not authenticated, skipping credential test.{NC}")
422
+ # Decided against this to keep output minimal unless actual test fails.
423
+ pass # Explicit pass for the outer else
236
424
 
237
425
 
238
426
  # --- AWS Functions ---
@@ -319,160 +507,343 @@ def _get_available_aws_profiles() -> List[str]:
319
507
 
320
508
 
321
509
  # --- Typer Applications ---
322
- gcp_app = typer.Typer(help="Manage GCP authentication and impersonation.")
323
- aws_app = typer.Typer(help="Manage AWS SSO authentication.")
510
+ gcp_app = typer.Typer(
511
+ help="Manage GCP authentication using gcloud config and ADC. (RC file/env var methods are deprecated)."
512
+ )
513
+ aws_app = typer.Typer(help="Manage AWS SSO authentication using RC files.")
324
514
 
325
515
 
326
516
  # --- GCP Commands ---
327
517
  @gcp_app.command("status")
328
518
  def gcp_status():
329
- """Show current GCP authentication and impersonation status."""
330
- user_account = _get_current_gcp_user()
331
- impersonated_sa = _get_current_gcp_impersonation()
332
-
333
- print(f"{BLUE}GCP Status:{NC}")
334
- print(f"User account: {GREEN}{user_account}{NC}")
335
- print(f"Service account: {GREEN}{impersonated_sa}{NC}")
336
- print(f"Project: {GREEN}{GCP_PROJECT_ID}{NC}")
519
+ """Show active GCP credentials for CLI and Libraries/ADC."""
520
+ cli_user = _get_current_gcp_user()
521
+ cli_impersonation = _get_current_gcp_impersonation()
522
+ adc_principal = _get_adc_status()
523
+
524
+ # Determine active principal for CLI
525
+ if cli_impersonation != "None":
526
+ cli_active_short = _get_short_name(cli_impersonation)
527
+ else:
528
+ cli_active_short = _get_short_name(cli_user)
529
+
530
+ # Get short name for ADC principal
531
+ adc_active_short = _get_short_name(adc_principal)
532
+
533
+ # Define a fixed width for the principal name field
534
+ name_width = 10
535
+
536
+ print(
537
+ f"Using {GREEN}{cli_active_short:<{name_width}}{NC} for {BLUE}gcloud CLI{NC} (gcloud, gsutil)"
538
+ )
337
539
  print(
338
- f"Mode: {GREEN}{'Service account impersonation' if impersonated_sa != 'None' else 'Personal account'}{NC}"
540
+ f"Using {GREEN}{adc_active_short:<{name_width}}{NC} for {BLUE}Libraries/Tools{NC} (warehouse, Terraform, Python clients)\n"
339
541
  )
340
542
 
341
- _test_gcp_credentials(user_account, impersonated_sa)
543
+ # Run tests silently, they will print to stderr only on failure
544
+ _test_gcp_credentials(cli_user, cli_impersonation)
342
545
 
343
546
 
344
547
  @gcp_app.command("login")
345
548
  def gcp_login():
346
- """Authenticate with GCP using your Google account."""
347
- _run_gcloud_login()
348
- print("\nTo activate devcon service account impersonation, run:")
349
- print(f' {YELLOW}eval "$(dh gcp use-devcon --export)"{NC}')
350
- print("To use your personal account permissions, run:")
351
- print(f' {YELLOW}eval "$(dh gcp use-user --export)"{NC}')
549
+ """Authenticate user & configure CLI to impersonate devcon SA."""
550
+ # Step 1: Authenticate the user (updates ADC for user)
551
+ _run_gcloud_login() # Uses device flow
552
+
553
+ # Step 2: Configure gcloud CLI for devcon SA impersonation
554
+ print(f"\n{BLUE}Configuring gcloud CLI to impersonate {GCP_DEVCON_SA}...{NC}")
555
+ set_sa_rc, _, set_sa_err = _gcloud_set_config(
556
+ "auth/impersonate_service_account", GCP_DEVCON_SA
557
+ )
558
+ if set_sa_rc != 0:
559
+ print(
560
+ f"{RED}Error setting impersonation config: {set_sa_err}{NC}",
561
+ file=sys.stderr,
562
+ )
563
+ print(f"{YELLOW}Warning: CLI impersonation failed to configure.{NC}")
564
+ # Attempt to show status anyway before exiting command
565
+ print("\n{BLUE}Current status:{NC}")
566
+ gcp_status()
567
+ return
568
+
569
+ set_proj_rc, _, set_proj_err = _gcloud_set_config("project", GCP_PROJECT_ID)
570
+ if set_proj_rc != 0:
571
+ print(f"{RED}Error setting project config: {set_proj_err}{NC}", file=sys.stderr)
572
+ # Continue, but warn user
573
+
574
+ # Step 3: Print configuration options
575
+ print(f"\n{GREEN}Login successful. CLI configured for devcon impersonation.{NC}")
576
+ print(f"{BLUE}--- Common Configuration Commands ---\n{NC}")
577
+
578
+ cmd_width = 25 # Adjusted width for dh commands
579
+
580
+ print(f" {BLUE}Set CLI to use User:{NC}")
581
+ print(f" {YELLOW}{f'dh gcp use-user':<{cmd_width}}{NC}")
582
+
583
+ print(f" {BLUE}Set CLI to use Devcon SA:{NC}")
584
+ print(
585
+ f" {YELLOW}{f'dh gcp use-devcon':<{cmd_width}}{NC} {GREEN}(Current default after login){NC}"
586
+ )
587
+
588
+ print(f" {BLUE}Set Libraries/Tools (ADC) to use User:{NC}")
589
+ print(f" {YELLOW}{f'dh gcp use-user-adc':<{cmd_width}}{NC}")
590
+
591
+ print(f" {BLUE}Set Libraries/Tools (ADC) to use Devcon SA:{NC}")
592
+ print(f" {YELLOW}{f'dh gcp use-devcon-adc':<{cmd_width}}{NC}")
593
+
594
+ # Step 4: Show current status automatically
595
+ print(f"\n{BLUE}--- Current Status ---{NC}")
596
+ gcp_status()
352
597
 
353
598
 
354
599
  @gcp_app.command("use-devcon")
355
600
  def gcp_use_devcon(
356
601
  export: bool = typer.Option(
357
- False, "--export", "-x", help="Print export commands for the current shell."
602
+ False,
603
+ "--export",
604
+ "-x",
605
+ help="Deprecated. Has no effect. Settings are applied directly via gcloud config.",
606
+ hidden=True,
358
607
  ),
359
608
  ):
360
- """Configure gcloud CLI to impersonate the devcon SA by setting RC file variables.
609
+ """Configure gcloud CLI to impersonate the devcon SA.
361
610
 
362
- NOTE: This command DOES NOT automatically update Application Default Credentials (ADC).
363
- After running this, or after sourcing the export commands, you may need to run:
364
- 'gcloud auth application-default login --impersonate-service-account=...' manually
365
- if you need libraries (like DVC) to use these credentials.
611
+ This command updates gcloud configuration settings directly.
612
+ It DOES NOT modify shell RC files or require `eval`.
613
+ It DOES NOT automatically update Application Default Credentials (ADC) for impersonation.
366
614
 
367
- Ensures the primary user login is valid first to allow potential impersonation.
615
+ Ensures the primary user login is valid first.
368
616
  """
617
+ if export:
618
+ print(
619
+ f"{YELLOW}Warning: --export/-x is deprecated and has no effect. "
620
+ f"GCP settings are now managed via gcloud config.{NC}",
621
+ file=sys.stderr,
622
+ )
623
+
369
624
  if not _is_gcp_user_authenticated():
370
625
  print(
371
626
  f"{RED}Error: GCP user authentication is invalid or requires interactive login.{NC}",
372
627
  file=sys.stderr,
373
628
  )
374
629
  print(
375
- f"{YELLOW}Please run 'gcloud auth login' interactively first, then try this command again.{NC}",
630
+ f"{YELLOW}Please run 'dh gcp login' interactively first, then try this command again.{NC}",
376
631
  file=sys.stderr,
377
632
  )
378
633
  sys.exit(1)
379
634
 
380
- # Set gcloud CLI impersonation (persisted to RC files)
381
- _modify_rc_files("CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT", f"'{GCP_DEVCON_SA}'")
382
- _modify_rc_files("GOOGLE_CLOUD_PROJECT", f"'{GCP_PROJECT_ID}'")
383
-
384
- if export:
385
- # Print export commands for the current shell (for gcloud CLI)
386
- print(f"export CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT='{GCP_DEVCON_SA}'")
387
- print(f"export GOOGLE_CLOUD_PROJECT='{GCP_PROJECT_ID}'")
635
+ print(f"{BLUE}Configuring gcloud CLI to impersonate {GCP_DEVCON_SA}...{NC}")
388
636
 
389
- # Print confirmation and instructions for ADC to stderr
637
+ # Set gcloud CLI impersonation via config
638
+ set_sa_rc, _, set_sa_err = _gcloud_set_config(
639
+ "auth/impersonate_service_account", GCP_DEVCON_SA
640
+ )
641
+ if set_sa_rc != 0:
390
642
  print(
391
- f"{GREEN}GCP gcloud CLI impersonation for '{GCP_DEVCON_SA}' exported.{NC}",
643
+ f"{RED}Error setting impersonation config: {set_sa_err}{NC}",
392
644
  file=sys.stderr,
393
645
  )
646
+ sys.exit(1)
647
+
648
+ set_proj_rc, _, set_proj_err = _gcloud_set_config("project", GCP_PROJECT_ID)
649
+ if set_proj_rc != 0:
650
+ print(f"{RED}Error setting project config: {set_proj_err}{NC}", file=sys.stderr)
651
+ # Continue, but warn user
652
+
653
+ # Check for lingering legacy environment variable
654
+ if _get_env_var("CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT"):
394
655
  print(
395
- f"{YELLOW}NOTE: ADC file not updated by export. To update ADC for libraries, run:{NC}",
656
+ f"{YELLOW}Warning: Legacy env var CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT is set.{NC}",
396
657
  file=sys.stderr,
397
658
  )
398
659
  print(
399
- f"{YELLOW} gcloud auth application-default login --impersonate-service-account={GCP_DEVCON_SA}{NC}",
660
+ f"{YELLOW} This may override gcloud config. Consider running:{NC}",
400
661
  file=sys.stderr,
401
662
  )
402
- else:
403
- # Print confirmation
404
- print(
405
- f"{GREEN}RC files updated to use devcon SA for future gcloud CLI sessions.{NC}"
406
- )
407
- print(f"To apply gcloud CLI settings in current shell, run:")
408
- print(f' {YELLOW}eval "$(dh gcp use-devcon --export)"{NC}')
409
- print(
410
- f"{YELLOW}NOTE: ADC file not updated. To update ADC for libraries, run:{NC}"
411
- )
412
663
  print(
413
- f"{YELLOW} gcloud auth application-default login --impersonate-service-account={GCP_DEVCON_SA}{NC}"
664
+ f"{YELLOW} unset CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT{NC}",
665
+ file=sys.stderr,
414
666
  )
415
667
 
668
+ print(f"\n{GREEN}GCP CLI configured to use devcon SA ({GCP_DEVCON_SA}).{NC}")
669
+ print(f"Project set to: {GCP_PROJECT_ID}")
670
+ print(
671
+ f"{YELLOW}NOTE: If libraries/tools (e.g., for DVC, Terraform) need to use impersonation, update Application Default Credentials (ADC) manually:{NC}"
672
+ )
673
+ print(
674
+ f"{YELLOW} gcloud auth application-default login --impersonate-service-account={GCP_DEVCON_SA}{NC}"
675
+ )
676
+ print(f"Run 'dh gcp status' to verify CLI configuration.")
677
+
416
678
 
417
679
  @gcp_app.command("use-user")
418
680
  def gcp_use_user(
419
681
  export: bool = typer.Option(
420
- False, "--export", "-x", help="Print export commands for the current shell."
682
+ False,
683
+ "--export",
684
+ "-x",
685
+ help="Deprecated. Has no effect. Settings are applied directly via gcloud config.",
686
+ hidden=True,
421
687
  ),
422
688
  ):
423
- """Configure gcloud CLI to use the personal user account by setting RC file variables.
689
+ """Configure gcloud CLI to use the personal user account via gcloud config.
424
690
 
425
- NOTE: This command DOES NOT automatically update Application Default Credentials (ADC).
426
- After running this, or after sourcing the export commands, you may need to run:
427
- 'gcloud auth application-default login' manually
428
- if you need libraries (like DVC) to use your personal credentials.
691
+ This command updates gcloud configuration settings directly.
692
+ It DOES NOT modify shell RC files or require `eval`.
693
+ It DOES NOT automatically update Application Default Credentials (ADC).
429
694
 
430
695
  Ensures the primary user login is valid first.
431
696
  """
697
+ if export:
698
+ print(
699
+ f"{YELLOW}Warning: --export/-x is deprecated and has no effect. "
700
+ f"GCP settings are now managed via gcloud config.{NC}",
701
+ file=sys.stderr,
702
+ )
703
+
432
704
  if not _is_gcp_user_authenticated():
433
705
  print(
434
706
  f"{RED}Error: GCP user authentication is invalid or requires interactive login.{NC}",
435
707
  file=sys.stderr,
436
708
  )
437
709
  print(
438
- f"{YELLOW}Please run 'gcloud auth login' interactively first, then try this command again.{NC}",
710
+ f"{YELLOW}Please run 'dh gcp login' interactively first, then try this command again.{NC}",
439
711
  file=sys.stderr,
440
712
  )
441
713
  sys.exit(1)
442
714
 
443
- # Unset gcloud CLI impersonation (persisted to RC files)
444
- _modify_rc_files("CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT", None)
445
- _modify_rc_files("GOOGLE_CLOUD_PROJECT", f"'{GCP_PROJECT_ID}'")
715
+ print(f"{BLUE}Configuring gcloud CLI to use personal user account...{NC}")
446
716
 
447
- if export:
448
- # Print export commands for the current shell (for gcloud CLI)
449
- print(f"unset CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT")
450
- print(f"export GOOGLE_CLOUD_PROJECT='{GCP_PROJECT_ID}'")
717
+ # Unset gcloud CLI impersonation via config
718
+ unset_sa_rc, _, unset_sa_err = _gcloud_unset_config(
719
+ "auth/impersonate_service_account"
720
+ )
721
+ if unset_sa_rc != 0:
722
+ print(
723
+ f"{RED}Error unsetting impersonation config: {unset_sa_err}{NC}",
724
+ file=sys.stderr,
725
+ )
726
+ # Continue, but warn user
451
727
 
452
- # Print confirmation and instructions for ADC to stderr
728
+ set_proj_rc, _, set_proj_err = _gcloud_set_config("project", GCP_PROJECT_ID)
729
+ if set_proj_rc != 0:
730
+ print(f"{RED}Error setting project config: {set_proj_err}{NC}", file=sys.stderr)
731
+ # Continue, but warn user
732
+
733
+ # Check for lingering legacy environment variable
734
+ if _get_env_var("CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT"):
453
735
  print(
454
- f"{GREEN}GCP gcloud CLI impersonation unset and exported.{NC}",
736
+ f"{YELLOW}Warning: Legacy env var CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT is set.{NC}",
455
737
  file=sys.stderr,
456
738
  )
457
739
  print(
458
- f"{YELLOW}NOTE: ADC file not updated by export. To update ADC for libraries, run:{NC}",
740
+ f"{YELLOW} This may interfere with using your personal account. Consider running:{NC}",
459
741
  file=sys.stderr,
460
742
  )
461
743
  print(
462
- f"{YELLOW} gcloud auth application-default login{NC}",
744
+ f"{YELLOW} unset CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT{NC}",
463
745
  file=sys.stderr,
464
746
  )
747
+
748
+ print(f"\n{GREEN}GCP CLI configured to use personal account.{NC}")
749
+ print(f"Project set to: {GCP_PROJECT_ID}")
750
+ print(
751
+ f"{YELLOW}NOTE: If libraries/tools (e.g., for DVC, Terraform) need to use impersonation, update Application Default Credentials (ADC) manually:{NC}"
752
+ )
753
+ print(f"{YELLOW} gcloud auth application-default login{NC}")
754
+ print(f"Run 'dh gcp status' to verify CLI configuration.")
755
+
756
+
757
+ # === NEW ADC Commands ===
758
+
759
+
760
+ @gcp_app.command("use-user-adc")
761
+ def gcp_use_user_adc():
762
+ """Configure Libraries/Tools (ADC) to use your PERSONAL account."""
763
+ if not _is_gcp_user_authenticated():
764
+ print(
765
+ f"{RED}Error: GCP user authentication is invalid or requires interactive login.{NC}",
766
+ file=sys.stderr,
767
+ )
768
+ print(
769
+ f"{YELLOW}Please run 'dh gcp login' interactively first.{NC}",
770
+ file=sys.stderr,
771
+ )
772
+ sys.exit(1)
773
+
774
+ print(f"{BLUE}Attempting to configure ADC for your personal user account...{NC}")
775
+ print(
776
+ f"{YELLOW}This may require you to complete a browser authentication flow.{NC}"
777
+ )
778
+
779
+ gcloud_path = _find_executable("gcloud")
780
+ cmd = [gcloud_path, "auth", "application-default", "login"]
781
+
782
+ # Allow interaction, don't capture output
783
+ returncode, _, _ = _run_command(
784
+ cmd, capture=False, check=False, suppress_output=False
785
+ )
786
+
787
+ if returncode == 0:
788
+ print(f"\n{GREEN}Successfully configured ADC for personal user account.{NC}")
789
+ print(f"{BLUE}--- Current Status ---{NC}")
790
+ gcp_status() # Show status after successful change
465
791
  else:
466
- # Print confirmation
467
792
  print(
468
- f"{GREEN}RC files updated to use personal account for future gcloud CLI sessions.{NC}"
793
+ f"{RED}Failed to configure ADC (Return code: {returncode}). Check messages above.{NC}",
794
+ file=sys.stderr,
795
+ )
796
+ sys.exit(1)
797
+
798
+
799
+ @gcp_app.command("use-devcon-adc")
800
+ def gcp_use_devcon_adc():
801
+ """Configure Libraries/Tools (ADC) to use the DEVCON service account."""
802
+ if not _is_gcp_user_authenticated():
803
+ print(
804
+ f"{RED}Error: GCP user authentication is invalid or requires interactive login.{NC}",
805
+ file=sys.stderr,
806
+ )
807
+ print(
808
+ f"{YELLOW}Please run 'dh gcp login' interactively first.{NC}",
809
+ file=sys.stderr,
469
810
  )
470
- print(f"To apply gcloud CLI settings in current shell, run:")
471
- print(f' {YELLOW}eval "$(dh gcp use-user --export)"{NC}')
811
+ sys.exit(1)
812
+
813
+ print(f"{BLUE}Attempting to configure ADC for devcon SA ({GCP_DEVCON_SA})...{NC}")
814
+ print(
815
+ f"{YELLOW}This may require you to complete a browser authentication flow.{NC}"
816
+ )
817
+
818
+ gcloud_path = _find_executable("gcloud")
819
+ cmd = [
820
+ gcloud_path,
821
+ "auth",
822
+ "application-default",
823
+ "login",
824
+ f"--impersonate-service-account={GCP_DEVCON_SA}",
825
+ ]
826
+
827
+ # Allow interaction, don't capture output
828
+ returncode, _, _ = _run_command(
829
+ cmd, capture=False, check=False, suppress_output=False
830
+ )
831
+
832
+ if returncode == 0:
472
833
  print(
473
- f"{YELLOW}NOTE: ADC file not updated. To update ADC for libraries, run:{NC}"
834
+ f"\n{GREEN}Successfully configured ADC for devcon SA ({GCP_DEVCON_SA}).{NC}"
474
835
  )
475
- print(f"{YELLOW} gcloud auth application-default login{NC}")
836
+ print(f"{BLUE}--- Current Status ---{NC}")
837
+ gcp_status() # Show status after successful change
838
+ else:
839
+ print(
840
+ f"{RED}Failed to configure ADC (Return code: {returncode}). Check messages above.{NC}",
841
+ file=sys.stderr,
842
+ )
843
+ sys.exit(1)
844
+
845
+
846
+ # === End NEW ADC Commands ===
476
847
 
477
848
 
478
849
  # --- AWS Commands ---
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.0.8
3
+ Version: 1.0.9
4
4
  Summary: Common tools for all the repos at Dayhoff Labs
5
5
  Author: Daniel Martin-Alarcon
6
6
  Author-email: dma@dayhofflabs.com
@@ -2,7 +2,7 @@ dayhoff_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  dayhoff_tools/chemistry/standardizer.py,sha256=uMn7VwHnx02nc404eO6fRuS4rsl4dvSPf2ElfZDXEpY,11188
3
3
  dayhoff_tools/chemistry/utils.py,sha256=jt-7JgF-GeeVC421acX-bobKbLU_X94KNOW24p_P-_M,2257
4
4
  dayhoff_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- dayhoff_tools/cli/cloud_commands.py,sha256=Z_zCtZYH0thljiOSkr1fjFZal7a8DZtY-dH7uZEa4YI,22571
5
+ dayhoff_tools/cli/cloud_commands.py,sha256=KiYEuD3nSg8QPWBYfrhdze2La_CJe4iqK-8uOAHyS8U,35827
6
6
  dayhoff_tools/cli/main.py,sha256=E1-3rZ26LMgJVKBz6CdJwsHs9fJsSGa2_9tot3hNgz4,3604
7
7
  dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
8
8
  dayhoff_tools/cli/utility_commands.py,sha256=AsZMpvUNP2xjn5cZ9_BrBNHggfuy6PLwlHw1WP0d7o0,9602
@@ -25,7 +25,7 @@ dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
25
25
  dayhoff_tools/structure.py,sha256=ufN3gAodQxhnt7psK1VTQeu9rKERmo_PhoxIbB4QKMw,27660
26
26
  dayhoff_tools/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJqE4,16456
27
27
  dayhoff_tools/warehouse.py,sha256=TqV8nex1AluNaL4JuXH5zuu9P7qmE89lSo6f_oViy6U,14965
28
- dayhoff_tools-1.0.8.dist-info/METADATA,sha256=Lq5sNvPmJa0zu7EMKyS8ID0moWaqUhoQpbcb1kCphco,1930
29
- dayhoff_tools-1.0.8.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
30
- dayhoff_tools-1.0.8.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
31
- dayhoff_tools-1.0.8.dist-info/RECORD,,
28
+ dayhoff_tools-1.0.9.dist-info/METADATA,sha256=z5v8zbt4z_B5283bqkkFlybQox_tZacU-Z6JPNyg1vc,1930
29
+ dayhoff_tools-1.0.9.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
30
+ dayhoff_tools-1.0.9.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
31
+ dayhoff_tools-1.0.9.dist-info/RECORD,,