From fc87fbed14ef991972fb40faed33ba554a3f51f5 Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Tue, 26 May 2026 13:44:46 +0800 Subject: [PATCH] fix(credential): drop misleading [id] positional from create The `credential create [id]` positional was ignored on the wire: the CLI POSTed to /apisix/admin/consumers//credentials with the value placed in `name`, and the server returned a fresh UUID for `id`. The help text promised something the code never did. Rename the positional to `[name]` and reflect that in: - the `Use` string and short description (id is server-generated) - the `Options.ID` -> `Options.Name` field (matches what the value actually maps to in the request body) - the existing positional test, renamed to drop "ID" from the title - the user-guide doc, with a note that the returned `id` differs from the positional `[name]` Behavior on the wire is unchanged. Closes #36. --- docs/user-guide/credential.md | 19 ++++++++++++++++--- pkg/cmd/credential/create/create.go | 14 +++++++------- pkg/cmd/credential/create/create_test.go | 8 ++++---- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/docs/user-guide/credential.md b/docs/user-guide/credential.md index 6f3ed3a..fe30d21 100644 --- a/docs/user-guide/credential.md +++ b/docs/user-guide/credential.md @@ -56,14 +56,19 @@ a7 credential get key-1 --consumer jack -g default ### `a7 credential create` -Creates a new credential for a consumer from a JSON or YAML file. +Creates a new credential for a consumer. The credential `id` is always server-generated; the optional positional `[name]` (or the `name` field in a file payload) is stored as the credential's display name. + +**Usage:** `a7 credential create [name] --consumer -g ` | Flag | Short | Default | Description | |------|-------|---------|-------------| | `--consumer` | | | Consumer username (required) | | `--gateway-group` | `-g` | | Target gateway group (required) | -| `--file` | `-f` | | Path to credential config file (required) | -| `--output` | `-o` | `yaml` | Output format (`json`, `yaml`) | +| `--file` | `-f` | | Path to credential config file (JSON or YAML) | +| `--desc` | | | Credential description | +| `--plugins-json` | | | Plugins as a JSON string | +| `--labels` | | | Labels in `key=value` format (repeatable) | +| `--output` | `-o` | `json` | Output format (`json`, `yaml`) | **Examples:** @@ -72,6 +77,14 @@ Create a credential from YAML: a7 credential create --consumer jack -g default -f key-auth.yaml ``` +Create a credential inline, naming it `my-key`: +```bash +a7 credential create my-key --consumer jack -g default \ + --plugins-json '{"key-auth":{"key":"secret-token-val"}}' +``` + +> Note: the returned `id` is assigned by API7 EE and will not match the positional `[name]`. Use the returned `id` for subsequent `get`, `update`, and `delete` calls. + ### `a7 credential update` Updates an existing credential by ID using a JSON or YAML file. API7 EE uses JSON Patch (RFC 6902) for partial updates. diff --git a/pkg/cmd/credential/create/create.go b/pkg/cmd/credential/create/create.go index a5e9185..25b921e 100644 --- a/pkg/cmd/credential/create/create.go +++ b/pkg/cmd/credential/create/create.go @@ -23,7 +23,7 @@ type Options struct { GatewayGroup string Consumer string File string - ID string + Name string Desc string PluginsJSON string @@ -33,15 +33,15 @@ type Options struct { func NewCmd(f *cmd.Factory) *cobra.Command { opts := &Options{IO: f.IOStreams, Client: f.HttpClient, Config: f.Config} c := &cobra.Command{ - Use: "create [id]", - Short: "Create a credential", + Use: "create [name]", + Short: "Create a credential (id is server-generated)", Args: cobra.MaximumNArgs(1), RunE: func(c *cobra.Command, args []string) error { opts.Output, _ = c.Flags().GetString("output") opts.GatewayGroup, _ = c.Flags().GetString("gateway-group") opts.Consumer, _ = c.Flags().GetString("consumer") if len(args) > 0 { - opts.ID = args[0] + opts.Name = args[0] } return actionRun(opts) }, @@ -79,8 +79,8 @@ func actionRun(opts *Options) error { return err } - if opts.ID != "" { - payload["name"] = opts.ID + if opts.Name != "" { + payload["name"] = opts.Name delete(payload, "id") } else if _, ok := payload["name"]; ok { delete(payload, "id") @@ -139,7 +139,7 @@ func actionRun(opts *Options) error { labels[parts[0]] = parts[1] } - bodyReq := api.Credential{Name: opts.ID, Desc: opts.Desc} + bodyReq := api.Credential{Name: opts.Name, Desc: opts.Desc} if len(pl) > 0 { bodyReq.Plugins = pl } diff --git a/pkg/cmd/credential/create/create_test.go b/pkg/cmd/credential/create/create_test.go index 0763990..5251c40 100644 --- a/pkg/cmd/credential/create/create_test.go +++ b/pkg/cmd/credential/create/create_test.go @@ -57,7 +57,7 @@ func TestCreateCredential_JSONOutput(t *testing.T) { registry.Verify(t) } -func TestCreateCredential_PositionalIDSetsName(t *testing.T) { +func TestCreateCredential_PositionalSetsName(t *testing.T) { ios, _, out, _ := iostreams.Test() registry := &httpmock.Registry{} registry.RegisterResponder(http.MethodPost, "/apisix/admin/consumers/alice/credentials", func(req *http.Request) (httpmock.Response, error) { @@ -66,17 +66,17 @@ func TestCreateCredential_PositionalIDSetsName(t *testing.T) { return httpmock.Response{}, fmt.Errorf("decode request body: %w", err) } if payload["name"] != "cred1" { - return httpmock.Response{}, fmt.Errorf("expected positional id to map to name, got payload: %#v", payload) + return httpmock.Response{}, fmt.Errorf("expected positional to map to name, got payload: %#v", payload) } if _, ok := payload["id"]; ok { - return httpmock.Response{}, fmt.Errorf("expected positional id to be normalized to name only, got payload: %#v", payload) + return httpmock.Response{}, fmt.Errorf("expected positional to be normalized to name only, got payload: %#v", payload) } return httpmock.JSONResponse(`{"id":"generated","name":"cred1"}`), nil }) opts := &Options{IO: ios, Client: func() (*http.Client, error) { return registry.GetClient(), nil }, Config: func() (config.Config, error) { return &mockConfig{baseURL: "http://api.local", gatewayGroup: "gg1"}, nil - }, Consumer: "alice", GatewayGroup: "gg1", ID: "cred1", PluginsJSON: `{"key-auth":{"key":"k"}}`} + }, Consumer: "alice", GatewayGroup: "gg1", Name: "cred1", PluginsJSON: `{"key-auth":{"key":"k"}}`} if err := actionRun(opts); err != nil { t.Fatalf("actionRun failed: %v", err)