This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 480f4bca7 fix(rmqctl): fetch the confirm token after interactive
confirmation (#4623)
480f4bca7 is described below
commit 480f4bca7f462837d00c0c637552861dad283d0a
Author: Apulupie <[email protected]>
AuthorDate: Mon Sep 21 21:06:23 2026 +0800
fix(rmqctl): fetch the confirm token after interactive confirmation (#4623)
`rmqctl`'s automatic dry-run preview in `catalog.go` was gated on
`tool.RiskLevel != "L1" && runtime.options.yes`, but `confirmRisk` only reaches
that gate with `--yes` when the confirmation was interactive. Such a run went
straight to the real call, and the server's `ToolMutationFilter` verifies a
confirm token on every non-dry-run invocation, so `ToolTokenService` rejected
it with `CONFIRMATION_TOKEN_REQUIRED`: every L2/L3 mutation confirmed at the
prompt failed and the flow the pro [...]
The `--yes` half of the condition is gone, so an interactively confirmed
run fetches its token the way a `--yes` run already did. All 13 L2/L3 tools
under `tool-catalog/tools` declare `dry_run`, so the injected preview stays
schema-valid; explicit `--confirm-token`, `--dry-run` and the non-TTY early
failure in `defaultConfirm` are unchanged, and no catalog yaml moved.
Fixes #4624
---
rmqctl/cmd/app_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++
rmqctl/cmd/catalog.go | 14 ++++++------
2 files changed, 66 insertions(+), 6 deletions(-)
diff --git a/rmqctl/cmd/app_test.go b/rmqctl/cmd/app_test.go
index cbbaab683..71574d711 100644
--- a/rmqctl/cmd/app_test.go
+++ b/rmqctl/cmd/app_test.go
@@ -327,3 +327,61 @@ func TestCatalogInteractiveConfirmAcceptsYes(t *testing.T)
{
t.Fatalf("expected only JSON result in stdout: %s, err=%v",
stdout, err)
}
}
+
+// TestCatalogInteractiveConfirmAutoFetchesToken verifies that after the user
+// answers the interactive confirmation prompt, the client transparently runs
+// the dry-run preview to obtain the confirm_token: the server rejects any
+// non-dry-run mutation without a token, so without this the "Type yes to
+// continue" flow would always end in CONFIRMATION_TOKEN_REQUIRED.
+func TestCatalogInteractiveConfirmAutoFetchesToken(t *testing.T) {
+ const confirmToken = "interactive-confirmation"
+ instanceKey := catalogInstanceArgumentKey(t, "rmq.topic.update")
+ observed := make(chan observedToolCall, 2)
+ server := httptest.NewServer(http.HandlerFunc(func(w
http.ResponseWriter, r *http.Request) {
+ var request types.ToolCallRequest
+ if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ observed <- observedToolCall{r.Method, r.URL.Path,
r.Header.Get("Authorization"), request}
+ mutation := map[string]any{
+ "status": "PLANNED",
+ "instanceId": request.Arguments[instanceKey],
+ "plan": map[string]any{"summary": "Update
topic orders"},
+ "confirm_token": confirmToken,
+ }
+ if dryRun, ok := request.Arguments["dry_run"]; !ok || dryRun !=
true {
+ mutation["status"] = "EXECUTED"
+ mutation["result"] = map[string]any{"topic":
request.Arguments["topicName"]}
+ }
+ writeStudioSuccess(t, w, mutation)
+ }))
+ defer server.Close()
+
+ stdout, stderr, exitCode := executeTestAppWithStdin(t, server.Client(),
server.URL, "dev",
+ "yes\n",
+ "--output", "json",
+ "topic", "update", "--topic-name", "orders", "--write-queues",
"8",
+ )
+ if exitCode != 0 {
+ t.Fatalf("interactive confirm should proceed: exit=%d
stderr=%s", exitCode, stderr)
+ }
+ if !strings.Contains(stderr, "Type \"yes\" to continue:") {
+ t.Fatalf("expected confirmation prompt in stderr: %s", stderr)
+ }
+
+ preview := <-observed
+ if preview.request.Arguments["dry_run"] != true ||
+ preview.request.Arguments["confirm_token"] != nil {
+ t.Fatalf("expected an auto dry-run preview first: %#v",
preview.request)
+ }
+ applyCall := <-observed
+ if applyCall.request.Arguments["dry_run"] != nil ||
+ applyCall.request.Arguments["confirm_token"] != confirmToken {
+ t.Fatalf("expected the apply call to carry the auto-fetched
token: %#v", applyCall.request)
+ }
+ var apply map[string]any
+ if err := json.Unmarshal([]byte(stdout), &apply); err != nil ||
apply["status"] != "EXECUTED" {
+ t.Fatalf("unexpected apply output: %#v, err=%v", apply, err)
+ }
+}
diff --git a/rmqctl/cmd/catalog.go b/rmqctl/cmd/catalog.go
index c2bcfedf8..84469a399 100644
--- a/rmqctl/cmd/catalog.go
+++ b/rmqctl/cmd/catalog.go
@@ -208,12 +208,14 @@ func runTool(
}
// L2/L3 mutations require a server-issued confirm_token from a matching
- // dry-run preview before they execute. When --yes is supplied without
an
- // explicit --confirm-token or --dry-run, transparently run the preview
first
- // to obtain the token so callers need not script the two-phase
handshake
- // manually. L1 tools, explicit dry-runs, and calls that already carry a
- // confirm_token are left untouched.
- if tool.RiskLevel != "L1" && runtime.options.yes {
+ // dry-run preview before they execute. Unless an explicit
--confirm-token
+ // or --dry-run is supplied, transparently run the preview first to
obtain
+ // the token so callers need not script the two-phase handshake
manually —
+ // for scripted --yes calls and for interactively confirmed runs alike,
+ // because the server rejects both with CONFIRMATION_TOKEN_REQUIRED when
+ // the token is missing. L1 tools, explicit dry-runs, and calls that
+ // already carry a confirm_token are left untouched.
+ if tool.RiskLevel != "L1" {
if _, hasToken := arguments["confirm_token"]; !hasToken {
if dryRun, _ := arguments["dry_run"].(bool); !dryRun {
preview := make(map[string]any,
len(arguments)+1)