-
Notifications
You must be signed in to change notification settings - Fork 99
Add Secret Management UI Implementation #3129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
db24fe0
cb98035
f19e812
33443a4
c274b99
7ab8714
1066eb9
02a3929
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -399,6 +399,46 @@ type SubscriptionPlanDeletedEvent struct { | |
| CorrelationID string `json:"correlationId"` | ||
| } | ||
|
|
||
| // SecretUpdatedEventPayload represents the payload of a secret.updated event, fired | ||
| // when a secret is rotated. It never carries the plaintext value — Hash is the | ||
| // HMAC-SHA256 change-detection digest, safe to transmit since it cannot be reversed | ||
| // into the plaintext. The receiving handler fetches the fresh plaintext separately | ||
| // over the authenticated internal secret-value endpoint. | ||
| type SecretUpdatedEventPayload struct { | ||
| Handle string `json:"handle"` | ||
| DisplayName string `json:"name"` | ||
| Hash string `json:"hash"` | ||
| // Revision orders events for the same handle so a redelivered or reordered | ||
| // event cannot undo a change already applied locally. See Client.secretRevisionCache. | ||
| Revision int64 `json:"revision"` | ||
| } | ||
|
|
||
| // SecretUpdatedEvent represents the complete secret.updated event. | ||
| type SecretUpdatedEvent struct { | ||
| Type string `json:"type"` | ||
| Payload SecretUpdatedEventPayload `json:"payload"` | ||
| Timestamp string `json:"timestamp"` | ||
| CorrelationID string `json:"correlationId"` | ||
| } | ||
|
|
||
| // SecretDeletedEventPayload represents the payload of a secret.deleted event, | ||
| // fired when a secret is permanently deleted. | ||
| type SecretDeletedEventPayload struct { | ||
| Handle string `json:"handle"` | ||
| // Revision — see SecretUpdatedEventPayload.Revision. Compared against the same | ||
| // cache so a late deletion cannot evict a secret that was recreated under the | ||
| // same handle after it. | ||
| Revision int64 `json:"revision"` | ||
| } | ||
|
Comment on lines
+424
to
+432
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline platform-api/internal/model/secret.go --items all
rg -n -C 5 'Revision|isStaleSecretEvent|secretRevisionCache|SecretDeletedEventPayload' \
platform-api/internal/model/secret.go \
platform-api/internal/service/secret_service.go \
gateway/gateway-controller/pkg/controlplane/client.go \
gateway/gateway-controller/pkg/controlplane/events.goRepository: wso2/api-platform Length of output: 19590 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '180,285p' platform-api/internal/service/secret_service.go
sed -n '384,100p' platform-api/internal/store/postgres/secret_repository.go
sed -n '70,220p' platform-api/internal/store/postgres/secret_repository.go
printf '\n--- create/update/delete helpers references ---\n'
rg -n -C 8 'CreatedAt|UpdatedAt|UnixNano|Find[ ]+\\(|Get[ ]+\\(|Create\\(|Update\\(' platform-api/internal/store platform-api/internal/service | sed -n '1,260p'Repository: wso2/api-platform Length of output: 3706 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate secret repositories ---'
git ls-files | rg 'secret.*repository|secret.*store|postgres.*secret|sql/.*secret|*.sql' | rg -i 'secret' | sed -n '1,120p'
printf '%s\n' '--- Update/GetByHandle/Create definitions'
rg -n -C 8 'func .*GetByHandle|func .*Create|func .*Update|type Secret\s+\{|CreatedAt\s+|UpdatedAt\s+' platform-api/internal platform-api/api | sed -n '1,360p'
printf '%s\n' '--- Create flow around SecretCreate'
rg -n -C 12 'func \(s \*SecretService\) Create|NewSecret|Created_at|CreatedAt|UpdatedAt' platform-api/internal/service/secret_service.go platform-api/internal/model platform-api/api | sed -n '1,280p'Repository: wso2/api-platform Length of output: 411 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate secret repository files ---'
git ls-files | rg -i 'secret.*(repository|store|reposit)|.*postgres|.*sql' | rg -i 'secret' | sed -n '1,160p'
printf '%s\n' '--- Create/GetByHandle/Update definitions ---'
rg -n -C 12 'func \(s \*SecretService\) Create|func .*GetByHandle|func .*Update|func .*FindRefsAndDelete|NewSecret|CreatedAt\s+|UpdatedAt\s+' platform-api/internal/service/secret_service.go platform-api/internal/model platform-api/internal/store platform-api/internal/dto | sed -n '1,380p'
printf '%s\n' '--- exact Update implementation ---'
sed -n '190,270p' platform-api/internal/service/secret_service.goRepository: wso2/api-platform Length of output: 36046 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '88,185p' platform-api/internal/service/secret_service.go
rg -n -C 12 'type Secret struct|CreatedAt|UpdatedAt|db:"created_at"|db:"updated_at"|Update\(|FindRefsAndDelete|GetByHandle' platform-api/internal platform-api/api --glob '*.go' --glob '*.sql' | sed -n '1,320p'Repository: wso2/api-platform Length of output: 31888 🌐 Web query:
💡 Result: In GORM, you do not need to manually create a callback to set Citations:
Make recreated secret handles use strictly newer revisions.
🤖 Prompt for AI Agents |
||
|
|
||
| // SecretDeletedEvent represents the complete secret.deleted event. | ||
| type SecretDeletedEvent struct { | ||
| Type string `json:"type"` | ||
| Payload SecretDeletedEventPayload `json:"payload"` | ||
| Timestamp string `json:"timestamp"` | ||
| CorrelationID string `json:"correlationId"` | ||
| } | ||
|
|
||
| // ApplicationKeyMappingPayload represents a single application to API key mapping entry. | ||
| type ApplicationKeyMappingPayload struct { | ||
| ApiKeyUuid string `json:"apiKeyUuid"` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,8 +20,10 @@ package controlplane | |
|
|
||
| import ( | ||
| "log/slog" | ||
| "time" | ||
|
|
||
| "github.com/wso2/api-platform/gateway/gateway-controller/pkg/constants" | ||
| "github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils" | ||
| ) | ||
|
|
||
| // syncSecrets pulls secrets from the Platform API and upserts them into local | ||
|
|
@@ -73,12 +75,14 @@ func (c *Client) syncSecretsBulk() { | |
| } | ||
|
|
||
| synced, skipped, failed := 0, 0, 0 | ||
| activeHandles := make(map[string]struct{}, len(metas)) | ||
|
|
||
| for _, meta := range metas { | ||
| if meta.Status != "ACTIVE" { | ||
| skipped++ | ||
| continue | ||
| } | ||
| activeHandles[meta.Handle] = struct{}{} | ||
|
|
||
| if meta.Value == nil { | ||
| c.logger.Warn("Bulk fetch returned no value for secret — skipping", | ||
|
|
@@ -101,10 +105,13 @@ func (c *Client) syncSecretsBulk() { | |
| synced++ | ||
| } | ||
|
|
||
| evicted := c.evictSecretsNotIn(activeHandles) | ||
|
|
||
| c.logger.Info("Bulk Platform API secret sync complete", | ||
| slog.Int("synced", synced), | ||
| slog.Int("skipped", skipped), | ||
| slog.Int("failed", failed), | ||
| slog.Int("evicted", evicted), | ||
| ) | ||
| } | ||
|
|
||
|
|
@@ -120,12 +127,14 @@ func (c *Client) syncSecretsIncremental() { | |
| } | ||
|
|
||
| synced, skipped, failed := 0, 0, 0 | ||
| activeHandles := make(map[string]struct{}, len(metas)) | ||
|
|
||
| for _, meta := range metas { | ||
| if meta.Status != "ACTIVE" { | ||
| skipped++ | ||
| continue | ||
| } | ||
| activeHandles[meta.Handle] = struct{}{} | ||
|
|
||
| // Skip if hash unchanged since last sync. | ||
| if cached, ok := c.secretHashCache.Load(meta.Handle); ok && cached.(string) == meta.Hash { | ||
|
|
@@ -156,13 +165,60 @@ func (c *Client) syncSecretsIncremental() { | |
| synced++ | ||
| } | ||
|
|
||
| evicted := c.evictSecretsNotIn(activeHandles) | ||
|
|
||
| c.logger.Info("Incremental Platform API secret sync complete", | ||
| slog.Int("synced", synced), | ||
| slog.Int("skipped", skipped), | ||
| slog.Int("failed", failed), | ||
| slog.Int("evicted", evicted), | ||
| ) | ||
| } | ||
|
|
||
| // evictSecretsNotIn is the poll-based recovery path for the same eviction that | ||
| // handleSecretDeletedEvent applies live: a gateway that is disconnected at the | ||
| // moment a secret is deleted only receives that secret.deleted WebSocket event if | ||
| // it's connected when the event is broadcast. This diffs the | ||
| // latest Platform API response against secretHashCache so that, on the next | ||
| // reconnect/poll, any cached handle that is no longer ACTIVE (permanently deleted, | ||
| // or flipped to a non-ACTIVE status) gets evicted from local storage even though | ||
| // the live event was missed. | ||
| // | ||
| // activeHandles is the set of handles the just-completed poll returned with | ||
| // status ACTIVE; every other handle currently in secretHashCache is stale. | ||
| func (c *Client) evictSecretsNotIn(activeHandles map[string]struct{}) int { | ||
| var stale []string | ||
| c.secretHashCache.Range(func(key, _ any) bool { | ||
| handle, ok := key.(string) | ||
| if !ok { | ||
| return true | ||
| } | ||
| if _, ok := activeHandles[handle]; !ok { | ||
| stale = append(stale, handle) | ||
| } | ||
| return true | ||
| }) | ||
|
|
||
| for _, handle := range stale { | ||
| correlationID := utils.GenerateDeterministicUUIDv7(handle, time.Now()) | ||
| if err := c.secretSyncer.Delete(handle, correlationID); err != nil { | ||
| c.logger.Warn("Failed to evict stale secret from local store", | ||
| slog.String("secret_handle", handle), | ||
| slog.String("correlation_id", correlationID), | ||
| slog.Any("error", err), | ||
| ) | ||
| continue | ||
| } | ||
|
Comment on lines
+202
to
+211
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect GenerateDeterministicUUIDv7 and its existing call sites.
set -euo pipefail
ast-grep run --pattern 'func GenerateDeterministicUUIDv7($$$) { $$$ }' --lang go gateway/gateway-controller/pkg/utils
rg -nP --type=go -C 3 '\bGenerateDeterministicUUIDv7\s*\('Repository: wso2/api-platform Length of output: 155 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== locate files =="
git ls-files | rg 'sync_secrets\.go|utils\.go|uuid|correlation|GenerateDeterministicUUIDv7' || true
echo "== find function by text =="
rg -n --type=go -C 4 'GenerateDeterministicUUIDv7|DeterministicUUIDv7|deterministic.*UUID|UUIDv7|UUID.*v7' . || true
echo "== target file excerpt if present =="
if [ -f gateway/gateway-controller/pkg/controlplane/sync_secrets.go ]; then
nl -ba gateway/gateway-controller/pkg/controlplane/sync_secrets.go | sed -n '170,225p'
fiRepository: wso2/api-platform Length of output: 31695 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== gateway utils GenerateDeterministicUUIDv7 =="
sed -n '80,115p' gateway/gateway-controller/pkg/utils/commonutils.go
echo "== platform-api GenerateDeterministicUUIDv7 =="
sed -n '526,550p' platform-api/internal/utils/common.go
echo "== relevant deterministic UUID test cases =="
sed -n '137,174p' gateway/gateway-controller/pkg/utils/api_deployment_test.go
sed -n '230,262p' gateway/gateway-controller/pkg/controlplane/sync_test.go
echo "== retry/poll context for secret eviction (if present) =="
rg -n --type=go -C 4 'stale|secretSyncer\.Delete|GenerateDeterministicUUIDv7\(handle' gateway/gateway-controller/pkg/controlplane/sync_secrets.go gateway/gateway-controller/pkg/controlplane || trueRepository: wso2/api-platform Length of output: 50373 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== sync polling/eviction call context =="
rg -n --type=go -C 4 'evictSecretsNotIn|Start.*poll|poll|Ticker|Ticker\(|time\.Ticker|ticker\.' gateway/gateway-controller/pkg/controlplane || true
echo "== deterministic UUID millisecond edge behavior probe =="
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp()*1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return "{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}".format(
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for ns in [0, 500_000_000, 999_000_000, 999_999_999]:
t = base + datetime.timedelta(nanoseconds=ns)
print(base.isoformat(), "+", ns, "ns ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PYRepository: wso2/api-platform Length of output: 18094 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== deterministic UUID millisecond edge behavior probe =="
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp()*1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return "{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}".format(
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PY
echo "== retry/test around delete error keeps same correlation seed if same time still in same millisecond =="
sed -n '920,940p' gateway/gateway-controller/pkg/controlplane/sync_secrets_test.goRepository: wso2/api-platform Length of output: 393 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== deterministic UUID millisecond edge behavior probe =="
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp() * 1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return "{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}".format(
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PY
echo "== retry behavior in sync_secrets_test =="
sed -n '920,940p' gateway/gateway-controller/pkg/controlplane/sync_secrets_test.goRepository: wso2/api-platform Length of output: 393 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp() * 1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return ("{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}").format(
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7(" handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PYRepository: wso2/api-platform Length of output: 336 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp() * 1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
parts = (
struct.unpack(">I", bytes(b[:4]))[0],
struct.unpack(">H", bytes(b[4:6]))[0],
b[6] << 8 | b[7],
b[8], b[9],
struct.unpack(">H", bytes(b[10:12]))[0],
struct.unpack(">H", bytes(b[12:14]))[0],
struct.unpack(">H", bytes(b[14:16]))[0],
)
return "{:08x}-{:04x}-{:04x}-{:02}{:02}-{:02}{:02}{:02}{:02}{:02}{:02}{:02}{:02}".format(*parts)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PY
echo "== retry behavior in sync_secrets_test =="
sed -n '920,940p' gateway/gateway-controller/pkg/controlplane/sync_secrets_test.goRepository: wso2/api-platform Length of output: 336 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import hashlib, struct, datetime
def uuid7(handle, t):
ms = int(t.timestamp() * 1000)
b = bytearray(16)
b[0:2] = struct.pack(">H", (ms >> 32) & 0xFFFF)
b[2:6] = struct.pack(">I", ms & 0xFFFFFFFF)
b[6:] = hashlib.sha256(handle.encode()).digest()[:10]
b[6] |= 0x70
b[8] |= 0x80
return uuid7_bytes(bytes(b))
def uuid7_bytes(b):
parts = ["{:08x}".format(struct.unpack(">I", b[:4])[0]),
"{:04x}".format(struct.unpack(">H", b[4:6])[0]),
"{:04x}".format(b[6] << 8 | b[7]),
"{:02x}{:02x}".format(b[8], b[9]),
"{:02x}{:02x}".format(struct.unpack(">H", b[10:12])[0]),
"{:02x}{:02x}".format(struct.unpack(">H", b[12:14])[0]),
"{:02x}{:02x}".format(struct.unpack(">H", b[14:16])[0])]
return "-".join(parts)
base = datetime.datetime(2026, 1, 2, 3, 4, 5, 123456, tzinfo=datetime.timezone.utc)
for add_us in [0, 500000, 999000, 999999]:
t = base + datetime.timedelta(microseconds=add_us)
print(base.isoformat(), "+", add_us, "us ->", uuid7("handle", t))
print("equal within same millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=999)))
print("different millisecond:", uuid7("handle", base) == uuid7("handle", base + datetime.timedelta(milliseconds=1)))
PY
echo "== retry behavior in sync_secrets_test =="
sed -n '920,940p' gateway/gateway-controller/pkg/controlplane/sync_secrets_test.goRepository: wso2/api-platform Length of output: 378 Use per-attempt entropy for stale-secret eviction correlation IDs.
🤖 Prompt for AI Agents |
||
| c.secretHashCache.Delete(handle) | ||
| c.logger.Info("Evicted stale secret from local store during poll sync", | ||
| slog.String("secret_handle", handle), | ||
| slog.String("correlation_id", correlationID), | ||
| ) | ||
| } | ||
|
|
||
| return len(stale) | ||
| } | ||
|
Comment on lines
+189
to
+220
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift Evict only handles that were cached before the poll started.
Snapshot the cache keys before the fetch and restrict eviction to that snapshot. 🛠️ Proposed fix- metas, err := c.apiUtilsService.FetchPlatformSecrets(nil, false)
+ preFetchHandles := c.cachedSecretHandles()
+ metas, err := c.apiUtilsService.FetchPlatformSecrets(nil, false)
if err != nil {
c.logger.Error("Failed to fetch platform secrets metadata", slog.Any("error", err))
return
}- evicted := c.evictSecretsNotIn(activeHandles)
+ evicted := c.evictSecretsNotIn(activeHandles, preFetchHandles)-func (c *Client) evictSecretsNotIn(activeHandles map[string]struct{}) int {
+// cachedSecretHandles snapshots the handles currently in secretHashCache.
+func (c *Client) cachedSecretHandles() map[string]struct{} {
+ snapshot := make(map[string]struct{})
+ c.secretHashCache.Range(func(key, _ any) bool {
+ if handle, ok := key.(string); ok {
+ snapshot[handle] = struct{}{}
+ }
+ return true
+ })
+ return snapshot
+}
+
+// candidates limits eviction to handles cached before the poll response was
+// fetched, so a handle added concurrently is never mistaken for a stale one.
+func (c *Client) evictSecretsNotIn(activeHandles, candidates map[string]struct{}) int {
var stale []string
c.secretHashCache.Range(func(key, _ any) bool {
handle, ok := key.(string)
if !ok {
return true
}
+ if _, eligible := candidates[handle]; !eligible {
+ return true
+ }
if _, ok := activeHandles[handle]; !ok {
stale = append(stale, handle)
}
return true
})🤖 Prompt for AI Agents |
||
|
|
||
| // syncSecretRefsFromYAML extracts {{ secret "handle" }} placeholders from the | ||
| // supplied YAML, then fetches and upserts any handle that is not already in the | ||
| // local hash cache. This is called from deployment event handlers so that secrets | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.