Skip to content

[wip] puzzlectl: add credential provision subcommand#10

Draft
sallyom wants to merge 1 commit intoLobsterTrap:mainfrom
sallyom:creds-provision
Draft

[wip] puzzlectl: add credential provision subcommand#10
sallyom wants to merge 1 commit intoLobsterTrap:mainfrom
sallyom:creds-provision

Conversation

@sallyom
Copy link
Copy Markdown

@sallyom sallyom commented Mar 29, 2026

verified in a podman machine session-bus setup

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a new provision subcommand to the credential management CLI tool, enabling users to provision credentials for a specified branch with support for JSON or plain text output formatting.

Copilot AI review requested due to automatic review settings March 29, 2026 16:14
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5df595ce-4b5e-4e79-b250-1aef5ada4bf3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new CLI subcommand variant Provision was added to the CredentialAction enum in the puzzlectl tool, enabling users to provision credentials with a specified branch ID. The D-Bus credential command dispatcher now routes this action to call client.provision_credentials(&branch_id).await? and formats the output as either JSON or plain text.

Changes

Cohort / File(s) Summary
Credential Provisioning CLI Command
crates/puzzlectl/src/main.rs
Added Provision { branch_id: String } variant to CredentialAction enum. Implemented dispatch logic to call client.provision_credentials(&branch_id).await? and format the response as JSON or plain text based on the output configuration.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A branch to provision, credentials so fine,
With --branch-id flags, everything will align,
The D-Bus dispatcher hops along with delight,
Pretty-printed JSON output, oh what a sight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a credential provision subcommand to puzzlectl. It accurately reflects the primary intent of the PR.

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

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

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

@sallyom sallyom changed the title puzzlectl: add credential provision subcommand [wip] puzzlectl: add credential provision subcommand Mar 29, 2026
@sallyom sallyom marked this pull request as draft March 29, 2026 16:17
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/puzzlectl/src/main.rs (1)

1361-1363: Add client-side branch_id validation before provisioning call.

For consistency with other branch-oriented commands and clearer CLI errors, validate branch_id before invoking D-Bus.

