github2gerrit 0.1.4__py3-none-any.whl → 0.1.6__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.
github2gerrit/config.py CHANGED
@@ -80,6 +80,8 @@ KNOWN_KEYS: set[str] = {
80
80
  "ISSUE_ID",
81
81
  "G2G_VERBOSE",
82
82
  "G2G_SKIP_GERRIT_COMMENTS",
83
+ "G2G_ENABLE_DERIVATION",
84
+ "G2G_AUTO_SAVE_CONFIG",
83
85
  "GITHUB_TOKEN",
84
86
  # Optional inputs (reusable workflow compatibility)
85
87
  "GERRIT_SERVER",
@@ -128,11 +130,7 @@ def _coerce_value(raw: str) -> str:
128
130
  # Normalize escaped newline sequences into real newlines so that values
129
131
  # like SSH keys or known_hosts entries can be specified inline using
130
132
  # '\n' or '\r\n' in configuration files.
131
- normalized_newlines = (
132
- unquoted.replace("\\r\\n", "\n")
133
- .replace("\\n", "\n")
134
- .replace("\r\n", "\n")
135
- )
133
+ normalized_newlines = unquoted.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\r\n", "\n")
136
134
  b = _normalize_bool_like(normalized_newlines)
137
135
  return b if b is not None else normalized_newlines
138
136
 
@@ -313,6 +311,210 @@ def filter_known(
313
311
  return {k: v for k, v in cfg.items() if k in KNOWN_KEYS}
314
312
 
315
313
 
314
+ def _is_github_actions_context() -> bool:
315
+ """Detect if running in GitHub Actions environment."""
316
+ return os.getenv("GITHUB_ACTIONS") == "true" or os.getenv("GITHUB_EVENT_NAME", "").strip() != ""
317
+
318
+
319
+ def _is_local_cli_context() -> bool:
320
+ """Detect if running as local CLI tool."""
321
+ return not _is_github_actions_context()
322
+
323
+
324
+ def derive_gerrit_parameters(organization: str | None) -> dict[str, str]:
325
+ """Derive Gerrit parameters from GitHub organization name.
326
+
327
+ Args:
328
+ organization: GitHub organization name
329
+
330
+ Returns:
331
+ Dict with derived parameter values:
332
+ - GERRIT_SSH_USER_G2G: [org].gh2gerrit
333
+ - GERRIT_SSH_USER_G2G_EMAIL: releng+[org]-gh2gerrit@linuxfoundation.org
334
+ - GERRIT_SERVER: gerrit.[org].org
335
+ """
336
+ if not organization:
337
+ return {}
338
+
339
+ org = organization.strip().lower()
340
+ return {
341
+ "GERRIT_SSH_USER_G2G": f"{org}.gh2gerrit",
342
+ "GERRIT_SSH_USER_G2G_EMAIL": (f"releng+{org}-gh2gerrit@linuxfoundation.org"),
343
+ "GERRIT_SERVER": f"gerrit.{org}.org",
344
+ }
345
+
346
+
347
+ def apply_parameter_derivation(
348
+ cfg: dict[str, str],
349
+ organization: str | None = None,
350
+ save_to_config: bool = True,
351
+ ) -> dict[str, str]:
352
+ """Apply dynamic parameter derivation for missing Gerrit parameters.
353
+
354
+ This function derives standard Gerrit parameters when they are not
355
+ explicitly configured. The derivation is based on the GitHub organization:
356
+
357
+ - gerrit_ssh_user_g2g: [org].gh2gerrit
358
+ - gerrit_ssh_user_g2g_email: releng+[org]-gh2gerrit@linuxfoundation.org
359
+ - gerrit_server: gerrit.[org].org
360
+
361
+ Derivation behavior depends on execution context:
362
+ - GitHub Actions: Automatic derivation when organization is available
363
+ - Local CLI: Requires G2G_ENABLE_DERIVATION=true for automatic
364
+ derivation
365
+
366
+ Args:
367
+ cfg: Configuration dictionary to augment
368
+ organization: GitHub organization name for derivation
369
+ save_to_config: Whether to save derived parameters to config file
370
+
371
+ Returns:
372
+ Configuration dictionary with derived values for missing parameters
373
+ """
374
+ if not organization:
375
+ return cfg
376
+
377
+ # Check execution context to determine derivation strategy
378
+ is_github_actions = _is_github_actions_context()
379
+ enable_derivation = is_github_actions or os.getenv("G2G_ENABLE_DERIVATION", "").strip().lower() in (
380
+ "1",
381
+ "true",
382
+ "yes",
383
+ "on",
384
+ )
385
+
386
+ if not enable_derivation:
387
+ log.debug(
388
+ "Parameter derivation disabled for local CLI usage. "
389
+ "Set G2G_ENABLE_DERIVATION=true to enable automatic derivation."
390
+ )
391
+ return cfg
392
+
393
+ # Only derive parameters that are missing or empty
394
+ derived = derive_gerrit_parameters(organization)
395
+ result = dict(cfg)
396
+ newly_derived = {}
397
+
398
+ for key, value in derived.items():
399
+ if key not in result or not result[key].strip():
400
+ log.debug(
401
+ "Deriving %s from organization '%s': %s (context: %s)",
402
+ key,
403
+ organization,
404
+ value,
405
+ "GitHub Actions" if is_github_actions else "Local CLI",
406
+ )
407
+ result[key] = value
408
+ newly_derived[key] = value
409
+
410
+ if newly_derived:
411
+ log.info(
412
+ "Derived parameters applied for organization '%s' (%s): %s",
413
+ organization,
414
+ "GitHub Actions" if is_github_actions else "Local CLI",
415
+ ", ".join(f"{k}={v}" for k, v in newly_derived.items()),
416
+ )
417
+ # Save newly derived parameters to configuration file for future use
418
+ # Default to true for local CLI, false for GitHub Actions
419
+ default_auto_save = "false" if _is_github_actions_context() else "true"
420
+ auto_save_enabled = os.getenv("G2G_AUTO_SAVE_CONFIG", default_auto_save).strip().lower() in (
421
+ "1",
422
+ "true",
423
+ "yes",
424
+ "on",
425
+ )
426
+ if save_to_config and newly_derived and auto_save_enabled:
427
+ # Save to config in local CLI mode to create persistent configuration
428
+ try:
429
+ save_derived_parameters_to_config(organization, newly_derived)
430
+ log.info(
431
+ "Automatically saved derived parameters to configuration "
432
+ "file for organization '%s'. "
433
+ "This creates a persistent configuration that you can "
434
+ "customize if needed.",
435
+ organization,
436
+ )
437
+ except Exception as exc:
438
+ log.warning("Failed to save derived parameters to config: %s", exc)
439
+
440
+ return result
441
+
442
+
443
+ def save_derived_parameters_to_config(
444
+ organization: str,
445
+ derived_params: dict[str, str],
446
+ config_path: str | None = None,
447
+ ) -> None:
448
+ """Save derived parameters to the organization's configuration file.
449
+
450
+ This function updates the configuration file to include any derived
451
+ parameters that are not already present in the organization section.
452
+ This creates a persistent configuration that users can modify if needed.
453
+
454
+ Args:
455
+ organization: GitHub organization name for config section
456
+ derived_params: Dictionary of derived parameter key-value pairs
457
+ config_path: Path to config file (optional, uses default if not
458
+ provided)
459
+
460
+ Raises:
461
+ Exception: If saving fails
462
+ """
463
+ if not organization or not derived_params:
464
+ return
465
+
466
+ if config_path is None:
467
+ config_path = os.getenv("G2G_CONFIG_PATH", "").strip() or DEFAULT_CONFIG_PATH
468
+
469
+ config_file = Path(config_path).expanduser()
470
+
471
+ try:
472
+ # Only update when a configuration file already exists
473
+ if not config_file.exists():
474
+ log.debug(
475
+ "Configuration file does not exist; skipping auto-save of derived parameters: %s",
476
+ config_file,
477
+ )
478
+ return
479
+
480
+ # Parse existing content using configparser
481
+ cp = _load_ini(config_file)
482
+
483
+ # Find or create the organization section
484
+ org_section = _select_section(cp, organization)
485
+ if org_section is None:
486
+ # Section doesn't exist, we'll need to add it
487
+ cp.add_section(organization)
488
+ org_section = organization
489
+
490
+ # Add derived parameters that don't already exist
491
+ params_added = []
492
+ for key, value in derived_params.items():
493
+ if not cp.has_option(org_section, key):
494
+ cp.set(org_section, key, f'"{value}"')
495
+ params_added.append(key)
496
+
497
+ # Only write if we added parameters
498
+ if params_added:
499
+ # Write the updated configuration
500
+ with config_file.open("w", encoding="utf-8") as f:
501
+ cp.write(f)
502
+
503
+ log.debug(
504
+ "Saved derived parameters to configuration file %s [%s]: %s",
505
+ config_file,
506
+ organization,
507
+ ", ".join(params_added),
508
+ )
509
+
510
+ except Exception as exc:
511
+ log.warning(
512
+ "Failed to save derived parameters to configuration file %s: %s",
513
+ config_file,
514
+ exc,
515
+ )
516
+
517
+
316
518
  def overlay_missing(
317
519
  primary: dict[str, str],
318
520
  fallback: dict[str, str],