Skip to content

Remove Caliper AWS secret and improve the vault - #82

Merged
kpouget merged 10 commits into
openshift-psap:mainfrom
kpouget:mlflow
Jun 25, 2026
Merged

Remove Caliper AWS secret and improve the vault#82
kpouget merged 10 commits into
openshift-psap:mainfrom
kpouget:mlflow

Conversation

@kpouget

@kpouget kpouget commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added phase-aware vault handling, making vault setup and listing work more consistently across orchestration commands.
    • Added a cleanup command in CI orchestration for post-run teardown.
    • Updated MLflow export and replot configurations to use Vault-based secret references.
  • Bug Fixes

    • Simplified export and replot flows to handle single-run and multi-run cases more reliably.
    • Improved vault listing so optional vaults are included where applicable.

@openshift-ci

openshift-ci Bot commented Jun 25, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign kpouget for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kpouget, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 57 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b10aee2-f950-4d15-95f3-d54c6ae3f52d

📥 Commits

Reviewing files that changed from the base of the PR and between a798fe0 and 2fe496b.

📒 Files selected for processing (1)
  • projects/skeleton/orchestration/test_skeleton.py
📝 Walkthrough

Walkthrough

Phase-based vault helpers were added and adopted by skeleton and llm_d orchestration commands. Caliper export and replot code stopped handling AWS credential-file paths, and several orchestration configs now reference Vault-backed MLflow secrets.

Changes

Vault and MLflow secret migration

Layer / File(s) Summary
Phase vault helpers
projects/core/library/vault.py
Adds phase-scoped vault lookup, initialization, and list-all helpers that merge global and phase-specific vault config entries.
Phase-aware CI wiring
projects/llm_d/orchestration/ci.py, projects/skeleton/orchestration/ci.py, projects/skeleton/orchestration/config.yaml
Skeleton and llm_d CI entrypoints now use phase-scoped vault initialization and vault listing, and skeleton adds post_cleanup plus phase-aware vault config.
MLflow secret configs
projects/caliper/orchestration/export_config.py, projects/caliper/orchestration/export_config.py, projects/llm_d/orchestration/config.yaml, projects/mcp_gateway/orchestration/config.yaml, projects/rhaiis/orchestration/config.yaml, projects/skeleton/orchestration/config.yaml
Caliper MLflow secret blocks now use vault.name and mlflow_secret, and rhaiis also defines export metadata.
Export and replot flow
projects/caliper/orchestration/export.py, projects/caliper/orchestration/replot.py
Export and replot remove AWS credential-path handling and environment-variable wiring; export now branches directly on discovered run directories.

Sequence Diagram(s)

sequenceDiagram
  participant Main as main(ctx)
  participant VaultInit as vault.phase_vault_init
  participant ResolveEntry as create_fournos_resolve_entrypoint
  participant VaultList as vault.phase_vault_list_all
  Main->>VaultInit: phase_vault_init(ctx.invoked_subcommand)
  Main->>ResolveEntry: pass vault.phase_vault_list_all
  ResolveEntry->>VaultList: list vault names
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • openshift-psap/forge#26: Changes vault.init(...) behavior in projects/core/library/vault.py, which is now wrapped by the new phase_vault_init() helper.
  • openshift-psap/forge#45: Introduces the resolve-fournos-config resolver entrypoint that this PR now wires to vault.phase_vault_list_all.

Poem

🐇 I hop through vaults with phase-bound flair,
No AWS strings are lurking იქ in there.
I sniff out MLflow secrets neat,
Then thump along on cleaner feet.
With carrots high and configs bright,
My burrow hums a tidier light.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: removing Caliper AWS secret handling and introducing improved vault behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kpouget

kpouget commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

/test fournos skeleton
/cluster forge-smoke-testing
/pipeline forge-test-only

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
projects/caliper/orchestration/replot.py (1)

148-155: 🎯 Functional Correctness | 🔴 Critical

Fix the call to run_replot_from_orchestration_config to remove the vault_aws_secret argument.

The caller in projects/core/library/replot.py still passes vault_aws_secret (line 71), but the function definition in projects/caliper/orchestration/replot.py (line 148) no longer accepts this parameter. This removal of the argument from the signature and failure to update the call site will result in a runtime error.