♻️ Proposed patch
                     CredentialAction::Provision { branch_id } => {
+                        validate_branch_id(&branch_id)?;
                         let result = client.provision_credentials(&branch_id).await?;
                         match cli.output {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/puzzlectl/src/main.rs` around lines 1361 - 1363, Before calling
client.provision_credentials in the CredentialAction::Provision arm, validate
the provided branch_id on the client side (same validation used by other branch
commands: e.g., parse/validate as BranchId::from_str or the existing
validate_branch_id/parse_branch_id helper) and return a friendly CLI error if
malformed or empty instead of invoking client.provision_credentials(&branch_id).
Move the validation above the await call, and only call
client.provision_credentials(&branch_id).await? when validation succeeds; ensure
error handling uses the same cli.output/error formatting as the other
branch-oriented commands.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/puzzlectl/src/main.rs`:
- Around line 1361-1363: Before calling client.provision_credentials in the
CredentialAction::Provision arm, validate the provided branch_id on the client
side (same validation used by other branch commands: e.g., parse/validate as
BranchId::from_str or the existing validate_branch_id/parse_branch_id helper)
and return a friendly CLI error if malformed or empty instead of invoking
client.provision_credentials(&branch_id). Move the validation above the await
call, and only call client.provision_credentials(&branch_id).await? when
validation succeeds; ensure error handling uses the same cli.output/error
formatting as the other branch-oriented commands.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7b1ae55-7b73-49be-b9df-dc76947e4e46

📥 Commits

Reviewing files that changed from the base of the PR and between a79a3d2 and 05f9f7f.

📒 Files selected for processing (1)
  • crates/puzzlectl/src/main.rs

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new puzzlectl credential provision subcommand to trigger per-branch phantom-token credential provisioning via the existing D-Bus client API (verified in a podman machine session-bus setup).

Changes:

  • Adds CredentialAction::Provision { branch_id } to the CLI subcommands.
  • Wires the new action into the credential command handler and prints the D-Bus result as JSON or text.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
}
}
CredentialAction::Provision { branch_id } => {
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

CredentialAction::Provision passes branch_id straight to D-Bus without the validate_branch_id(...) check that other branch-id-taking commands use (see BranchAction/AgentAction). Please validate the branch id before calling client.provision_credentials(...) to fail fast and preserve the CLI’s path-traversal/format hardening pattern (M-ctl2).

Suggested change
CredentialAction::Provision { branch_id } => {
CredentialAction::Provision { branch_id } => {
let branch_id = validate_branch_id(&branch_id)?;

Copilot uses AI. Check for mistakes.
/// Provision phantom-token credentials for a branch
Provision {
/// Branch ID to provision credentials for
branch_id: String,
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The new Provision subcommand uses the argument name branch_id, while other subcommands that take a branch id consistently call the positional field id (e.g., BranchAction::{Inspect,Approve,...} and AgentAction::{Info,Kill}). Consider renaming this field to id (and updating the match arm) to keep the CLI surface consistent and reduce cognitive overhead.

Suggested change
branch_id: String,
id: String,

Copilot uses AI. Check for mistakes.
@sallyom sallyom force-pushed the creds-provision branch 2 times, most recently from c0e69f5 to 35343d7 Compare March 29, 2026 17:21
Copilot AI review requested due to automatic review settings March 29, 2026 17:21
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread podman/puzzle-podman
Comment on lines +618 to +621
done < <(
python3 - "$PROVISION_RESULT" <<'PY'
import json
import sys
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The new JSON parsing relies on python3, but this script runs with set -euo pipefail and currently does not verify that python3 exists. If python3 is missing (or fails), this can unexpectedly abort puzzle-podman even though credential provisioning is intended to be non-fatal. Consider guarding this block with a command -v python3 check and falling back to an empty env-var list (or using an already-available parser) when Python is unavailable.

Copilot uses AI. Check for mistakes.
Comment thread podman/puzzle-podman
Comment on lines +628 to +630
for env_var, phantom in payload.get("phantom_env_vars", {}).items():
if env_var and phantom:
print(f"{env_var}={phantom}")
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The embedded Python only catches JSON parse errors; if phantom_env_vars is present but not an object (or any other runtime error occurs), it will raise and emit a traceback to stderr. Since provisioning is best-effort here, it would be safer to wrap the iteration in a broader try/except (or validate the type before calling .items()) and exit 0 on unexpected shapes.

Suggested change
for env_var, phantom in payload.get("phantom_env_vars", {}).items():
if env_var and phantom:
print(f"{env_var}={phantom}")
try:
env_map = payload.get("phantom_env_vars", {})
# Ensure we only iterate if phantom_env_vars is a mapping; otherwise, exit quietly.
if not isinstance(env_map, dict):
sys.exit(0)
for env_var, phantom in env_map.items():
if env_var and phantom:
print(f"{env_var}={phantom}")
except Exception:
# Best-effort provisioning: on any unexpected error, exit successfully.
sys.exit(0)

Copilot uses AI. Check for mistakes.
Comment on lines +1401 to +1410
let domains = c
.get("target_domains")
.and_then(|v| v.as_array())
.map(|domains| {
domains.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>()
})
.unwrap_or_default();
(allowed_profiles.is_empty()
|| allowed_profiles.iter().any(|p| *p == "*" || *p == profile))
&& domains.iter().any(|d| *d == domain)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The credential test filtering checks target_domains using exact string equality, but the proxy/credential logic supports glob patterns like *.openai.com (see domain_matches in puzzle-proxy). This can produce false negatives (e.g., api.openai.com won't match *.openai.com). Consider reusing the same domain-matching logic (domain_matches / StoredCredential::matches_domain) when deciding whether a credential matches the requested domain.

Copilot uses AI. Check for mistakes.
Comment on lines +1408 to +1409
(allowed_profiles.is_empty()
|| allowed_profiles.iter().any(|p| *p == "*" || *p == profile))
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The profile filter treats an empty allowed_profiles list as matching all profiles (allowed_profiles.is_empty() || ...), but the runtime credential logic (StoredCredential::matches_profile) only matches when the list contains "*" or the exact profile. This can cause puzzlectl credential test to report matches that won't actually inject. Consider aligning the CLI semantics with the backend (either remove the is_empty() fast-path here, or ensure empty lists are normalized to "*" at creation time).

Suggested change
(allowed_profiles.is_empty()
|| allowed_profiles.iter().any(|p| *p == "*" || *p == profile))
allowed_profiles
.iter()
.any(|p| *p == "*" || *p == profile)

Copilot uses AI. Check for mistakes.
Signed-off-by: sallyom <somalley@redhat.com>
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.

2 participants