返回 DeepSeek-Reasonix
credentials_keyring_helper.go
根目录 / internal / config / credentials_keyring_helper.go
1 package config
2
3 import (
4 "context"
5 "strings"
6 "time"
7 )
8
9 // legacyKeyringStatus classifies one keyring probe outcome for migration.
10 // Only absent may write a migration-done marker.
11 type legacyKeyringStatus string
12
13 const (
14 legacyKeyringFound legacyKeyringStatus = "found"
15 legacyKeyringAbsent legacyKeyringStatus = "absent"
16 legacyKeyringError legacyKeyringStatus = "error"
17 legacyKeyringTimeout legacyKeyringStatus = "timeout"
18 )
19
20 // legacyKeyringOutcome is the four-state result for one env key.
21 // Value is populated only for found probes inside this process and is scrubbed
22 // before the outcome is returned to migration callers after store-if-absent.
23 type legacyKeyringOutcome struct {
24 Status legacyKeyringStatus
25 Value string
26 }
27
28 // legacyKeyringGetBounded runs one uncancellable keyring lookup under ctx's
29 // budget. A buffered channel lets a late result be dropped without blocking
30 // the probe goroutine; the caller-side wait is bounded even though the OS
31 // call itself cannot be interrupted. On timeout the probe goroutine may stay
32 // blocked inside the OS call until the keyring answers (macOS may pop a GUI
33 // prompt) — that is inherent to an uncancellable API and only leaks a parked
34 // goroutine that exits on its own once the OS returns. timedOut reports that
35 // the budget expired before the lookup returned. A result that won the select
36 // race is returned as-is: err-vs-timeout classification stays in the
37 // per-platform probe, keeping the pre-existing semantics (a found value is
38 // not demoted to timeout).
39 func legacyKeyringGetBounded(ctx context.Context, get func() (string, error)) (value string, err error, timedOut bool) {
40 type result struct {
41 value string
42 err error
43 }
44 ch := make(chan result, 1)
45 go func() {
46 v, e := get()
47 ch <- result{value: v, err: e}
48 }()
49 select {
50 case <-ctx.Done():
51 return "", nil, true
52 case r := <-ch:
53 return r.value, r.err, false
54 }
55 }
56
57 // lookupLegacyKeyringBatch probes keys under a single shared deadline in-process.
58 // There is no external helper entrypoint: secrets never leave the process via
59 // stdout or a caller-controlled REASONIX_HOME dump path.
60 func lookupLegacyKeyringBatch(keys []string, budget time.Duration) map[string]legacyKeyringOutcome {
61 out := make(map[string]legacyKeyringOutcome, len(keys))
62 if len(keys) == 0 {
63 return out
64 }
65 if budget <= 0 {
66 budget = time.Second
67 }
68 ctx, cancel := context.WithTimeout(context.Background(), budget)
69 defer cancel()
70
71 for _, key := range keys {
72 if err := ctx.Err(); err != nil {
73 out[key] = legacyKeyringOutcome{Status: legacyKeyringTimeout}
74 continue
75 }
76 o := legacyKeyringProbeLookup(ctx, key)
77 switch o.Status {
78 case legacyKeyringFound:
79 if strings.TrimSpace(o.Value) == "" {
80 out[key] = legacyKeyringOutcome{Status: legacyKeyringAbsent}
81 continue
82 }
83 stored, err := storeCredentialIfAbsentAndNotCleared(key, o.Value)
84 o.Value = "" // scrub before returning
85 if err != nil {
86 out[key] = legacyKeyringOutcome{Status: legacyKeyringError}
87 continue
88 }
89 if !stored {
90 // Current store already has a value or tombstone; do not apply
91 // the legacy keyring secret. Report found so migration does not
92 // re-probe endlessly without writing an absent marker.
93 out[key] = legacyKeyringOutcome{Status: legacyKeyringFound}
94 continue
95 }
96 out[key] = legacyKeyringOutcome{Status: legacyKeyringFound}
97 case legacyKeyringAbsent, legacyKeyringError, legacyKeyringTimeout:
98 out[key] = legacyKeyringOutcome{Status: o.Status}
99 default:
100 out[key] = legacyKeyringOutcome{Status: legacyKeyringTimeout}
101 }
102 }
103 return out
104 }
105
105 lines GO