refactor(pass): pull store from cobra context#556
Conversation
Subcommands previously took a fully-initialized store.Store as a constructor argument, which forced the caller to open the OS keychain before cobra had even dispatched the subcommand. That eager init is the root cause of the headless-hang reported in #405: on systems with no D-Bus session bus the keychain dial blocks indefinitely, taking down commands that never needed the keychain in the first place. Drop the store argument from GetCommand/SetCommand/ListCommand/RmCommand and have each RunE pull the live store from the cobra command context via the new commands.StoreFrom helper. ErrNoStoreInContext is returned when no store has been attached, so a subcommand that runs without the root having opened the keychain fails with a clear error rather than dereferencing nil. Root and the OTel wrapper live in the consuming binary (secrets-engine- private) — once it switches to a PersistentPreRunE that calls commands.WithStore lazily, the headless `pass version` / `--help` paths will no longer touch the keychain. Tests now exercise each subcommand directly, attaching a mock store via WithStore and running the command in isolation. The telemetry test was tied to the now-removed withOTEL wrapper and has been dropped along with the otel/term direct dependencies; vendor/ is updated accordingly.
|
Replaces #554 |
There was a problem hiding this comment.
Assessment: 🟡 NEEDS ATTENTION
This PR correctly implements the lazy-store pattern — pulling store.Store from cobra's command context rather than passing it as a constructor argument. The refactor addresses the headless-hang root cause (#405) and all four subcommands (get, set, list, rm) properly propagate ErrNoStoreInContext. One minor defensive-coding note is included below.
| } | ||
|
|
||
| func StoreFrom(ctx context.Context) (store.Store, error) { | ||
| s, ok := ctx.Value(storeCtxKey{}).(store.Store) |
There was a problem hiding this comment.
Potential: StoreFrom cannot detect typed-nil stores
The type assertion ctx.Value(storeCtxKey{}).(store.Store) only sets ok = false when the value is absent or its dynamic type does not implement store.Store. If a caller passes a typed-nil concrete pointer — e.g. var s *keychain.Store; commands.WithStore(ctx, s) — the assertion succeeds (ok == true) and StoreFrom returns a non-nil interface wrapping a nil pointer, with no error. Any subsequent method call on that store will panic.
This scenario requires the consuming binary (being wired up separately in secrets-engine-private) to call WithStore with a typed-nil value, which is an unusual mistake. Still worth guarding against, e.g.:
if s == nil {
return nil, ErrNoStoreInContext
}Note: verification of this finding was inconclusive — surfacing for author evaluation.
Subcommands previously took a fully-initialized store.Store as a constructor argument, which forced the caller to open the OS keychain before cobra had even dispatched the subcommand. That eager init is the root cause of the headless-hang reported in #405: on systems with no D-Bus session bus the keychain dial blocks indefinitely, taking down commands that never needed the keychain in the first place.
Drop the store argument from GetCommand/SetCommand/ListCommand/RmCommand and have each RunE pull the live store from the cobra command context via the new commands.StoreFrom helper. ErrNoStoreInContext is returned when no store has been attached, so a subcommand that runs without the root having opened the keychain fails with a clear error rather than dereferencing nil.
Root and the OTel wrapper live in the consuming binary (secrets-engine- private) — once it switches to a PersistentPreRunE that calls commands.WithStore lazily, the headless
pass version/--helppaths will no longer touch the keychain.Tests now exercise each subcommand directly, attaching a mock store via WithStore and running the command in isolation. The telemetry test was tied to the now-removed withOTEL wrapper and has been dropped along with the otel/term direct dependencies; vendor/ is updated accordingly.