Skip to content

fix update commands to preserve full PUT payloads#20

Merged
guoqqqi merged 8 commits into
masterfrom
fix-partial-update-put-payloads
May 10, 2026
Merged

fix update commands to preserve full PUT payloads#20
guoqqqi merged 8 commits into
masterfrom
fix-partial-update-put-payloads

Conversation

@guoqqqi
Copy link
Copy Markdown
Contributor

@guoqqqi guoqqqi commented May 9, 2026

Summary

Fixes #18.

This PR changes flag-based update commands for resources whose API endpoints require full PUT payloads:

  • a7 credential update now reads the existing credential and preserves fields such as name and plugins unless the corresponding flags are provided.
  • a7 proto update now preserves existing content unless --content is provided.
  • a7 gateway-group update now preserves required fields such as name when only description, prefix, or labels are updated.

File-based update mode remains unchanged and continues to send the provided file payload directly.

Validation

  • go test ./pkg/cmd/credential/update ./pkg/cmd/proto/update ./pkg/cmd/gateway-group/update
  • go test ./...
  • Built the CLI and validated against a real API7 EE environment:
    • credential update --desc preserves existing plugins.
    • proto update --desc preserves existing content.
    • gateway-group update --description preserves existing name.

Follow-up

API capability/version boundary findings from the same live validation are tracked separately in #19.

Summary by CodeRabbit

  • Bug Fixes

    • Update commands now only modify fields explicitly provided via CLI flags, preserving unspecified data and allowing explicit clearing when a flag is supplied.
  • Tests

    • Added and enhanced unit and end-to-end tests to verify fetch-and-update flows, ensuring updates preserve existing fields and apply only the requested changes.

Review Change Stack

Copilot AI review requested due to automatic review settings May 9, 2026 00:39
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

Warning

Rate limit exceeded

@guoqqqi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 50 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0b0eef1c-166a-4401-8394-ee680942c3f6

📥 Commits

Reviewing files that changed from the base of the PR and between a73c474 and a3779d8.

📒 Files selected for processing (5)
  • pkg/cmd/credential/update/update.go
  • pkg/cmd/credential/update/update_test.go
  • pkg/cmd/gateway-group/update/update_test.go
  • test/e2e/credential_test.go
  • test/e2e/proto_test.go
📝 Walkthrough

Walkthrough

Four update commands—credential, gateway-group, and proto—are refactored to track which CLI flags were explicitly provided, fetch current resource state, conditionally parse and merge only provided changes, and send full PUT payloads that preserve unmodified fields required by the API schema.

Changes

Partial Update Flag Mode Fix

Layer / File(s) Summary
Flag State Tracking
pkg/cmd/credential/update/update.go, pkg/cmd/gateway-group/update/update.go, pkg/cmd/proto/update/update.go
Options structs gain boolean fields (DescSet, PluginsSet/LabelsSet, NameSet, DescriptionSet, PrefixSet, ContentSet) to record whether each field-related CLI flag was explicitly provided.
Flag Detection Initialization
pkg/cmd/credential/update/update.go, pkg/cmd/gateway-group/update/update.go, pkg/cmd/proto/update/update.go
NewCmd sets boolean state indicators using Cobra's Flags().Changed(...) for each tracked field, enabling conditional logic downstream.
Core Update Flow
pkg/cmd/credential/update/update.go, pkg/cmd/gateway-group/update/update.go, pkg/cmd/proto/update/update.go
Each command now parses labels/plugins only when their flags were set, fetches current resource state via GET, merges only provided changes into the existing payload, and sends the full merged payload via PUT.
Tests / Validation
pkg/cmd/credential/update/update_test.go, pkg/cmd/gateway-group/update/update_test.go, pkg/cmd/proto/update/update_test.go, test/e2e/*
Unit tests register GET mocks for current state and PUT responders that read/unmarshal the request body to assert unchanged required fields are preserved while provided fields are updated. New e2e tests verify update-then-get semantics for credential, proto, and gateway-group.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as a7 CLI
  participant API as Control Plane API
  participant DB as Storage
  CLI->>API: GET /resource/{id}
  API->>DB: read resource
  DB-->>API: current resource JSON
  API-->>CLI: current resource JSON
  CLI->>API: PUT /resource/{id} with merged payload
  API->>DB: write updated resource
  DB-->>API: stored resource
  API-->>CLI: updated resource JSON
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • api7/a7#16: Similar changes to detect explicitly-provided flags and perform read-modify-write updates across CLI update commands.
  • api7/a7#6: Related e2e/test adjustments around JSON output and stronger update verification.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security Check ❌ Error CRITICAL: Plugin API keys leaked in test error logs. Lines 134, 146, 164 in test/e2e/credential_test.go log raw stdout containing plugin secrets via require.NoError format string. Replace "stdout=%s stderr=%s" with non-leaking message like "credential operation failed" on lines 134, 146, 164 to prevent API keys in CI logs.
E2e Test Quality Review ⚠️ Warning E2E test logs sensitive plugin payload data via stdout/stderr in require.NoError calls (lines 134, 146, 164), violating security policy on logging plugin configurations without redaction. Replace require.NoError(..., "stdout=%s stderr=%s", stdout, stderr) with generic messages like "credential create failed" on lines 134, 146, 164 per review guidance.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: modifying update commands to preserve full PUT payloads when using flag-based updates instead of sending partial payloads.
Linked Issues check ✅ Passed All coding requirements from issue #18 are fully met: credential, proto, and gateway-group update commands now read existing resources, merge only CLI-provided fields, and send complete PUT payloads; file-based updates remain unchanged; unit and e2e tests validate preserved fields.
Out of Scope Changes check ✅ Passed All changes directly address issue #18 objectives: modified update commands for credential, proto, and gateway-group to preserve existing fields; added unit and e2e tests; no unrelated changes detected.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-partial-update-put-payloads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes flag-based update commands that previously sent partial PUT payloads to API endpoints that require full resource replacement, by first reading the current resource and then merging only the explicitly-provided flags before issuing the PUT.

Changes:

  • proto update, credential update, and gateway-group update (flag mode) now GET the current resource and preserve unspecified fields when building the PUT payload.
  • Introduces *Set booleans (based on cobra.Flags().Changed(...)) to distinguish “flag not provided” vs “provided empty value”.
  • Adds/updates unit tests to assert preservation of required/existing fields in the generated PUT payload.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pkg/cmd/proto/update/update.go Fetches current proto and merges only changed flag fields before PUT to preserve required/existing fields.
pkg/cmd/proto/update/update_test.go Updates test to validate PUT payload preserves content unless --content is explicitly set.
pkg/cmd/gateway-group/update/update.go Fetches current gateway group and merges only changed flag fields before PUT to preserve required name.
pkg/cmd/gateway-group/update/update_test.go New test ensuring required fields (e.g., name) are preserved when updating description/labels.
pkg/cmd/credential/update/update.go Fetches current credential and merges only changed flag fields before PUT to preserve name/plugins.
pkg/cmd/credential/update/update_test.go Updates tests to validate PUT payload preserves existing name/plugins and adds required GET mock for error-path test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/cmd/gateway-group/update/update.go Outdated
Comment on lines +111 to +115
var request api.GatewayGroup
if err := json.Unmarshal(currentBody, &request); err != nil {
return fmt.Errorf("failed to decode current gateway group: %w", err)
}
request.ID = opts.ID
Copilot AI review requested due to automatic review settings May 10, 2026 13:07
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 (2)
test/e2e/credential_test.go (2)

138-140: ⚡ Quick win

Register credential cleanup immediately after actualID is known.

If an assertion fails before Line 162, the explicit delete won’t run and can leave residue in shared e2e environments.

Suggested patch
 	actualID, ok := created["id"].(string)
 	require.True(t, ok && actualID != "", "credential create response should contain generated id: %v", created)
+	t.Cleanup(func() {
+		_, cleanupStderr, cleanupErr := runA7WithEnv(env, "credential", "delete", actualID,
+			"--consumer", username, "--force", "-g", gatewayGroup)
+		if cleanupErr != nil {
+			t.Logf("credential cleanup failed for %s: %s", actualID, cleanupStderr)
+		}
+	})

...

-	stdout, stderr, err = runA7WithEnv(env, "credential", "delete", actualID,
-		"--consumer", username, "--force", "-g", gatewayGroup)
-	require.NoError(t, err, "stdout=%s stderr=%s", stdout, stderr)

Also applies to: 162-164

🤖 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 `@test/e2e/credential_test.go` around lines 138 - 140, After extracting
actualID from created (actualID, ok := created["id"].(string) and the
require.True check), immediately register the cleanup that deletes the
credential (use t.Cleanup or an immediate defer) so the delete runs even if
subsequent assertions fail; move the existing explicit delete logic currently
around the later block (the delete at lines ~162-164) into that immediate
cleanup closure and reference actualID there to ensure removal in shared e2e
environments.

152-153: ⚡ Quick win

Strengthen preservation checks to validate nested plugin content.

Current assertions only verify that "key-auth" exists. They won’t catch regressions where nested fields are lost/changed while the key remains present.

Suggested patch
 	plugins := requireJSONObject(t, credential["plugins"], "credential.plugins")
 	assert.Contains(t, plugins, "key-auth")
+	keyAuth := requireJSONObject(t, plugins["key-auth"], "credential.plugins.key-auth")
+	assert.Equal(t, "e2e-update-key-12345", keyAuth["key"])

...

 	plugins = requireJSONObject(t, credential["plugins"], "credential.plugins")
 	assert.Contains(t, plugins, "key-auth")
+	keyAuth = requireJSONObject(t, plugins["key-auth"], "credential.plugins.key-auth")
+	assert.Equal(t, "e2e-update-key-12345", keyAuth["key"])

Also applies to: 159-160

🤖 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 `@test/e2e/credential_test.go` around lines 152 - 153, The test currently only
checks presence of "key-auth" in plugins (using plugins := requireJSONObject(t,
credential["plugins"], "credential.plugins") and assert.Contains), which misses
regressions where the nested plugin object changes; update the test to call
requireJSONObject(t, plugins["key-auth"], "credential.plugins.key-auth") to
assert the plugin value is an object and then assert its expected nested fields
(e.g., required config/keys) exist and have expected types/values using the same
require* helpers used elsewhere in the file; apply the same change for the other
occurrence around the second assertion block.
🤖 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 `@test/e2e/credential_test.go`:
- Line 134: The test currently passes raw stdout and stderr into require.NoError
(the call using require.NoError(t, err, "stdout=%s stderr=%s", stdout, stderr))
which can leak plugin payloads; update the checks to avoid printing raw command
output by removing stdout/stderr from the failure message (use
require.NoError(t, err) or a generic message) or redact sensitive fields before
logging (e.g., replace tokens/keys in the stdout/stderr strings), and apply this
change for every occurrence that uses stdout and stderr variables in
require.NoError in the credential_test.go tests.

---

Nitpick comments:
In `@test/e2e/credential_test.go`:
- Around line 138-140: After extracting actualID from created (actualID, ok :=
created["id"].(string) and the require.True check), immediately register the
cleanup that deletes the credential (use t.Cleanup or an immediate defer) so the
delete runs even if subsequent assertions fail; move the existing explicit
delete logic currently around the later block (the delete at lines ~162-164)
into that immediate cleanup closure and reference actualID there to ensure
removal in shared e2e environments.
- Around line 152-153: The test currently only checks presence of "key-auth" in
plugins (using plugins := requireJSONObject(t, credential["plugins"],
"credential.plugins") and assert.Contains), which misses regressions where the
nested plugin object changes; update the test to call requireJSONObject(t,
plugins["key-auth"], "credential.plugins.key-auth") to assert the plugin value
is an object and then assert its expected nested fields (e.g., required
config/keys) exist and have expected types/values using the same require*
helpers used elsewhere in the file; apply the same change for the other
occurrence around the second assertion block.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cbe9759d-46e1-4181-9754-358090742b2e

📥 Commits

Reviewing files that changed from the base of the PR and between 0d77de2 and a73c474.

📒 Files selected for processing (3)
  • test/e2e/credential_test.go
  • test/e2e/gateway_group_test.go
  • test/e2e/proto_test.go

Comment thread test/e2e/credential_test.go Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment on lines +15 to +19
type mockConfig struct{}

func (m *mockConfig) BaseURL() string { return "" }
func (m *mockConfig) Token() string { return "" }
func (m *mockConfig) GatewayGroup() string { return "" }
Copilot AI review requested due to automatic review settings May 10, 2026 13:17
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comment on lines 107 to 108
if err := json.Unmarshal([]byte(opts.PluginsJSON), &pl); err != nil {
return fmt.Errorf("invalid --plugins-json: %w", err)
Comment on lines +96 to 101
stdout, _, err := runA7WithEnv(env, "credential", "create", credID,
"--consumer", username,
"--plugins-json", `{"key-auth":{"key":"e2e-positional-key-12345"}}`,
"-g", gatewayGroup)
require.NoError(t, err, "stdout=%s stderr=%s", stdout, stderr)
require.NoError(t, err, "credential create failed")

Comment thread test/e2e/credential_test.go Outdated
Comment on lines +108 to +111
_, _, cleanupErr := runA7WithEnv(env, "credential", "delete", actualID,
"--consumer", username, "--force", "-g", gatewayGroup)
if cleanupErr != nil {
t.Logf("credential cleanup failed for %s", actualID)
Copilot AI review requested due to automatic review settings May 10, 2026 13:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment on lines +132 to 145
var bodyReq api.Credential
if err := json.Unmarshal(currentBody, &bodyReq); err != nil {
return fmt.Errorf("failed to decode current credential: %w", err)
}
bodyReq.ID = opts.ID
if opts.DescSet {
bodyReq.Desc = opts.Desc
}
if opts.PluginsSet {
bodyReq.Plugins = pl
}
if len(labels) > 0 {
if opts.LabelsSet {
bodyReq.Labels = labels
}
Comment on lines +113 to 126
var bodyReq api.Proto
if err := json.Unmarshal(currentBody, &bodyReq); err != nil {
return fmt.Errorf("failed to decode current proto: %w", err)
}
bodyReq.ID = opts.ID
if opts.DescSet {
bodyReq.Desc = opts.Desc
}
if len(labels) > 0 {
if opts.ContentSet {
bodyReq.Content = opts.Content
}
if opts.LabelsSet {
bodyReq.Labels = labels
}
@guoqqqi guoqqqi merged commit b3d8ede into master May 10, 2026
6 checks passed
@guoqqqi guoqqqi deleted the fix-partial-update-put-payloads branch May 10, 2026 14:54
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.

Fix partial update flag mode for resources that require full PUT payloads

2 participants