Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions docs/user-guide/credential.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <user> -g <gateway-group>`
Comment on lines +59 to +61
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align later config examples with the new server-generated id guidance.

This section correctly says id is server-generated, but later YAML examples still show setting id during create flows, which is misleading after this change.

Also applies to: 86-87

🤖 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 `@docs/user-guide/credential.md` around lines 59 - 61, Update the later YAML
examples used in the create flows to remove any manual `id` fields and instead
rely on the server-generated id; specifically edit the examples that show
creating credentials (refer to the `a7 credential create [name] --consumer
<user> -g <gateway-group>` usage) and any YAML blocks that currently include
`id:` (also referenced near the second create example around the later block).
Ensure the examples only include allowed input fields such as `name`,
`consumer`, `gateway-group` (or equivalent) and add a brief comment or sentence
showing that the `id` will be returned/generated by the server rather than
provided by the user.


| 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:**

Expand All @@ -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.
Expand Down
14 changes: 7 additions & 7 deletions pkg/cmd/credential/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Options struct {
GatewayGroup string
Consumer string
File string
ID string
Name string

Desc string
PluginsJSON string
Expand All @@ -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)
},
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/cmd/credential/create/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
Expand Down
Loading