From 290ec63285fec94f0aea9658aaac49fc2b1cfa11 Mon Sep 17 00:00:00 2001 From: Qi Guo <979918879@qq.com> Date: Wed, 29 Apr 2026 01:23:15 +0800 Subject: [PATCH 1/4] fix: honor secret create file positional id --- pkg/cmd/secret/create/create.go | 3 +++ pkg/cmd/secret/create/create_test.go | 38 ++++++++++++++++++++++++++++ test/e2e/secret_test.go | 4 +-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/secret/create/create.go b/pkg/cmd/secret/create/create.go index f96844d..2174318 100644 --- a/pkg/cmd/secret/create/create.go +++ b/pkg/cmd/secret/create/create.go @@ -79,6 +79,9 @@ func actionRun(opts *Options) error { if err != nil { return err } + if opts.ID != "" { + payload["id"] = opts.ID + } httpClient, err := opts.Client() if err != nil { diff --git a/pkg/cmd/secret/create/create_test.go b/pkg/cmd/secret/create/create_test.go index 68afed5..989fd51 100644 --- a/pkg/cmd/secret/create/create_test.go +++ b/pkg/cmd/secret/create/create_test.go @@ -3,6 +3,8 @@ package create import ( "encoding/json" "net/http" + "os" + "path/filepath" "testing" "github.com/api7/a7/internal/config" @@ -64,6 +66,42 @@ func TestCreateSecret_JSON(t *testing.T) { registry.Verify(t) } +func TestCreateSecret_FileUsesPositionalID(t *testing.T) { + ios, _, out, _ := iostreams.Test() + registry := &httpmock.Registry{} + registry.Register(http.MethodPut, "/apisix/admin/secret_providers/vault/s1", httpmock.JSONResponse(`{"id":"vault/s1","uri":"http://vault","prefix":"kv"}`)) + + file := filepath.Join(t.TempDir(), "secret.json") + if err := os.WriteFile(file, []byte(`{"uri":"http://vault","prefix":"kv","token":"tok"}`), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + 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 + }, + GatewayGroup: "gg1", + ID: "vault/s1", + File: file, + } + + if err := actionRun(opts); err != nil { + t.Fatalf("actionRun failed: %v", err) + } + + var item api.Secret + if err := json.Unmarshal(out.Bytes(), &item); err != nil { + t.Fatalf("failed to parse output: %v", err) + } + if item.ID != "vault/s1" { + t.Fatalf("expected positional id in output, got: %+v", item) + } + + registry.Verify(t) +} + func TestCreateSecret_RequiresID(t *testing.T) { ios, _, _, _ := iostreams.Test() registry := &httpmock.Registry{} diff --git a/test/e2e/secret_test.go b/test/e2e/secret_test.go index 492ce3c..2c7b218 100644 --- a/test/e2e/secret_test.go +++ b/test/e2e/secret_test.go @@ -65,9 +65,7 @@ func TestSecret_CRUD(t *testing.T) { // Create stdout, stderr, err := runA7WithEnv(env, "secret", "create", secretID, "-f", tmpFile, "-g", gatewayGroup) - if err != nil { - t.Skip("secret create failed (vault may not be configured)") - } + require.NoError(t, err, "secret create failed: stdout=%s stderr=%s", stdout, stderr) // Get stdout, stderr, err = runA7WithEnv(env, "secret", "get", secretID, "-g", gatewayGroup) From e7d3330eaa8cee09499b9041ce93c014483e9eba Mon Sep 17 00:00:00 2001 From: Qi Guo <979918879@qq.com> Date: Wed, 29 Apr 2026 22:53:02 +0800 Subject: [PATCH 2/4] test: assert provider-local secret id --- test/e2e/secret_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/secret_test.go b/test/e2e/secret_test.go index 2c7b218..b3a11df 100644 --- a/test/e2e/secret_test.go +++ b/test/e2e/secret_test.go @@ -75,7 +75,7 @@ func TestSecret_CRUD(t *testing.T) { // Get JSON var secret map[string]interface{} runA7JSON(t, env, &secret, "secret", "get", secretID, "-g", gatewayGroup, "-o", "json") - assert.Equal(t, secretID, secret["id"]) + assert.Equal(t, "e2e-secret-crud", secret["id"]) assert.Equal(t, "https://vault.example.com", secret["uri"]) assert.Equal(t, "kv/apisix", secret["prefix"]) From f8dfd921bae108770a509c9a4d57fb893343f8f5 Mon Sep 17 00:00:00 2001 From: Qi Guo <979918879@qq.com> Date: Thu, 30 Apr 2026 00:39:54 +0800 Subject: [PATCH 3/4] fix: require secret id in file create --- pkg/cmd/secret/create/create.go | 2 ++ pkg/cmd/secret/create/create_test.go | 25 +++++++++++++++++++++++++ test/e2e/secret_test.go | 2 ++ 3 files changed, 29 insertions(+) diff --git a/pkg/cmd/secret/create/create.go b/pkg/cmd/secret/create/create.go index 2174318..fb45df5 100644 --- a/pkg/cmd/secret/create/create.go +++ b/pkg/cmd/secret/create/create.go @@ -81,6 +81,8 @@ func actionRun(opts *Options) error { } if opts.ID != "" { payload["id"] = opts.ID + } else if id, ok := payload["id"]; !ok || id == nil || fmt.Sprint(id) == "" { + return fmt.Errorf("secret provider id is required; use a positional arg or --id") } httpClient, err := opts.Client() diff --git a/pkg/cmd/secret/create/create_test.go b/pkg/cmd/secret/create/create_test.go index 989fd51..7c5746d 100644 --- a/pkg/cmd/secret/create/create_test.go +++ b/pkg/cmd/secret/create/create_test.go @@ -102,6 +102,31 @@ func TestCreateSecret_FileUsesPositionalID(t *testing.T) { registry.Verify(t) } +func TestCreateSecret_FileRequiresID(t *testing.T) { + ios, _, _, _ := iostreams.Test() + registry := &httpmock.Registry{} + + file := filepath.Join(t.TempDir(), "secret.json") + if err := os.WriteFile(file, []byte(`{"uri":"http://vault","prefix":"kv","token":"tok"}`), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + err := actionRun(&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 + }, + GatewayGroup: "gg1", + File: file, + }) + if err == nil || err.Error() != "secret provider id is required; use a positional arg or --id" { + t.Fatalf("expected --id required error, got: %v", err) + } + + registry.Verify(t) +} + func TestCreateSecret_RequiresID(t *testing.T) { ios, _, _, _ := iostreams.Test() registry := &httpmock.Registry{} diff --git a/test/e2e/secret_test.go b/test/e2e/secret_test.go index b3a11df..f626fdb 100644 --- a/test/e2e/secret_test.go +++ b/test/e2e/secret_test.go @@ -75,6 +75,8 @@ func TestSecret_CRUD(t *testing.T) { // Get JSON var secret map[string]interface{} runA7JSON(t, env, &secret, "secret", "get", secretID, "-g", gatewayGroup, "-o", "json") + // The runtime secret provider API returns the provider-local ID; the CLI + // still uses the compound ID for addressing resources. assert.Equal(t, "e2e-secret-crud", secret["id"]) assert.Equal(t, "https://vault.example.com", secret["uri"]) assert.Equal(t, "kv/apisix", secret["prefix"]) From 7ac9d5e00b8851cf1a73cd8efb4d834a005a8372 Mon Sep 17 00:00:00 2001 From: Qi Guo <979918879@qq.com> Date: Thu, 30 Apr 2026 01:59:58 +0800 Subject: [PATCH 4/4] fix: reject blank secret file ids --- pkg/cmd/secret/create/create.go | 18 +++++++++++++++--- pkg/cmd/secret/create/create_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/secret/create/create.go b/pkg/cmd/secret/create/create.go index fb45df5..3e63f39 100644 --- a/pkg/cmd/secret/create/create.go +++ b/pkg/cmd/secret/create/create.go @@ -80,9 +80,21 @@ func actionRun(opts *Options) error { return err } if opts.ID != "" { - payload["id"] = opts.ID - } else if id, ok := payload["id"]; !ok || id == nil || fmt.Sprint(id) == "" { - return fmt.Errorf("secret provider id is required; use a positional arg or --id") + id := strings.TrimSpace(opts.ID) + if id == "" { + return fmt.Errorf("secret provider id is required; use a positional arg or --id") + } + payload["id"] = id + } else { + id, ok := payload["id"] + if !ok || id == nil { + return fmt.Errorf("secret provider id is required; use a positional arg or --id") + } + trimmedID := strings.TrimSpace(fmt.Sprint(id)) + if trimmedID == "" { + return fmt.Errorf("secret provider id is required; use a positional arg or --id") + } + payload["id"] = trimmedID } httpClient, err := opts.Client() diff --git a/pkg/cmd/secret/create/create_test.go b/pkg/cmd/secret/create/create_test.go index 7c5746d..0331164 100644 --- a/pkg/cmd/secret/create/create_test.go +++ b/pkg/cmd/secret/create/create_test.go @@ -127,6 +127,31 @@ func TestCreateSecret_FileRequiresID(t *testing.T) { registry.Verify(t) } +func TestCreateSecret_FileRejectsWhitespaceID(t *testing.T) { + ios, _, _, _ := iostreams.Test() + registry := &httpmock.Registry{} + + file := filepath.Join(t.TempDir(), "secret.json") + if err := os.WriteFile(file, []byte(`{"id":" ","uri":"http://vault","prefix":"kv","token":"tok"}`), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + err := actionRun(&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 + }, + GatewayGroup: "gg1", + File: file, + }) + if err == nil || err.Error() != "secret provider id is required; use a positional arg or --id" { + t.Fatalf("expected --id required error, got: %v", err) + } + + registry.Verify(t) +} + func TestCreateSecret_RequiresID(t *testing.T) { ios, _, _, _ := iostreams.Test() registry := &httpmock.Registry{}