Skip to content

feat(admin-cli): generate RPC commands from protobuf#4124

Draft
poroh wants to merge 1 commit into
NVIDIA:mainfrom
poroh:feat/generate-admin-cli-from-proto
Draft

feat(admin-cli): generate RPC commands from protobuf#4124
poroh wants to merge 1 commit into
NVIDIA:mainfrom
poroh:feat/generate-admin-cli-from-proto

Conversation

@poroh

@poroh poroh commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Many admin CLI commands only parse arguments, construct a protobuf request, and invoke one unary RPC. Maintaining separate argument, command, and dispatch modules for them creates repetitive boilerplate.

This PR explores generating that plumbing from protobuf custom annotations. It migrates tpm-ca delete and managed-host set-primary-dpu as initial examples. Commands requiring custom validation, output formatting, or multiple RPC calls remain handwritten.

Related issues

N/A

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)

Additional Notes

Add custom protobuf options for CLI command and argument metadata,
then compile annotated unary RPCs into clap argument types and
dispatch implementations. Migrate TPM CA deletion and primary DPU
selection as initial examples.

Signed-off-by: Dmitry Porokh <dporokh@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 064dd83c-46b7-4073-9c10-66905d89bfae

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:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
crates/admin-cli/src/managed_host/mod.rs (1)

63-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Second UUID placeholder deviates from the canonical value.

The DPU machine id example uses abcdef01-2345-6789-abcd-ef0123456789 instead of the documented canonical UUID placeholder 12345678-1234-5678-90ab-cdef01234567. Since this command takes two machine-id arguments, consider reusing the canonical UUID for both (as other multi-argument examples in this codebase do) for consistency with the documented placeholder convention.

As per path instructions, crates/admin-cli/**: "Use realistic, consistent placeholder values instead of angle brackets: UUIDs as '12345678-1234-5678-90ab-cdef01234567'."

🤖 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 `@crates/admin-cli/src/managed_host/mod.rs` around lines 63 - 76, Update the
examples in SetPrimaryDpu so both machine-ID arguments use the canonical UUID
placeholder 12345678-1234-5678-90ab-cdef01234567, preserving the existing
command syntax and --reboot example.

Source: Path instructions

crates/macros/src/lib.rs (1)

77-99: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

mut ctx binding is unconditional even when no run arm needs mutability.

The generated dispatch() always declares mut ctx: RuntimeContext (line 122), but only run_arms borrow it mutably. dispatch_arms consume ctx by value and the new rpc_arms only need &ctx.api_client.0. An enum whose variants are entirely #[dispatch]/#[rpc] (no plain run variants) will trigger unused_mut, which fails the build under this repo's "warnings as errors" policy. Neither of the two migrated enums in this PR hits this today, but it's worth guarding against now that #[rpc] adds a third non-mutating variant kind.

As per coding guidelines, **/*.rs: "Enable strict linting: treat warnings and Clippy lints as errors, avoid #[allow(...)]."