Call site details
return run_replot_from_orchestration_config(
    replot_url=replot_url,
    artifact_directory=artifact_directory,
    vault_name=vault_name,
    vault_mlflow_secret=vault_mlflow_secret,
    vault_aws_secret=vault_aws_secret,  # Remove this line
    keep_replot_dir=keep_replot_dir,
    postprocess_config=postprocess_config,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/caliper/orchestration/replot.py` around lines 148 - 155, The call to
run_replot_from_orchestration_config in the replot helper still passes
vault_aws_secret even though the function signature no longer accepts it. Update
the caller in projects/core/library/replot.py to stop passing vault_aws_secret
and keep the argument list aligned with run_replot_from_orchestration_config and
its parameters vault_name, vault_mlflow_secret, keep_replot_dir, and
postprocess_config.
🧹 Nitpick comments (1)
projects/caliper/orchestration/replot.py (1)

100-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove stale AWS credential comments.

These comments still describe AWS credential setup after the AWS credential path plumbing was removed, which makes the new MLflow-secret-only contract ambiguous.

Suggested cleanup
-            # Set tracking URI before creating client (AWS credentials need to be set first)
+            # Set tracking URI after mlflow_connection_env applies MLflow credentials.
             mlflow.set_tracking_uri(mlflow_uri)
-    # Get AWS credentials if provided
-

Also applies to: 183-183

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/caliper/orchestration/replot.py` around lines 100 - 103, The
comments around mlflow_connection_env in replot.py are stale and still mention
AWS credential setup even though that path was removed. Update the nearby
comments in the replot flow and any matching comment near the export/replot
setup to describe only the current MLflow-secret-based behavior, using the
mlflow_connection_env and mlflow.set_tracking_uri call sites as the anchors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@projects/caliper/orchestration/export.py`:
- Around line 96-101: The multi-run dry-run path in export() sets ret to 0 but
never creates status_yaml, so the later status file write/open path can fail
with FileNotFoundError. Update the dry-run branch in the multi-run handling to
either generate the expected status_yaml payload or return/skip before any code
that opens it, using the existing export() flow and status_yaml variable as the
main anchors.

In `@projects/core/library/vault.py`:
- Around line 492-520: phase_vault_init currently only reads phase-scoped keys
via _phase_vault_get_for_phase, so legacy flat vault lists are ignored and
nothing gets initialized. Update phase_vault_init to fall back to the existing
flat vault list when the phase-based lookups return empty, while still
preserving the current all/all-optional and phase-specific behavior. Use the
existing symbols phase_vault_init, _phase_vault_get_for_phase,
phase_vault_list_all, and init to keep compatibility with both legacy and phased
vault configurations.
- Around line 527-535: Handle the missing vaults config before iterating in
resolve-fournos-config: the vault_config value from
config.project.get_config("vaults") can be absent or non-mapping, so update the
vault handling in the vault resolution logic to treat a missing value as empty
and only call .items() on a dict-like new-format config. Keep the existing
list-vs-dict behavior in vault_config processing, but add a safe fallback so a
project without a vaults stanza returns an empty secretRefs list instead of
crashing.

---

Outside diff comments:
In `@projects/caliper/orchestration/replot.py`:
- Around line 148-155: The call to run_replot_from_orchestration_config in the
replot helper still passes vault_aws_secret even though the function signature
no longer accepts it. Update the caller in projects/core/library/replot.py to
stop passing vault_aws_secret and keep the argument list aligned with
run_replot_from_orchestration_config and its parameters vault_name,
vault_mlflow_secret, keep_replot_dir, and postprocess_config.

---

Nitpick comments:
In `@projects/caliper/orchestration/replot.py`:
- Around line 100-103: The comments around mlflow_connection_env in replot.py
are stale and still mention AWS credential setup even though that path was
removed. Update the nearby comments in the replot flow and any matching comment
near the export/replot setup to describe only the current MLflow-secret-based
behavior, using the mlflow_connection_env and mlflow.set_tracking_uri call sites
as the anchors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94b71e3f-50b1-495b-829b-32c1e89413e4

📥 Commits

Reviewing files that changed from the base of the PR and between 58e5f2a and a798fe0.

📒 Files selected for processing (10)
  • projects/caliper/orchestration/export.py
  • projects/caliper/orchestration/export_config.py
  • projects/caliper/orchestration/replot.py
  • projects/core/library/vault.py
  • projects/llm_d/orchestration/ci.py
  • projects/llm_d/orchestration/config.yaml
  • projects/mcp_gateway/orchestration/config.yaml
  • projects/rhaiis/orchestration/config.yaml
  • projects/skeleton/orchestration/ci.py
  • projects/skeleton/orchestration/config.yaml
💤 Files with no reviewable changes (4)
  • projects/caliper/orchestration/export_config.py
  • projects/llm_d/orchestration/config.yaml
  • projects/rhaiis/orchestration/config.yaml
  • projects/mcp_gateway/orchestration/config.yaml

Comment on lines +96 to +101
if len(run_dirs) > 1:
if export_cfg.dry_run:
logger.info(
"dry-run: would export %d run dirs from %s (skipping)", len(run_dirs), from_path
)
ret = 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Write or return status for multi-run dry runs.

Line 101 sets ret = 0 without producing status_yaml, but Line 134 still opens that file, so multi-run dry-runs will fail with FileNotFoundError after logging success.

Possible localized fix
         if export_cfg.dry_run:
             logger.info(
                 "dry-run: would export %d run dirs from %s (skipping)", len(run_dirs), from_path
             )
-            ret = 0
+            return {
+                "mlflow": {
+                    "status": "dry_run",
+                    "detail": f"would export {len(run_dirs)} run dirs from {from_path}",
+                }
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(run_dirs) > 1:
if export_cfg.dry_run:
logger.info(
"dry-run: would export %d run dirs from %s (skipping)", len(run_dirs), from_path
)
ret = 0
if len(run_dirs) > 1:
if export_cfg.dry_run:
logger.info(
"dry-run: would export %d run dirs from %s (skipping)", len(run_dirs), from_path
)
return {
"mlflow": {
"status": "dry_run",
"detail": f"would export {len(run_dirs)} run dirs from {from_path}",
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/caliper/orchestration/export.py` around lines 96 - 101, The
multi-run dry-run path in export() sets ret to 0 but never creates status_yaml,
so the later status file write/open path can fail with FileNotFoundError. Update
the dry-run branch in the multi-run handling to either generate the expected
status_yaml payload or return/skip before any code that opens it, using the
existing export() flow and status_yaml variable as the main anchors.

Comment on lines +492 to +520
def phase_vault_init(phase: str) -> None:
"""Initialize vaults for a specific phase."""

# Get global mandatory vaults (always loaded)
global_mandatory = _phase_vault_get_for_phase("all")

# Get phase-specific mandatory vaults
phase_mandatory = _phase_vault_get_for_phase(phase)

# Combine all mandatory vaults
mandatory_vaults = global_mandatory + phase_mandatory

# Get global optional vaults (always loaded optionally)
global_optional = _phase_vault_get_for_phase("all-optional")

# Get phase-specific optional vaults
phase_optional = _phase_vault_get_for_phase(f"{phase}-optional")

# Combine all optional vaults
optional_vaults = global_optional + phase_optional

if not mandatory_vaults and not optional_vaults:
logger.info(f"No vault to initialize for phase '{phase}'")
return

# Initialize both mandatory and optional vaults in a single call
# Mandatory vaults: strict=True (automation fails if missing/invalid)
# Optional vaults: strict=False (automation continues with warnings if missing/invalid)
init(mandatory_vaults=mandatory_vaults, optional_vaults=optional_vaults)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve legacy flat vault lists during phase init.

Line 492 now initializes only vaults.all / phase keys. If vaults is still a flat list, _phase_vault_get_for_phase(...) returns [] and no vaults are initialized, while phase_vault_list_all() still supports that legacy format.

Proposed fix
 def phase_vault_init(phase: str) -> None:
     """Initialize vaults for a specific phase."""
+    from projects.core.library import config
+
+    vault_config = config.project.get_config("vaults", [])
+    if isinstance(vault_config, list):
+        if not vault_config:
+            logger.info(f"No vault to initialize for phase '{phase}'")
+            return
+
+        init(vaults=vault_config)
+        return
 
     # Get global mandatory vaults (always loaded)
     global_mandatory = _phase_vault_get_for_phase("all")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def phase_vault_init(phase: str) -> None:
"""Initialize vaults for a specific phase."""
# Get global mandatory vaults (always loaded)
global_mandatory = _phase_vault_get_for_phase("all")
# Get phase-specific mandatory vaults
phase_mandatory = _phase_vault_get_for_phase(phase)
# Combine all mandatory vaults
mandatory_vaults = global_mandatory + phase_mandatory
# Get global optional vaults (always loaded optionally)
global_optional = _phase_vault_get_for_phase("all-optional")
# Get phase-specific optional vaults
phase_optional = _phase_vault_get_for_phase(f"{phase}-optional")
# Combine all optional vaults
optional_vaults = global_optional + phase_optional
if not mandatory_vaults and not optional_vaults:
logger.info(f"No vault to initialize for phase '{phase}'")
return
# Initialize both mandatory and optional vaults in a single call
# Mandatory vaults: strict=True (automation fails if missing/invalid)
# Optional vaults: strict=False (automation continues with warnings if missing/invalid)
init(mandatory_vaults=mandatory_vaults, optional_vaults=optional_vaults)
def phase_vault_init(phase: str) -> None:
"""Initialize vaults for a specific phase."""
from projects.core.library import config
vault_config = config.project.get_config("vaults", [])
if isinstance(vault_config, list):
if not vault_config:
logger.info(f"No vault to initialize for phase '{phase}'")
return
init(vaults=vault_config)
return
# Get global mandatory vaults (always loaded)
global_mandatory = _phase_vault_get_for_phase("all")
# Get phase-specific mandatory vaults
phase_mandatory = _phase_vault_get_for_phase(phase)
# Combine all mandatory vaults
mandatory_vaults = global_mandatory + phase_mandatory
# Get global optional vaults (always loaded optionally)
global_optional = _phase_vault_get_for_phase("all-optional")
# Get phase-specific optional vaults
phase_optional = _phase_vault_get_for_phase(f"{phase}-optional")
# Combine all optional vaults
optional_vaults = global_optional + phase_optional
if not mandatory_vaults and not optional_vaults:
logger.info(f"No vault to initialize for phase '{phase}'")
return
# Initialize both mandatory and optional vaults in a single call
# Mandatory vaults: strict=True (automation fails if missing/invalid)
# Optional vaults: strict=False (automation continues with warnings if missing/invalid)
init(mandatory_vaults=mandatory_vaults, optional_vaults=optional_vaults)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/core/library/vault.py` around lines 492 - 520, phase_vault_init
currently only reads phase-scoped keys via _phase_vault_get_for_phase, so legacy
flat vault lists are ignored and nothing gets initialized. Update
phase_vault_init to fall back to the existing flat vault list when the
phase-based lookups return empty, while still preserving the current
all/all-optional and phase-specific behavior. Use the existing symbols
phase_vault_init, _phase_vault_get_for_phase, phase_vault_list_all, and init to
keep compatibility with both legacy and phased vault configurations.

Comment on lines +527 to +535
vault_config = config.project.get_config("vaults")

# Handle both old format (list) and new format (dict with categories)
if isinstance(vault_config, list):
return vault_config

# New format: collect all vaults from all categories
all_vaults = []
for _category, vaults in vault_config.items():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle missing vaults before calling .items().

Line 527 has no default, then Line 535 assumes a mapping. A project without a vaults stanza can crash resolve-fournos-config instead of producing an empty secretRefs list.

Proposed fix
-    vault_config = config.project.get_config("vaults")
+    vault_config = config.project.get_config("vaults", [])
 
     # Handle both old format (list) and new format (dict with categories)
     if isinstance(vault_config, list):
         return vault_config
+
+    if not vault_config:
+        return []
+
+    if not isinstance(vault_config, dict):
+        raise TypeError("vaults must be a list or mapping of phase/category names to lists")
 
     # New format: collect all vaults from all categories
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vault_config = config.project.get_config("vaults")
# Handle both old format (list) and new format (dict with categories)
if isinstance(vault_config, list):
return vault_config
# New format: collect all vaults from all categories
all_vaults = []
for _category, vaults in vault_config.items():
vault_config = config.project.get_config("vaults", [])
# Handle both old format (list) and new format (dict with categories)
if isinstance(vault_config, list):
return vault_config
if not vault_config:
return []
if not isinstance(vault_config, dict):
raise TypeError("vaults must be a list or mapping of phase/category names to lists")
# New format: collect all vaults from all categories
all_vaults = []
for _category, vaults in vault_config.items():
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/core/library/vault.py` around lines 527 - 535, Handle the missing
vaults config before iterating in resolve-fournos-config: the vault_config value
from config.project.get_config("vaults") can be absent or non-mapping, so update
the vault handling in the vault resolution logic to treat a missing value as
empty and only call .items() on a dict-like new-format config. Keep the existing
list-vs-dict behavior in vault_config processing, but add a safe fallback so a
project without a vaults stanza returns an empty secretRefs list instead of
crashing.

@psap-forge-bot

Copy link
Copy Markdown

🟢 Test of 'skeleton test' succeeded after 00 hours 00 minutes 00 seconds 🟢

• Link to the test results.

• Generated 2 Caliper report(s):

  • throughput_chart.html
  • summary_table.html

Test configuration:

ci_job.cluster: forge-smoke-testing
ci_job.exclusive: true
ci_job.fjob: forge-skeleton-20260625-114416
ci_job.name: skeleton
ci_job.owner: kpouget
project.args: []
project.name: skeleton

Execution logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant