From 9d8fa17dde6ec31cb11fe3776467d313d92d07bc Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 12 Sep 2025 15:37:17 -0600 Subject: [PATCH] refactor: Refactor configuration structs directly into the provider definition to simplify validation, structs, and future extensions --- Cargo.lock | 1345 ++++++++++++++++- Cargo.toml | 5 +- ...r.nuspec.template => gman.nuspec.template} | 0 ...{managarr.rb.template => gman.rb.template} | 0 src/bin/gman/cli.rs | 29 +- src/bin/gman/main.rs | 34 +- src/config.rs | 54 +- src/providers/aws_secrets_manager.rs | 0 src/providers/local.rs | 185 +-- src/providers/mod.rs | 58 +- tests/config_tests.rs | 56 - tests/providers/local_tests.rs | 59 +- tests/providers/provider_tests.rs | 57 +- 13 files changed, 1535 insertions(+), 347 deletions(-) rename deployment/chocolatey/{managarr.nuspec.template => gman.nuspec.template} (100%) rename deployment/homebrew/{managarr.rb.template => gman.rb.template} (100%) create mode 100644 src/providers/aws_secrets_manager.rs diff --git a/Cargo.lock b/Cargo.lock index 36a3385..beb0011 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,12 +135,397 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-config" +version = "1.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc1b40fb26027769f16960d2f4a6bc20c4bb755d403e552c8c1a73af433c246" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.3.1", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d025db5d9f52cbc413b167136afb3d8aeea708c0d8884783cf6253be5e22f6f2" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c034a1bc1d70e16e7f4e4caf7e9f7693e4c9c24cd91cf17c2a0b21abaebc7c8b" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-secretsmanager" +version = "1.88.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1656cc8753202f255a1bcc6e06f9e768f30968684022fd0dd2f8912cad00fcef" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357a841807f6b52cb26123878b3326921e2a25faca412fabdd32bd35b7edd5d3" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e05f33b6c9026fecfe9b3b6740f34d41bc6ff641a6a32dabaab60209245b75" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.86.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d835f123f307cafffca7b9027c14979f1d403b417d8541d67cf252e8a21e35" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084c34162187d39e3740cb635acd73c4e3a551a36146ad6fe8883c929c9f876c" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.3.1", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c4dacf2d38996cf729f55e7a762b30918229917eca115de45dfa8dfb97796c9" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147e8eea63a40315d704b97bf9bc9b8c1402ae94f89d5ad6f7550d963309da1b" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.12", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.7.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.31", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa31b350998e703e9826b2104dd6f63be0508666e1aba88137af060e8944047" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa63ad37685ceb7762fa4d73d06f1d5493feb88e3f27259b9ed277f4c01b185" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f5e0fc8a6b3f2303f331b94504bbf754d85488f402d6f1dd7a6080f99afe56" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b069d19bf01e46298eaedd7c6f283fe565a59263e53eebec945f3e6398f42390" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -156,18 +541,54 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -225,15 +646,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] -name = "cc" -version = "1.2.37" +name = "bytes" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "cc" +version = "1.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.3" @@ -289,6 +737,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.47" @@ -339,6 +798,15 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -379,6 +847,26 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -596,12 +1084,24 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -616,12 +1116,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.14" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.60.2", ] [[package]] @@ -660,6 +1160,51 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -690,7 +1235,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.5+wasi-0.2.4", + "wasi 0.14.4+wasi-0.2.4", ] [[package]] @@ -699,15 +1244,23 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "gman" -version = "0.2.0" +version = "0.1.0" dependencies = [ "anyhow", "argon2", "assert_cmd", + "aws-config", + "aws-sdk-secretsmanager", "backtrace", - "base64", + "base64 0.22.1", "chacha20poly1305", "chrono", "clap", @@ -733,10 +1286,49 @@ dependencies = [ "serde_yaml", "tempfile", "thiserror", + "tokio", "validator", "zeroize", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.11.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap 2.11.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -761,6 +1353,83 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "human-panic" version = "2.0.3" @@ -779,15 +1448,118 @@ dependencies = [ [[package]] name = "humantime" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.12", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.3.1", + "hyper 1.7.0", + "hyper-util", + "rustls 0.23.31", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.7.0", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -933,9 +1705,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -957,18 +1729,54 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.78" @@ -991,6 +1799,16 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.3", +] + [[package]] name = "libredox" version = "0.1.9" @@ -1003,9 +1821,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -1079,6 +1897,12 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1106,6 +1930,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1118,6 +1952,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1154,6 +1997,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + [[package]] name = "option-ext" version = "0.2.0" @@ -1181,6 +2030,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parking_lot" version = "0.12.4" @@ -1221,14 +2076,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "plist" version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ - "base64", - "indexmap 2.11.1", + "base64 0.22.1", + "indexmap 2.11.0", "quick-xml", "serde", "time", @@ -1309,6 +2176,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1500,12 +2377,32 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" + [[package]] name = "regex-syntax" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rpassword" version = "7.4.0" @@ -1534,16 +2431,121 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] -name = "rustix" -version = "1.1.2" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.5", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.4.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1570,6 +2572,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.0", +] + [[package]] name = "schemars" version = "0.9.0" @@ -1600,6 +2611,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "secrecy" version = "0.10.3" @@ -1609,6 +2630,48 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -1666,11 +2729,11 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.1", + "indexmap 2.11.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -1698,13 +2761,24 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.11.1", + "indexmap 2.11.0", "itoa", "ryu", "serde", "unsafe-libyaml", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shell-words" version = "1.1.0" @@ -1747,12 +2821,38 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1892,6 +2992,70 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.31", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.9.5" @@ -1919,6 +3083,65 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typemap-ors" version = "1.0.0" @@ -1942,9 +3165,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -1989,6 +3212,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -2001,6 +3230,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2020,6 +3255,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -2058,6 +3295,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -2067,6 +3310,15 @@ dependencies = [ "libc", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2075,18 +3327,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.5+wasi-0.2.4" +version = "0.14.4+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.0+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" +checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" dependencies = [ "wit-bindgen", ] @@ -2174,13 +3417,13 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.62.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -2221,20 +3464,20 @@ checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-result" -version = "0.4.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.1.3", ] [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link 0.2.0", + "windows-link 0.1.3", ] [[package]] @@ -2414,6 +3657,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 71d91c0..25305f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gman" -version = "0.2.0" +version = "0.1.0" edition = "2024" authors = ["Alex Clarke "] description = "Universal secret management and injection tool" @@ -49,6 +49,9 @@ indoc = "2.0.6" regex = "1.11.2" serde_yaml = "0.9.34" tempfile = "3.22.0" +aws-sdk-secretsmanager = "1.88.0" +tokio = { version = "1.47.1", features = ["full"] } +aws-config = { version = "1.8.6", features = ["behavior-version-latest"] } [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/deployment/chocolatey/managarr.nuspec.template b/deployment/chocolatey/gman.nuspec.template similarity index 100% rename from deployment/chocolatey/managarr.nuspec.template rename to deployment/chocolatey/gman.nuspec.template diff --git a/deployment/homebrew/managarr.rb.template b/deployment/homebrew/gman.rb.template similarity index 100% rename from deployment/homebrew/managarr.rb.template rename to deployment/homebrew/gman.rb.template diff --git a/src/bin/gman/cli.rs b/src/bin/gman/cli.rs index 0d48c5c..9427bac 100644 --- a/src/bin/gman/cli.rs +++ b/src/bin/gman/cli.rs @@ -1,6 +1,6 @@ use crate::command::preview_command; use anyhow::{Context, Result, anyhow}; -use gman::config::{Config, ProviderConfig, RunConfig}; +use gman::config::{Config, RunConfig}; use gman::providers::SecretProvider; use heck::ToSnakeCase; use log::{debug, error}; @@ -15,9 +15,8 @@ const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}"; const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}"; pub fn wrap_and_run_command( - secrets_provider: Box, + secrets_provider: &mut dyn SecretProvider, config: &Config, - provider_config: &ProviderConfig, tokens: Vec, profile_name: Option, dry_run: bool, @@ -51,7 +50,7 @@ pub fn wrap_and_run_command( run_config_profile_name ); secrets_provider - .get_secret(provider_config, key.to_snake_case().to_uppercase().as_str()) + .get_secret(key.to_snake_case().to_uppercase().as_str()) .ok() .map_or_else( || { @@ -254,7 +253,7 @@ pub fn parse_args( mod tests { use super::*; use crate::cli::generate_files_secret_injections; - use gman::config::{Config, ProviderConfig, RunConfig}; + use gman::config::{Config, RunConfig}; use pretty_assertions::{assert_eq, assert_str_eq}; use std::collections::HashMap; use std::ffi::OsString; @@ -264,16 +263,16 @@ mod tests { fn name(&self) -> &'static str { "Dummy" } - fn get_secret(&self, _config: &ProviderConfig, key: &str) -> Result { + fn get_secret(&self, key: &str) -> Result { Ok(format!("{}_VAL", key)) } - fn set_secret(&self, _config: &ProviderConfig, _key: &str, _value: &str) -> Result<()> { + fn set_secret(&self, _key: &str, _value: &str) -> Result<()> { Ok(()) } - fn delete_secret(&self, _config: &ProviderConfig, _key: &str) -> Result<()> { + fn delete_secret(&self, _key: &str) -> Result<()> { Ok(()) } - fn sync(&self, _config: &mut ProviderConfig) -> Result<()> { + fn sync(&mut self) -> Result<()> { Ok(()) } } @@ -345,10 +344,10 @@ mod tests { #[test] fn test_wrap_and_run_command_no_profile() { let cfg = Config::default(); - let provider_cfg = ProviderConfig::default(); - let prov: Box = Box::new(DummyProvider); + let mut dummy = DummyProvider; + let prov: &mut dyn SecretProvider = &mut dummy; let tokens = vec![OsString::from("echo"), OsString::from("hi")]; - let err = wrap_and_run_command(prov, &cfg, &provider_cfg, tokens, None, true).unwrap_err(); + let err = wrap_and_run_command(prov, &cfg, tokens, None, true).unwrap_err(); assert!(err.to_string().contains("No run profile found")); } @@ -367,13 +366,13 @@ mod tests { run_configs: Some(vec![run_cfg]), ..Config::default() }; - let provider_cfg = ProviderConfig::default(); - let prov: Box = Box::new(DummyProvider); + let mut dummy = DummyProvider; + let prov: &mut dyn SecretProvider = &mut dummy; // Capture stderr for dry_run preview let tokens = vec![OsString::from("echo"), OsString::from("hello")]; // Best-effort: ensure function does not error under dry_run - let res = wrap_and_run_command(prov, &cfg, &provider_cfg, tokens, None, true); + let res = wrap_and_run_command(prov, &cfg, tokens, None, true); assert!(res.is_ok()); // Not asserting output text to keep test platform-agnostic } diff --git a/src/bin/gman/main.rs b/src/bin/gman/main.rs index f37873f..492a053 100644 --- a/src/bin/gman/main.rs +++ b/src/bin/gman/main.rs @@ -116,7 +116,8 @@ enum Commands { }, } -fn main() -> Result<()> { +#[tokio::main] +async fn main() -> Result<()> { if let Err(e) = log4rs::init_config(utils::init_logging_config()) { eprintln!("Failed to initialize logging: {e}"); } @@ -144,7 +145,7 @@ fn main() -> Result<()> { read_all_stdin().with_context(|| "unable to read plaintext from stdin")?; let snake_case_name = name.to_snake_case().to_uppercase(); secrets_provider - .set_secret(&provider_config, &snake_case_name, plaintext.trim_end()) + .set_secret(&snake_case_name, plaintext.trim_end()) .map(|_| match cli.output { Some(_) => (), None => println!("✓ Secret '{snake_case_name}' added to the vault."), @@ -153,7 +154,7 @@ fn main() -> Result<()> { Commands::Get { name } => { let snake_case_name = name.to_snake_case().to_uppercase(); secrets_provider - .get_secret(&provider_config, &snake_case_name) + .get_secret(&snake_case_name) .map(|secret| match cli.output { Some(OutputFormat::Json) => { let json_output = serde_json::json!({ @@ -175,7 +176,7 @@ fn main() -> Result<()> { read_all_stdin().with_context(|| "unable to read plaintext from stdin")?; let snake_case_name = name.to_snake_case().to_uppercase(); secrets_provider - .update_secret(&provider_config, &snake_case_name, plaintext.trim_end()) + .update_secret(&snake_case_name, plaintext.trim_end()) .map(|_| match cli.output { Some(_) => (), None => println!("✓ Secret '{snake_case_name}' updated in the vault."), @@ -183,16 +184,14 @@ fn main() -> Result<()> { } Commands::Delete { name } => { let snake_case_name = name.to_snake_case().to_uppercase(); - secrets_provider - .delete_secret(&provider_config, &snake_case_name) - .map(|_| { - if cli.output.is_none() { - println!("✓ Secret '{snake_case_name}' deleted from the vault.") - } - })?; + secrets_provider.delete_secret(&snake_case_name).map(|_| { + if cli.output.is_none() { + println!("✓ Secret '{snake_case_name}' deleted from the vault.") + } + })?; } Commands::List {} => { - let secrets = secrets_provider.list_secrets(&provider_config)?; + let secrets = secrets_provider.list_secrets()?; if secrets.is_empty() { match cli.output { Some(OutputFormat::Json) => { @@ -218,21 +217,14 @@ fn main() -> Result<()> { } } Commands::Sync {} => { - secrets_provider.sync(&mut provider_config).map(|_| { + secrets_provider.sync().map(|_| { if cli.output.is_none() { println!("✓ Secrets synchronized with remote") } })?; } Commands::External(tokens) => { - wrap_and_run_command( - secrets_provider, - &config, - &provider_config, - tokens, - cli.profile, - cli.dry_run, - )?; + wrap_and_run_command(secrets_provider, &config, tokens, cli.profile, cli.dry_run)?; } Commands::Completions { shell } => { let mut cmd = Cli::command(); diff --git a/src/config.rs b/src/config.rs index b4a3800..c2ef7ea 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,7 +25,7 @@ use anyhow::{Context, Result}; use log::debug; use serde::{Deserialize, Serialize}; use serde_with::serde_as; -use serde_with::{DisplayFromStr, skip_serializing_none}; +use serde_with::skip_serializing_none; use std::borrow::Cow; use std::path::PathBuf; use std::{env, fs}; @@ -97,8 +97,6 @@ fn flags_or_files(run_config: &RunConfig) -> Result<(), ValidationError> { } } -#[serde_as] -#[skip_serializing_none] /// Configuration for a secret provider. /// /// Example: create a local provider config and validate it @@ -108,37 +106,27 @@ fn flags_or_files(run_config: &RunConfig) -> Result<(), ValidationError> { /// use gman::providers::local::LocalProvider; /// use validator::Validate; /// -/// let provider_type = SupportedProvider::Local(LocalProvider); +/// let provider_type = SupportedProvider::Local { provider_def: LocalProvider::default() }; /// let provider_config = ProviderConfig { provider_type, ..Default::default() }; /// provider_config.validate().unwrap(); /// ``` #[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)] +#[skip_serializing_none] pub struct ProviderConfig { #[validate(required)] pub name: Option, - #[serde_as(as = "DisplayFromStr")] - #[serde(rename = "type")] + #[serde(flatten, rename = "type")] + #[validate(nested)] pub provider_type: SupportedProvider, - pub password_file: Option, - pub git_branch: Option, - pub git_remote_url: Option, - pub git_user_name: Option, - #[validate(email)] - pub git_user_email: Option, - pub git_executable: Option, } impl Default for ProviderConfig { fn default() -> Self { Self { name: Some("local".into()), - provider_type: SupportedProvider::Local(LocalProvider), - password_file: Config::local_provider_password_file(), - git_branch: Some("main".into()), - git_remote_url: None, - git_user_name: None, - git_user_email: None, - git_executable: None, + provider_type: SupportedProvider::Local { + provider_def: LocalProvider::default(), + }, } } } @@ -151,11 +139,11 @@ impl ProviderConfig { /// let provider_config = ProviderConfig::default().extract_provider(); /// println!("using provider: {}", provider_config.name()); /// ``` - pub fn extract_provider(&self) -> Box { - match &self.provider_type { - SupportedProvider::Local(p) => { + pub fn extract_provider(&mut self) -> &mut dyn SecretProvider { + match &mut self.provider_type { + SupportedProvider::Local { provider_def } => { debug!("Using local secret provider"); - Box::new(*p) + provider_def } } } @@ -173,7 +161,7 @@ impl ProviderConfig { /// use gman::providers::local::LocalProvider; /// use validator::Validate; /// -/// let provider_type = SupportedProvider::Local(LocalProvider); +/// let provider_type = SupportedProvider::Local{ provider_def: LocalProvider::default() }; /// let provider_config = ProviderConfig { provider_type, ..Default::default() }; /// let cfg = Config{ providers: vec![provider_config], ..Default::default() }; /// cfg.validate().unwrap(); @@ -289,12 +277,16 @@ pub fn load_config() -> Result { config .providers .iter_mut() - .filter(|p| matches!(p.provider_type, SupportedProvider::Local(_))) - .for_each(|p| { - if p.password_file.is_none() - && let Some(local_password_file) = Config::local_provider_password_file() - { - p.password_file = Some(local_password_file); + .filter(|p| matches!(p.provider_type, SupportedProvider::Local { .. })) + .for_each(|p| match p.provider_type { + SupportedProvider::Local { + ref mut provider_def, + } => { + if provider_def.password_file.is_none() + && let Some(local_password_file) = Config::local_provider_password_file() + { + provider_def.password_file = Some(local_password_file); + } } }); diff --git a/src/providers/aws_secrets_manager.rs b/src/providers/aws_secrets_manager.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/providers/local.rs b/src/providers/local.rs index 0931631..3f2b604 100644 --- a/src/providers/local.rs +++ b/src/providers/local.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::{env, fs}; use zeroize::Zeroize; -use crate::config::ProviderConfig; +use crate::config::Config; use crate::providers::SecretProvider; use crate::providers::git_sync::{SyncOpts, repo_name_from_url, sync_and_push}; use crate::{ @@ -21,27 +21,12 @@ use chacha20poly1305::{ }; use dialoguer::{Input, theme}; use log::{debug, error}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use theme::ColorfulTheme; use validator::Validate; -/// Configuration for the local file-based provider. -#[derive(Debug, Clone)] -pub struct LocalProviderConfig { - pub vault_path: String, -} - -impl Default for LocalProviderConfig { - fn default() -> Self { - Self { - vault_path: dirs::home_dir() - .map(|p| p.join(".gman_vault")) - .and_then(|p| p.to_str().map(|s| s.to_string())) - .unwrap_or_else(|| ".gman_vault".into()), - } - } -} - +#[skip_serializing_none] /// File-based vault provider with optional Git sync. /// /// This provider stores encrypted envelopes in a per-user configuration @@ -54,37 +39,59 @@ impl Default for LocalProviderConfig { /// use gman::providers::SecretProvider; /// use gman::config::Config; /// -/// let provider = LocalProvider; +/// let provider = LocalProvider::default(); /// let cfg = Config::default(); /// // Will prompt for a password when reading/writing secrets unless a /// // password file is configured. /// // provider.set_secret(&cfg, "MY_SECRET", "value")?; /// # Ok::<(), anyhow::Error>(()) /// ``` -#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)] -pub struct LocalProvider; +#[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct LocalProvider { + pub password_file: Option, + pub git_branch: Option, + pub git_remote_url: Option, + pub git_user_name: Option, + #[validate(email)] + pub git_user_email: Option, + pub git_executable: Option, +} + +impl Default for LocalProvider { + fn default() -> Self { + Self { + password_file: Config::local_provider_password_file(), + git_branch: Some("main".into()), + git_remote_url: None, + git_user_name: None, + git_user_email: None, + git_executable: None, + } + } +} impl SecretProvider for LocalProvider { fn name(&self) -> &'static str { "LocalProvider" } - fn get_secret(&self, config: &ProviderConfig, key: &str) -> Result { - let vault_path = active_vault_path(config)?; + fn get_secret(&self, key: &str) -> Result { + let vault_path = self.active_vault_path()?; let vault: HashMap = load_vault(&vault_path).unwrap_or_default(); let envelope = vault .get(key) .with_context(|| format!("key '{key}' not found in the vault"))?; - let password = get_password(config)?; + let password = self.get_password()?; let plaintext = decrypt_string(&password, envelope)?; drop(password); Ok(plaintext) } - fn set_secret(&self, config: &ProviderConfig, key: &str, value: &str) -> Result<()> { - let vault_path = active_vault_path(config)?; + fn set_secret(&self, key: &str, value: &str) -> Result<()> { + let vault_path = self.active_vault_path()?; let mut vault: HashMap = load_vault(&vault_path).unwrap_or_default(); if vault.contains_key(key) { error!( @@ -93,7 +100,7 @@ impl SecretProvider for LocalProvider { bail!("key '{key}' already exists"); } - let password = get_password(config)?; + let password = self.get_password()?; let envelope = encrypt_string(&password, value)?; drop(password); @@ -102,11 +109,11 @@ impl SecretProvider for LocalProvider { store_vault(&vault_path, &vault).with_context(|| "failed to save secret to the vault") } - fn update_secret(&self, config: &ProviderConfig, key: &str, value: &str) -> Result<()> { - let vault_path = active_vault_path(config)?; + fn update_secret(&self, key: &str, value: &str) -> Result<()> { + let vault_path = self.active_vault_path()?; let mut vault: HashMap = load_vault(&vault_path).unwrap_or_default(); - let password = get_password(config)?; + let password = self.get_password()?; let envelope = encrypt_string(&password, value)?; drop(password); @@ -125,8 +132,8 @@ impl SecretProvider for LocalProvider { store_vault(&vault_path, &vault).with_context(|| "failed to save secret to the vault") } - fn delete_secret(&self, config: &ProviderConfig, key: &str) -> Result<()> { - let vault_path = active_vault_path(config)?; + fn delete_secret(&self, key: &str) -> Result<()> { + let vault_path = self.active_vault_path()?; let mut vault: HashMap = load_vault(&vault_path).unwrap_or_default(); if !vault.contains_key(key) { error!("Key '{key}' does not exist in the vault."); @@ -137,18 +144,18 @@ impl SecretProvider for LocalProvider { store_vault(&vault_path, &vault).with_context(|| "failed to save secret to the vault") } - fn list_secrets(&self, config: &ProviderConfig) -> Result> { - let vault_path = active_vault_path(config)?; + fn list_secrets(&self) -> Result> { + let vault_path = self.active_vault_path()?; let vault: HashMap = load_vault(&vault_path).unwrap_or_default(); let keys: Vec = vault.keys().cloned().collect(); Ok(keys) } - fn sync(&self, config: &mut ProviderConfig) -> Result<()> { + fn sync(&mut self) -> Result<()> { let mut config_changed = false; - if config.git_branch.is_none() { + if self.git_branch.is_none() { config_changed = true; debug!("Prompting user to set git_branch in config for sync"); let branch: String = Input::with_theme(&ColorfulTheme::default()) @@ -156,18 +163,18 @@ impl SecretProvider for LocalProvider { .default("main".into()) .interact_text()?; - config.git_branch = Some(branch); + self.git_branch = Some(branch); } - if config.git_remote_url.is_none() { + if self.git_remote_url.is_none() { config_changed = true; debug!("Prompting user to set git_remote in config for sync"); let remote: String = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter remote git URL to sync with") .validate_with(|s: &String| { - ProviderConfig { + LocalProvider { git_remote_url: Some(s.clone()), - ..ProviderConfig::default() + ..LocalProvider::default() } .validate() .map(|_| ()) @@ -175,27 +182,66 @@ impl SecretProvider for LocalProvider { }) .interact_text()?; - config.git_remote_url = Some(remote); + self.git_remote_url = Some(remote); } if config_changed { debug!("Saving updated config"); - confy::store("gman", "config", &config) + confy::store("gman", "config", &self) .with_context(|| "failed to save updated config")?; } let sync_opts = SyncOpts { - remote_url: &config.git_remote_url, - branch: &config.git_branch, - user_name: &config.git_user_name, - user_email: &config.git_user_email, - git_executable: &config.git_executable, + remote_url: &self.git_remote_url, + branch: &self.git_branch, + user_name: &self.git_user_name, + user_email: &self.git_user_email, + git_executable: &self.git_executable, }; sync_and_push(&sync_opts) } } +impl LocalProvider { + fn repo_dir_for_config(&self) -> Result> { + if let Some(remote) = &self.git_remote_url { + let name = repo_name_from_url(remote); + let dir = base_config_dir()?.join(format!(".{}", name)); + Ok(Some(dir)) + } else { + Ok(None) + } + } + + fn active_vault_path(&self) -> Result { + if let Some(dir) = self.repo_dir_for_config()? + && dir.exists() + { + return Ok(dir.join("vault.yml")); + } + + default_vault_path() + } + + fn get_password(&self) -> Result { + if let Some(password_file) = &self.password_file { + let password = SecretString::new( + fs::read_to_string(password_file) + .with_context(|| format!("failed to read password file {:?}", password_file))? + .trim() + .to_string() + .into(), + ); + + Ok(password) + } else { + let password = rpassword::prompt_password("\nPassword: ")?; + Ok(SecretString::new(password.into())) + } + } +} + fn default_vault_path() -> Result { let xdg_path = env::var_os("XDG_CONFIG_HOME").map(PathBuf::from); @@ -213,26 +259,6 @@ fn base_config_dir() -> Result { .ok_or_else(|| anyhow!("Failed to determine config dir")) } -fn repo_dir_for_config(config: &ProviderConfig) -> Result> { - if let Some(remote) = &config.git_remote_url { - let name = repo_name_from_url(remote); - let dir = base_config_dir()?.join(format!(".{}", name)); - Ok(Some(dir)) - } else { - Ok(None) - } -} - -fn active_vault_path(config: &ProviderConfig) -> Result { - if let Some(dir) = repo_dir_for_config(config)? - && dir.exists() - { - return Ok(dir.join("vault.yml")); - } - - default_vault_path() -} - fn load_vault(path: &Path) -> Result> { if !path.exists() { return Ok(HashMap::new()); @@ -394,23 +420,6 @@ fn decrypt_string(password: &SecretString, envelope: &str) -> Result { Ok(s) } -fn get_password(config: &ProviderConfig) -> Result { - if let Some(password_file) = &config.password_file { - let password = SecretString::new( - fs::read_to_string(password_file) - .with_context(|| format!("failed to read password file {:?}", password_file))? - .trim() - .to_string() - .into(), - ); - - Ok(password) - } else { - let password = rpassword::prompt_password("\nPassword: ")?; - Ok(SecretString::new(password.into())) - } -} - #[cfg(test)] mod tests { use super::*; @@ -448,11 +457,11 @@ mod tests { let dir = tempdir().unwrap(); let file = dir.path().join("pw.txt"); fs::write(&file, "secretpw\n").unwrap(); - let cfg = ProviderConfig { + let provider = LocalProvider { password_file: Some(file), - ..ProviderConfig::default() + ..LocalProvider::default() }; - let pw = get_password(&cfg).unwrap(); + let pw = provider.get_password().unwrap(); assert_eq!(pw.expose_secret(), "secretpw"); } } diff --git a/src/providers/mod.rs b/src/providers/mod.rs index d287135..a76db9b 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -2,46 +2,36 @@ //! //! Implementations provide storage/backends for secrets and a common //! interface used by the CLI. -//! -//! Selecting a provider from a string: -//! ``` -//! use std::str::FromStr; -//! use gman::providers::SupportedProvider; -//! -//! let p = SupportedProvider::from_str("local").unwrap(); -//! assert_eq!(p.to_string(), "local"); -//! ``` mod git_sync; pub mod local; -use crate::config::ProviderConfig; use crate::providers::local::LocalProvider; use anyhow::{Result, anyhow}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; -use std::str::FromStr; use thiserror::Error; +use validator::{Validate, ValidationErrors}; /// A secret storage backend capable of CRUD and sync, with optional /// update and listing pub trait SecretProvider { fn name(&self) -> &'static str; - fn get_secret(&self, config: &ProviderConfig, key: &str) -> Result; - fn set_secret(&self, config: &ProviderConfig, key: &str, value: &str) -> Result<()>; - fn update_secret(&self, _config: &ProviderConfig, _key: &str, _value: &str) -> Result<()> { + fn get_secret(&self, key: &str) -> Result; + fn set_secret(&self, key: &str, value: &str) -> Result<()>; + fn update_secret(&self, _key: &str, _value: &str) -> Result<()> { Err(anyhow!( "update secret not supported for provider {}", self.name() )) } - fn delete_secret(&self, config: &ProviderConfig, key: &str) -> Result<()>; - fn list_secrets(&self, _config: &ProviderConfig) -> Result> { + fn delete_secret(&self, key: &str) -> Result<()>; + fn list_secrets(&self) -> Result> { Err(anyhow!( "list secrets is not supported for the provider {}", self.name() )) } - fn sync(&self, config: &mut ProviderConfig) -> Result<()>; + fn sync(&mut self) -> Result<()>; } /// Errors when parsing a provider identifier. @@ -52,24 +42,28 @@ pub enum ParseProviderError { } /// Registry of built-in providers. -#[derive(Debug, Clone, Copy, Deserialize, Eq, PartialEq)] +#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] +#[serde(deny_unknown_fields, tag = "type", rename_all = "snake_case")] +//TODO test that this works with the AWS config pub enum SupportedProvider { - Local(LocalProvider), + Local { + #[serde(flatten)] + provider_def: LocalProvider, + }, +} + +impl Validate for SupportedProvider { + fn validate(&self) -> Result<(), ValidationErrors> { + match self { + SupportedProvider::Local { provider_def } => provider_def.validate(), + } + } } impl Default for SupportedProvider { fn default() -> Self { - SupportedProvider::Local(LocalProvider) - } -} - -impl FromStr for SupportedProvider { - type Err = ParseProviderError; - - fn from_str(s: &str) -> Result { - match s.trim().to_lowercase().as_str() { - "local" => Ok(SupportedProvider::Local(LocalProvider)), - _ => Err(ParseProviderError::Unsupported(s.to_string())), + SupportedProvider::Local { + provider_def: LocalProvider::default(), } } } @@ -77,7 +71,7 @@ impl FromStr for SupportedProvider { impl Display for SupportedProvider { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - SupportedProvider::Local(_) => write!(f, "local"), + SupportedProvider::Local { .. } => write!(f, "local"), } } } diff --git a/tests/config_tests.rs b/tests/config_tests.rs index e79cad1..2ce4fef 100644 --- a/tests/config_tests.rs +++ b/tests/config_tests.rs @@ -1,8 +1,6 @@ #[cfg(test)] mod tests { use gman::config::{Config, ProviderConfig, RunConfig}; - use gman::providers::SupportedProvider; - use gman::providers::local::LocalProvider; use pretty_assertions::assert_eq; use validator::Validate; @@ -161,65 +159,11 @@ mod tests { assert!(run_config.validate().is_err()); } - #[test] - fn test_provider_config_valid() { - let config = ProviderConfig { - name: Some("local-test".to_string()), - provider_type: SupportedProvider::Local(LocalProvider), - password_file: None, - git_branch: None, - git_remote_url: None, - git_user_name: None, - git_user_email: Some("test@example.com".to_string()), - git_executable: None, - }; - - assert!(config.validate().is_ok()); - } - - #[test] - fn test_provider_config_invalid_email() { - let config = ProviderConfig { - name: Some("local-test".to_string()), - provider_type: SupportedProvider::Local(LocalProvider), - password_file: None, - git_branch: None, - git_remote_url: None, - git_user_name: None, - git_user_email: Some("test".to_string()), - git_executable: None, - }; - - assert!(config.validate().is_err()); - } - - #[test] - fn test_provider_config_missing_name() { - let config = ProviderConfig { - name: None, - provider_type: SupportedProvider::Local(LocalProvider), - password_file: None, - git_branch: None, - git_remote_url: None, - git_user_name: None, - git_user_email: None, - git_executable: None, - }; - - assert!(config.validate().is_err()); - } - #[test] fn test_provider_config_default() { let config = ProviderConfig::default(); assert_eq!(config.name, Some("local".to_string())); - assert_eq!(config.git_user_email, None); - assert_eq!(config.password_file, Config::local_provider_password_file()); - assert_eq!(config.git_branch, Some("main".into())); - assert_eq!(config.git_remote_url, None); - assert_eq!(config.git_user_name, None); - assert_eq!(config.git_executable, None); } #[test] diff --git a/tests/providers/local_tests.rs b/tests/providers/local_tests.rs index a5bc7a6..db67c4b 100644 --- a/tests/providers/local_tests.rs +++ b/tests/providers/local_tests.rs @@ -1,21 +1,56 @@ -use gman::providers::local::LocalProviderConfig; +use gman::config::Config; +use gman::providers::local::LocalProvider; +use pretty_assertions::assert_eq; use pretty_assertions::assert_str_eq; - -#[test] -fn test_local_provider_config_default() { - let config = LocalProviderConfig::default(); - let expected_path = dirs::home_dir() - .map(|p| p.join(".gman_vault")) - .and_then(|p| p.to_str().map(|s| s.to_string())) - .unwrap_or_else(|| ".gman_vault".into()); - assert_str_eq!(config.vault_path, expected_path); -} +use validator::Validate; #[test] fn test_local_provider_name() { use gman::providers::SecretProvider; use gman::providers::local::LocalProvider; - let provider = LocalProvider; + let provider = LocalProvider::default(); assert_str_eq!(provider.name(), "LocalProvider"); } + +#[test] +fn test_local_provider_valid() { + let provider = LocalProvider { + password_file: None, + git_branch: None, + git_remote_url: None, + git_user_name: None, + git_user_email: Some("test@example.com".to_string()), + git_executable: None, + }; + + assert!(provider.validate().is_ok()); +} + +#[test] +fn test_local_provider_invalid_email() { + let config = LocalProvider { + password_file: None, + git_branch: None, + git_remote_url: None, + git_user_name: None, + git_user_email: Some("test".to_string()), + git_executable: None, + }; + + assert!(config.validate().is_err()); +} + +#[test] +fn test_local_provider_default() { + let provider = LocalProvider::default(); + assert_eq!( + provider.password_file, + Config::local_provider_password_file() + ); + assert_eq!(provider.git_branch, Some("main".into())); + assert_eq!(provider.git_remote_url, None); + assert_eq!(provider.git_user_name, None); + assert_eq!(provider.git_user_email, None); + assert_eq!(provider.git_executable, None); +} diff --git a/tests/providers/provider_tests.rs b/tests/providers/provider_tests.rs index 3564728..215f704 100644 --- a/tests/providers/provider_tests.rs +++ b/tests/providers/provider_tests.rs @@ -1,49 +1,20 @@ -use gman::providers::local::LocalProvider; -use gman::providers::{ParseProviderError, SupportedProvider}; -use pretty_assertions::{assert_eq, assert_str_eq}; -use std::str::FromStr; - -#[test] -fn test_supported_provider_from_str() { - assert_eq!( - SupportedProvider::from_str("local").unwrap(), - SupportedProvider::Local(LocalProvider) - ); - assert_eq!( - SupportedProvider::from_str(" Local ").unwrap(), - SupportedProvider::Local(LocalProvider) - ); - assert!(matches!( - SupportedProvider::from_str("invalid"), - Err(ParseProviderError::Unsupported(_)) - )); -} - -#[test] -fn test_supported_provider_display() { - assert_str_eq!(SupportedProvider::Local(LocalProvider).to_string(), "local"); -} - -#[test] -fn test_supported_provider_from_str_valid() { - assert_eq!( - SupportedProvider::from_str("local").unwrap(), - SupportedProvider::Local(LocalProvider) - ); - assert_eq!( - SupportedProvider::from_str("LOCAL").unwrap(), - SupportedProvider::Local(LocalProvider) - ); -} - -#[test] -fn test_supported_provider_from_str_invalid() { - let err = SupportedProvider::from_str("invalid").unwrap_err(); - assert_str_eq!(err.to_string(), "unsupported provider 'invalid'"); -} +use gman::config::ProviderConfig; +use gman::providers::ParseProviderError; +use pretty_assertions::assert_eq; +use validator::Validate; #[test] fn test_parse_provider_error_display() { let err = ParseProviderError::Unsupported("test".to_string()); assert_eq!(err.to_string(), "unsupported provider 'test'"); } + +#[test] +fn test_provider_config_missing_name() { + let config = ProviderConfig { + name: None, + ..Default::default() + }; + + assert!(config.validate().is_err()); +}