🔧 Proposed conditional `mut`
-    let output = quote! {
-        impl crate::cfg::dispatch::Dispatch for `#name` {
-            async fn dispatch(
-                self,
-                mut ctx: crate::cfg::runtime::RuntimeContext,
-            ) -> crate::errors::CarbideCliResult<()> {
+    let ctx_binding = if run_arms.is_empty() {
+        quote! { ctx }
+    } else {
+        quote! { mut ctx }
+    };
+
+    let output = quote! {
+        impl crate::cfg::dispatch::Dispatch for `#name` {
+            async fn dispatch(
+                self,
+                `#ctx_binding`: crate::cfg::runtime::RuntimeContext,
+            ) -> crate::errors::CarbideCliResult<()> {

Also applies to: 118-134

🤖 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 `@crates/macros/src/lib.rs` around lines 77 - 99, Update the generated dispatch
method to make the RuntimeContext binding mutable only when at least one plain
run arm requires mutable access. Keep dispatch_arms consuming ctx and rpc_arms
borrowing it immutably, while preserving mutable binding for enums that generate
run_arms; avoid suppressing the unused_mut lint.

Source: Coding guidelines

crates/tonic-client-wrapper/src/codegen.rs (2)

1353-1388: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add table-driven negative-path coverage for make_admin_cli_command.

Only the happy path is tested. None of the validation branches (duplicate --long, positional with long/short set, missing help text, unknown kind, repeated field, oneof field, and — critically — a proto3 optional field) are exercised. A case with an optional field would have caught the bug flagged above at Line 459.

As per coding guidelines, **/*.{rs,toml} should "Use table-driven tests for functions mapping inputs to outputs or errors... scenarios! with Outcome for fallible operations." make_admin_cli_command returns Result<TokenStream>, making it a good candidate.

🤖 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 `@crates/tonic-client-wrapper/src/codegen.rs` around lines 1353 - 1388, Expand
test_make_admin_cli_command into a table-driven scenarios! test using Outcome
for successful and failing make_admin_cli_command calls. Cover duplicate long
options, positional arguments with long or short names, missing help, unknown
argument kind, repeated fields, oneof fields, and proto3 optional fields,
asserting each expected result or validation error while retaining the existing
happy-path assertions.

Source: Coding guidelines


472-481: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Enum fields always surface as raw i32 CLI args.

resolve_field_primitive_type maps Type::Enum to "i32" and is checked before the field.type_name branch, so any admin_cli-annotated enum field will always become a bare i32 clap argument (e.g. --mode 2) rather than the generated Rust enum, even for enums this codebase already derives clap::ValueEnum for (DpuMode, DeletedFilter, etc. in build.rs). This degrades operator UX (no validation, no symbolic names) for any future RPC with an enum request field.

No currently-migrated command (TPM CA delete / set-primary-dpu) appears to hit this path, so it's not blocking, but worth tracking before more RPCs are migrated.

🤖 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 `@crates/tonic-client-wrapper/src/codegen.rs` around lines 472 - 481, Update
the base-type selection around resolve_field_primitive_type so enum fields use
field.type_name and convert_protobuf_type_to_rust_type, allowing generated
clap::ValueEnum types instead of raw i32 arguments. Keep primitive scalar fields
on the existing resolve_field_primitive_type path and preserve the
unsupported-type error for fields lacking both representations.
🤖 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 `@crates/tonic-client-wrapper/src/codegen.rs`:
- Around line 453-470: Update the oneof rejection in admin_cli_field_type to
exempt fields where proto3_optional is true, matching the distinction already
used by make_convenience_converter; continue rejecting genuine oneof fields
while allowing supported proto3 optional fields to reach the existing Option<T>
handling.

---

Nitpick comments:
In `@crates/admin-cli/src/managed_host/mod.rs`:
- Around line 63-76: Update the examples in SetPrimaryDpu so both machine-ID
arguments use the canonical UUID placeholder
12345678-1234-5678-90ab-cdef01234567, preserving the existing command syntax and
--reboot example.

In `@crates/macros/src/lib.rs`:
- Around line 77-99: Update the generated dispatch method to make the
RuntimeContext binding mutable only when at least one plain run arm requires
mutable access. Keep dispatch_arms consuming ctx and rpc_arms borrowing it
immutably, while preserving mutable binding for enums that generate run_arms;
avoid suppressing the unused_mut lint.

In `@crates/tonic-client-wrapper/src/codegen.rs`:
- Around line 1353-1388: Expand test_make_admin_cli_command into a table-driven
scenarios! test using Outcome for successful and failing make_admin_cli_command
calls. Cover duplicate long options, positional arguments with long or short
names, missing help, unknown argument kind, repeated fields, oneof fields, and
proto3 optional fields, asserting each expected result or validation error while
retaining the existing happy-path assertions.
- Around line 472-481: Update the base-type selection around
resolve_field_primitive_type so enum fields use field.type_name and
convert_protobuf_type_to_rust_type, allowing generated clap::ValueEnum types
instead of raw i32 arguments. Keep primitive scalar fields on the existing
resolve_field_primitive_type path and preserve the unsupported-type error for
fields lacking both representations.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 256d31ab-bf3e-4875-b750-163c858aef60

📥 Commits

Reviewing files that changed from the base of the PR and between bae23dd and ae38e6d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • Cargo.toml
  • crates/admin-cli/src/managed_host/mod.rs
  • crates/admin-cli/src/managed_host/set_primary_dpu/args.rs
  • crates/admin-cli/src/managed_host/set_primary_dpu/cmd.rs
  • crates/admin-cli/src/managed_host/set_primary_dpu/mod.rs
  • crates/admin-cli/src/managed_host/tests.rs
  • crates/admin-cli/src/tpm_ca/delete/args.rs
  • crates/admin-cli/src/tpm_ca/delete/cmd.rs
  • crates/admin-cli/src/tpm_ca/delete/mod.rs
  • crates/admin-cli/src/tpm_ca/mod.rs
  • crates/admin-cli/src/tpm_ca/tests.rs
  • crates/macros/src/lib.rs
  • crates/rpc/Cargo.toml
  • crates/rpc/build.rs
  • crates/rpc/proto/admin_cli.proto
  • crates/rpc/proto/forge.proto
  • crates/rpc/src/admin_cli.rs
  • crates/rpc/src/protos/mod.rs
  • crates/tonic-client-wrapper/Cargo.toml
  • crates/tonic-client-wrapper/src/codegen.rs
💤 Files with no reviewable changes (6)
  • crates/admin-cli/src/managed_host/set_primary_dpu/cmd.rs
  • crates/admin-cli/src/tpm_ca/delete/args.rs
  • crates/admin-cli/src/tpm_ca/delete/cmd.rs
  • crates/admin-cli/src/managed_host/set_primary_dpu/mod.rs
  • crates/admin-cli/src/managed_host/set_primary_dpu/args.rs
  • crates/admin-cli/src/tpm_ca/delete/mod.rs

Comment on lines +453 to +470
fn admin_cli_field_type(
&self,
method_name: &str,
field: &FieldDescriptorProto,
required: bool,
) -> Result<TokenStream> {
if field.oneof_index.is_some() {
return Err(invalid_cli_annotation(format!(
"{method_name}.{}: oneof fields are not supported",
field.name()
)));
}
if field.label == Some(Label::Repeated as i32) {
return Err(invalid_cli_annotation(format!(
"{method_name}.{}: repeated fields are not supported",
field.name()
)));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Proto3 optional fields will be incorrectly rejected as "oneof" fields.

field.oneof_index.is_some() fires for any field belonging to a oneof — including the synthetic one-field oneof protoc generates for proto3 optional fields (proto3_optional = true). This function has no exemption for that case, so any CLI-annotated field marked optional in the .proto will fail codegen with "oneof fields are not supported", even though the very code below it (field_is_optional(field) && !requiredOption<T>) is written to support exactly this scenario. This same file already gets this distinction right in make_convenience_converter (field.oneof_index.is_some() && field.proto3_optional.is_none_or(|o| !o)).

🐛 Proposed fix
-        if field.oneof_index.is_some() {
+        if field.oneof_index.is_some() && !field.proto3_optional.unwrap_or(false) {
             return Err(invalid_cli_annotation(format!(
                 "{method_name}.{}: oneof fields are not supported",
                 field.name()
             )));
         }
📝 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
fn admin_cli_field_type(
&self,
method_name: &str,
field: &FieldDescriptorProto,
required: bool,
) -> Result<TokenStream> {
if field.oneof_index.is_some() {
return Err(invalid_cli_annotation(format!(
"{method_name}.{}: oneof fields are not supported",
field.name()
)));
}
if field.label == Some(Label::Repeated as i32) {
return Err(invalid_cli_annotation(format!(
"{method_name}.{}: repeated fields are not supported",
field.name()
)));
}
fn admin_cli_field_type(
&self,
method_name: &str,
field: &FieldDescriptorProto,
required: bool,
) -> Result<TokenStream> {
if field.oneof_index.is_some() && !field.proto3_optional.unwrap_or(false) {
return Err(invalid_cli_annotation(format!(
"{method_name}.{}: oneof fields are not supported",
field.name()
)));
}
if field.label == Some(Label::Repeated as i32) {
return Err(invalid_cli_annotation(format!(
"{method_name}.{}: repeated fields are not supported",
field.name()
)));
}
🤖 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 `@crates/tonic-client-wrapper/src/codegen.rs` around lines 453 - 470, Update
the oneof rejection in admin_cli_field_type to exempt fields where
proto3_optional is true, matching the distinction already used by
make_convenience_converter; continue rejecting genuine oneof fields while
allowing supported proto3 optional fields to reach the existing Option<T>
handling.

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