-
Notifications
You must be signed in to change notification settings - Fork 2.4k
refactor: abstract keyring logic to better enable DI #3262
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
Conversation
ea8f3d6 to
4234f40
Compare
| # Run comprehensive linting checks (includes tests, examples, benchmarks) | ||
| lint-all: | ||
| @echo "Running comprehensive linting checks..." | ||
| cargo clippy --workspace --all-targets --all-features -- -D warnings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason that lint doesn't do all-targets too? I feel like having distinct lint commands means we won't use one of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah - currently lint-all fails; we should fix them up in another PR. I left it there for future use. Currently just lint is equivalent of what's run in CI
| impl From<KeyringError> for StorageError { | ||
| fn from(err: KeyringError) -> Self { | ||
| match err { | ||
| KeyringError::NotFound { .. } => StorageError::NotFound, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this losing information that might be useful for debugging, can it be retained when converting to StorageError?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly, although the only thing NotFound contains is NotFound { service: String, username: String }.
| // Read should succeed with mock keyring | ||
| let read_result = manager.read_credentials::<TestCredentials>(); | ||
| assert!(read_result.is_ok(), "Read should succeed with fallback"); | ||
| assert!(read_result.is_ok(), "Read should succeed with mock keyring"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this test is still about using the 'fallback_to_disk'=true rather than about using a mock keyring.
crates/goose/src/config/base.rs
Outdated
| .expect("goose requires a home dir") | ||
| .config_dir(); | ||
|
|
||
| let keyring: Arc<dyn KeyringBackend> = if env::var("GOOSE_DISABLE_KEYRING").is_ok() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it it worth explicitly checking the value inside the GOOSE_DISABLE_KEYRING rather than just checking the var is present? Most vars can be true or false depending on the value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, worth making it more explicit is a good idea; fixed it.
crates/goose/src/config/base.rs
Outdated
| .config_dir(); | ||
|
|
||
| let keyring: Arc<dyn KeyringBackend> = if env::var("GOOSE_DISABLE_KEYRING").is_ok() { | ||
| Arc::new(FileKeyringBackend::new(config_dir.join("secrets.yaml"))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bonus points if "secrets.yaml" is a constant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep good call; added it now
| } | ||
|
|
||
| #[test] | ||
| fn test_multiple_secrets_with_mock() -> Result<(), ConfigError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove the test_secret_management_with_mock test and make this cover it all?
| secrets_path: PathBuf, | ||
| } | ||
|
|
||
| impl FileKeyringBackend { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this backwards compatible with the existing 'secrets.yaml' that was previously used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch; I missed the old secrets.yaml flow here. Adding it now.
| let backend = FileKeyringBackend::new(temp_file.path().to_path_buf()); | ||
|
|
||
| // Test setting a password | ||
| backend.set_password("test_service", "test_user", "test_password")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These comments don't add anything compared to just the code on the next line, please remove and check for similar issues throughout.
| // If it succeeds with fake keys, the logic worked | ||
| } | ||
| Err(error) => { | ||
| // If it fails, it should be due to missing API keys, confirming we tried to create providers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe some of these original comments are useful context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment I thought was more relevant: // Should not fail due to missing secrets, but may fail due to invalid fake keys and // Since we provided fake API keys, the creation should proceed to the provider instantiation
// It may still fail at the provider level, but we should not get keychain-related errors
| use chrono::Utc; | ||
| use mcp_core::{content::TextContent, Role}; | ||
| use std::env; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At a glance it seems like all the changes in this file are unrelated to the PR focus which is using a new keyring abstraction. Maybe it crept in from some other work and we should keep it separate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep agree - it was an opportunistic refactor while I was removing the need for touching the user's keyring when running tests. Originally, I was setting the ENV in the tests to disable the keyring access. But I could not resist the temptation to use Drop trait :)
jsibbison-square
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some comments but overall I think this looks ok. Double check the file implementation is backwards compatible. Would appreciate a second set of eyes from someone with history on keychain implementation (maybe @michaelneale ?) but looks ok to me.
|
|
||
| // Create the appropriate keyring backend based on environment | ||
| let keyring: Arc<dyn goose::keyring::KeyringBackend> = | ||
| if std::env::var("GOOSE_DISABLE_KEYRING").is_ok() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it will create a MockKeyringBackend, just curious about: MockKeyringBackend condition only hit while running test? or any chances thatGOOSE_DISABLE_KEYRING is set when user runs goose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch; fixed it by using a better abstraction.
1ba4ce8 to
838bf76
Compare
|
I still dont' quite follow this - was this tested once compiled, and also run as desktop bundle? as it seems a very risky change |
|
(I did test it with it bundled and signed, seemed ok) |
|
main concern is that goose-mcp has a goose crate dependency (don't really know what that means in the end, but feels potentially circular) |
This reverts commit 7e4a98d.
* main: (51 commits) docs: reflecting benefits of CLI providers (block#3399) feat: fetch openrouter supported models in `goose configure` (block#3347) Add the ability to configure rustyline to use a different edit mode (e.g. vi) (block#2769) docs: update CLI provider guide (block#3397) Streamable HTTP CLI flag (block#3394) docs: Show both remote options for extensions in CLI (block#3392) docs: fix YouTube Transcript MCP package manager (block#3390) docs: simplify alby mcp (block#3379) docs: add max turns (block#3372) feat(cli): add cost estimation per provider for Goose CLI (block#3330) feat: Allow Ollama for non-tool models for chat only (block#3308) [cli] Add --provider and --model CLI options to run command (block#3295) Docs: Lead/worker model in Goose Desktop (block#3342) revert: refactor: abstract keyring logic to better enable DI (block#3358) Drop temporal-service binary (block#3340) docs: add fuzzy search (block#3357) Fix name of GPT-4.1 System Prompt (block#3348) (block#3351) docs: add goose-mobile (block#3315) refactor: abstract keyring logic to better enable DI (block#3262) fix: correct tool use for anthropic (block#3311) ...
Signed-off-by: Adam Tarantino <[email protected]>
Signed-off-by: Soroosh <[email protected]>
Signed-off-by: Kyle Santiago <[email protected]>
This change introduces a clean keyring abstraction layer to replace global keyring state with dependency injection, addressing critical runtime errors and improving testability.
The implementation creates a KeyringBackend trait with concrete implementations for system keyring (SystemKeyringBackend) and file-based storage (FileKeyringBackend), allowing the configuration system to use different keyring backends based on the GOOSE_DISABLE_KEYRING environment variable.
This architectural improvement eliminates the need to touch the user's real OS keyring and thus enables comprehensive unit testing without keychain popups. The refactoring maintains full backward compatibility while establishing a foundation for reliable testing and development workflows