feat/secrets #111
							
								
								
									
										238
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										238
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -378,7 +378,7 @@ dependencies = [ | ||||
|  "serde_json", | ||||
|  "serde_repr", | ||||
|  "serde_urlencoded", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
|  "tokio", | ||||
|  "tokio-util", | ||||
|  "tower-service", | ||||
| @ -473,7 +473,7 @@ dependencies = [ | ||||
|  "semver", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -515,6 +515,12 @@ version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "cfg_aliases" | ||||
| version = "0.2.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "chacha20" | ||||
| version = "0.9.1" | ||||
| @ -1689,9 +1695,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "js-sys", | ||||
|  "libc", | ||||
|  "r-efi", | ||||
|  "wasi 0.14.2+wasi-0.2.4", | ||||
|  "wasm-bindgen", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -1789,6 +1797,7 @@ dependencies = [ | ||||
|  "env_logger", | ||||
|  "fqdn", | ||||
|  "futures-util", | ||||
|  "harmony-secret-derive", | ||||
|  "harmony_macros", | ||||
|  "harmony_types", | ||||
|  "helm-wrapper-rs", | ||||
| @ -1829,6 +1838,35 @@ dependencies = [ | ||||
|  "uuid", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "harmony-secret" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "async-trait", | ||||
|  "directories", | ||||
|  "harmony-secret-derive", | ||||
|  "http 1.3.1", | ||||
|  "infisical", | ||||
|  "lazy_static", | ||||
|  "log", | ||||
|  "pretty_assertions", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "tempfile", | ||||
|  "thiserror 2.0.14", | ||||
|  "tokio", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "harmony-secret-derive" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "proc-macro-crate", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "harmony_cli" | ||||
| version = "0.1.0" | ||||
| @ -1963,7 +2001,7 @@ dependencies = [ | ||||
|  "non-blank-string-rs", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -2131,7 +2169,7 @@ dependencies = [ | ||||
|  "httpdate", | ||||
|  "itoa", | ||||
|  "pin-project-lite", | ||||
|  "socket2", | ||||
|  "socket2 0.5.10", | ||||
|  "tokio", | ||||
|  "tower-service", | ||||
|  "tracing", | ||||
| @ -2210,6 +2248,7 @@ dependencies = [ | ||||
|  "tokio", | ||||
|  "tokio-rustls", | ||||
|  "tower-service", | ||||
|  "webpki-roots", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -2272,7 +2311,7 @@ dependencies = [ | ||||
|  "libc", | ||||
|  "percent-encoding", | ||||
|  "pin-project-lite", | ||||
|  "socket2", | ||||
|  "socket2 0.5.10", | ||||
|  "system-configuration 0.6.1", | ||||
|  "tokio", | ||||
|  "tower-service", | ||||
| @ -2489,6 +2528,21 @@ version = "2.0.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "infisical" | ||||
| version = "0.0.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4d97c33b08e22b2f7b9f87a8fc06a7d247442db7bf216ffc6661a74ed8aea658" | ||||
| dependencies = [ | ||||
|  "base64 0.22.1", | ||||
|  "reqwest 0.12.20", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "thiserror 1.0.69", | ||||
|  "tokio", | ||||
|  "url", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "inout" | ||||
| version = "0.1.4" | ||||
| @ -2529,6 +2583,17 @@ dependencies = [ | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "io-uring" | ||||
| version = "0.7.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" | ||||
| dependencies = [ | ||||
|  "bitflags 2.9.1", | ||||
|  "cfg-if", | ||||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "ipnet" | ||||
| version = "2.11.0" | ||||
| @ -2622,7 +2687,7 @@ dependencies = [ | ||||
|  "pest_derive", | ||||
|  "regex", | ||||
|  "serde_json", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -2723,7 +2788,7 @@ dependencies = [ | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "serde_yaml", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
|  "tokio", | ||||
|  "tokio-tungstenite", | ||||
|  "tokio-util", | ||||
| @ -2748,7 +2813,7 @@ dependencies = [ | ||||
|  "serde", | ||||
|  "serde-value", | ||||
|  "serde_json", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -2786,7 +2851,7 @@ dependencies = [ | ||||
|  "pin-project", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
|  "tokio", | ||||
|  "tokio-util", | ||||
|  "tracing", | ||||
| @ -2898,6 +2963,12 @@ dependencies = [ | ||||
|  "hashbrown 0.15.4", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "lru-slab" | ||||
| version = "0.1.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "md5" | ||||
| version = "0.7.0" | ||||
| @ -3200,7 +3271,7 @@ dependencies = [ | ||||
|  "pretty_assertions", | ||||
|  "rand 0.8.5", | ||||
|  "serde", | ||||
|  "thiserror 1.0.69", | ||||
|  "thiserror 2.0.14", | ||||
|  "tokio", | ||||
|  "uuid", | ||||
|  "xml-rs", | ||||
| @ -3367,7 +3438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" | ||||
| dependencies = [ | ||||
|  "memchr", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
|  "ucd-trie", | ||||
| ] | ||||
| 
 | ||||
| @ -3588,6 +3659,15 @@ dependencies = [ | ||||
|  "elliptic-curve", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "proc-macro-crate" | ||||
| version = "3.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" | ||||
| dependencies = [ | ||||
|  "toml_edit", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "proc-macro2" | ||||
| version = "1.0.95" | ||||
| @ -3603,6 +3683,61 @@ version = "0.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quinn" | ||||
| version = "0.11.8" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" | ||||
| dependencies = [ | ||||
|  "bytes", | ||||
|  "cfg_aliases", | ||||
|  "pin-project-lite", | ||||
|  "quinn-proto", | ||||
|  "quinn-udp", | ||||
|  "rustc-hash", | ||||
|  "rustls", | ||||
|  "socket2 0.5.10", | ||||
|  "thiserror 2.0.14", | ||||
|  "tokio", | ||||
|  "tracing", | ||||
|  "web-time", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quinn-proto" | ||||
| version = "0.11.12" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" | ||||
| dependencies = [ | ||||
|  "bytes", | ||||
|  "getrandom 0.3.3", | ||||
|  "lru-slab", | ||||
|  "rand 0.9.1", | ||||
|  "ring", | ||||
|  "rustc-hash", | ||||
|  "rustls", | ||||
|  "rustls-pki-types", | ||||
|  "slab", | ||||
|  "thiserror 2.0.14", | ||||
|  "tinyvec", | ||||
|  "tracing", | ||||
|  "web-time", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quinn-udp" | ||||
| version = "0.5.13" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" | ||||
| dependencies = [ | ||||
|  "cfg_aliases", | ||||
|  "libc", | ||||
|  "once_cell", | ||||
|  "socket2 0.5.10", | ||||
|  "tracing", | ||||
|  "windows-sys 0.59.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "1.0.40" | ||||
| @ -3721,7 +3856,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" | ||||
| dependencies = [ | ||||
|  "getrandom 0.2.16", | ||||
|  "libredox", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -3822,6 +3957,7 @@ dependencies = [ | ||||
|  "base64 0.22.1", | ||||
|  "bytes", | ||||
|  "encoding_rs", | ||||
|  "futures-channel", | ||||
|  "futures-core", | ||||
|  "futures-util", | ||||
|  "h2 0.4.10", | ||||
| @ -3838,6 +3974,8 @@ dependencies = [ | ||||
|  "native-tls", | ||||
|  "percent-encoding", | ||||
|  "pin-project-lite", | ||||
|  "quinn", | ||||
|  "rustls", | ||||
|  "rustls-pki-types", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
| @ -3845,6 +3983,7 @@ dependencies = [ | ||||
|  "sync_wrapper 1.0.2", | ||||
|  "tokio", | ||||
|  "tokio-native-tls", | ||||
|  "tokio-rustls", | ||||
|  "tokio-util", | ||||
|  "tower", | ||||
|  "tower-http", | ||||
| @ -3854,6 +3993,7 @@ dependencies = [ | ||||
|  "wasm-bindgen-futures", | ||||
|  "wasm-streams", | ||||
|  "web-sys", | ||||
|  "webpki-roots", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -4017,7 +4157,7 @@ dependencies = [ | ||||
|  "flurry", | ||||
|  "log", | ||||
|  "serde", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
|  "tokio", | ||||
|  "tokio-util", | ||||
| ] | ||||
| @ -4043,6 +4183,12 @@ version = "0.1.25" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustc-hash" | ||||
| version = "2.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "rustc_version" | ||||
| version = "0.4.1" | ||||
| @ -4142,6 +4288,7 @@ version = "1.12.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" | ||||
| dependencies = [ | ||||
|  "web-time", | ||||
|  "zeroize", | ||||
| ] | ||||
| 
 | ||||
| @ -4580,7 +4727,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" | ||||
| dependencies = [ | ||||
|  "num-bigint", | ||||
|  "num-traits", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
|  "time", | ||||
| ] | ||||
| 
 | ||||
| @ -4627,6 +4774,16 @@ dependencies = [ | ||||
|  "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 = "spin" | ||||
| version = "0.9.8" | ||||
| @ -4764,9 +4921,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.104" | ||||
| version = "2.0.105" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" | ||||
| checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
| @ -4900,11 +5057,11 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "thiserror" | ||||
| version = "2.0.12" | ||||
| version = "2.0.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" | ||||
| checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" | ||||
| dependencies = [ | ||||
|  "thiserror-impl 2.0.12", | ||||
|  "thiserror-impl 2.0.14", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -4920,9 +5077,9 @@ dependencies = [ | ||||
| 
 | ||||
| [[package]] | ||||
| name = "thiserror-impl" | ||||
| version = "2.0.12" | ||||
| version = "2.0.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" | ||||
| checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
| @ -4989,20 +5146,38 @@ dependencies = [ | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "tokio" | ||||
| version = "1.45.1" | ||||
| name = "tinyvec" | ||||
| version = "1.9.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" | ||||
| checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" | ||||
| dependencies = [ | ||||
|  "tinyvec_macros", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "tinyvec_macros" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" | ||||
| 
 | ||||
| [[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 1.0.4", | ||||
|  "parking_lot", | ||||
|  "pin-project-lite", | ||||
|  "signal-hook-registry", | ||||
|  "socket2", | ||||
|  "slab", | ||||
|  "socket2 0.6.0", | ||||
|  "tokio-macros", | ||||
|  "windows-sys 0.52.0", | ||||
|  "windows-sys 0.59.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -5252,7 +5427,7 @@ dependencies = [ | ||||
|  "log", | ||||
|  "rand 0.9.1", | ||||
|  "sha1", | ||||
|  "thiserror 2.0.12", | ||||
|  "thiserror 2.0.14", | ||||
|  "utf-8", | ||||
| ] | ||||
| 
 | ||||
| @ -5553,6 +5728,15 @@ dependencies = [ | ||||
|  "wasm-bindgen", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "webpki-roots" | ||||
| version = "1.0.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" | ||||
| dependencies = [ | ||||
|  "rustls-pki-types", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "winapi" | ||||
| version = "0.3.9" | ||||
|  | ||||
| @ -12,6 +12,8 @@ members = [ | ||||
|   "harmony_cli", | ||||
|   "k3d", | ||||
|   "harmony_composer", | ||||
|   "harmony_secret_derive", | ||||
|   "harmony_secret", | ||||
| ] | ||||
| 
 | ||||
| [workspace.package] | ||||
| @ -53,6 +55,10 @@ chrono = "0.4" | ||||
| similar = "2" | ||||
| uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] } | ||||
| pretty_assertions = "1.4.1" | ||||
| tempfile = "3.20.0" | ||||
| bollard = "0.19.1" | ||||
| base64 = "0.22.1" | ||||
| tar = "0.4.44" | ||||
| lazy_static = "1.5.0" | ||||
| directories = "6.0.0" | ||||
| thiserror = "2.0.14" | ||||
|  | ||||
| @ -38,8 +38,8 @@ serde-value.workspace = true | ||||
| helm-wrapper-rs = "0.4.0" | ||||
| non-blank-string-rs = "1.0.4" | ||||
| k3d-rs = { path = "../k3d" } | ||||
| directories = "6.0.0" | ||||
| lazy_static = "1.5.0" | ||||
| directories.workspace = true | ||||
| lazy_static.workspace = true | ||||
| dockerfile_builder = "0.1.5" | ||||
| temp-file = "0.1.9" | ||||
| convert_case.workspace = true | ||||
| @ -59,7 +59,7 @@ similar.workspace = true | ||||
| futures-util = "0.3.31" | ||||
| tokio-util = "0.7.15" | ||||
| strum = { version = "0.27.1", features = ["derive"] } | ||||
| tempfile = "3.20.0" | ||||
| tempfile.workspace = true | ||||
| serde_with = "3.14.0" | ||||
| schemars = "0.8.22" | ||||
| kube-derive = "1.1.0" | ||||
| @ -67,6 +67,7 @@ bollard.workspace = true | ||||
| tar.workspace = true | ||||
| base64.workspace = true | ||||
| once_cell = "1.21.3" | ||||
| harmony-secret-derive = { version = "0.1.0", path = "../harmony_secret_derive" } | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| pretty_assertions.workspace = true | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								harmony/harmony.rlib
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								harmony/harmony.rlib
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										23
									
								
								harmony_secret/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								harmony_secret/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| [package] | ||||
| name = "harmony-secret" | ||||
| edition = "2024" | ||||
| version.workspace = true | ||||
| readme.workspace = true | ||||
| license.workspace = true | ||||
| 
 | ||||
| [dependencies] | ||||
| harmony-secret-derive = { version = "0.1.0", path = "../harmony_secret_derive" } | ||||
| serde = { version = "1.0.209", features = ["derive", "rc"] } | ||||
| serde_json = "1.0.127" | ||||
| thiserror.workspace = true | ||||
| lazy_static.workspace = true | ||||
| directories.workspace = true | ||||
| log.workspace = true | ||||
| infisical = "0.0.2" | ||||
| tokio.workspace = true | ||||
| async-trait.workspace = true | ||||
| http.workspace = true | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| pretty_assertions.workspace = true | ||||
| tempfile.workspace = true | ||||
							
								
								
									
										18
									
								
								harmony_secret/src/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								harmony_secret/src/config.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| use lazy_static::lazy_static; | ||||
| 
 | ||||
| lazy_static! { | ||||
|     pub static ref SECRET_NAMESPACE: String = | ||||
|         std::env::var("HARMONY_SECRET_NAMESPACE").expect("HARMONY_SECRET_NAMESPACE environment variable is required, it should contain the name of the project you are working on to access its secrets"); | ||||
|     pub static ref SECRET_STORE: Option<String> = | ||||
|         std::env::var("HARMONY_SECRET_STORE").ok(); | ||||
|     pub static ref INFISICAL_URL: Option<String> = | ||||
|         std::env::var("HARMONY_SECRET_INFISICAL_URL").ok(); | ||||
|     pub static ref INFISICAL_PROJECT_ID: Option<String> = | ||||
|         std::env::var("HARMONY_SECRET_INFISICAL_PROJECT_ID").ok(); | ||||
|     pub static ref INFISICAL_ENVIRONMENT: Option<String> = | ||||
|         std::env::var("HARMONY_SECRET_INFISICAL_ENVIRONMENT").ok(); | ||||
|     pub static ref INFISICAL_CLIENT_ID: Option<String> = | ||||
|         std::env::var("HARMONY_SECRET_INFISICAL_CLIENT_ID").ok(); | ||||
|     pub static ref INFISICAL_CLIENT_SECRET: Option<String> = | ||||
|         std::env::var("HARMONY_SECRET_INFISICAL_CLIENT_SECRET").ok(); | ||||
| } | ||||
							
								
								
									
										166
									
								
								harmony_secret/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								harmony_secret/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,166 @@ | ||||
| pub mod config; | ||||
| mod store; | ||||
| 
 | ||||
| use crate::config::SECRET_NAMESPACE; | ||||
| use async_trait::async_trait; | ||||
| use config::INFISICAL_CLIENT_ID; | ||||
| use config::INFISICAL_CLIENT_SECRET; | ||||
| use config::INFISICAL_ENVIRONMENT; | ||||
| use config::INFISICAL_PROJECT_ID; | ||||
| use config::INFISICAL_URL; | ||||
| use config::SECRET_STORE; | ||||
| use serde::{Serialize, de::DeserializeOwned}; | ||||
| use std::fmt; | ||||
| use store::InfisicalSecretStore; | ||||
| use store::LocalFileSecretStore; | ||||
| use thiserror::Error; | ||||
| use tokio::sync::OnceCell; | ||||
| 
 | ||||
| pub use harmony_secret_derive::Secret; | ||||
| 
 | ||||
| // The Secret trait remains the same.
 | ||||
| pub trait Secret: Serialize + DeserializeOwned + Sized { | ||||
|     const KEY: &'static str; | ||||
| } | ||||
| 
 | ||||
| // The error enum remains the same.
 | ||||
| #[derive(Debug, Error)] | ||||
| pub enum SecretStoreError { | ||||
|     #[error("Secret not found for key '{key}' in namespace '{namespace}'")] | ||||
|     NotFound { namespace: String, key: String }, | ||||
|     #[error("Failed to deserialize secret for key '{key}': {source}")] | ||||
|     Deserialization { | ||||
|         key: String, | ||||
|         source: serde_json::Error, | ||||
|     }, | ||||
|     #[error("Failed to serialize secret for key '{key}': {source}")] | ||||
|     Serialization { | ||||
|         key: String, | ||||
|         source: serde_json::Error, | ||||
|     }, | ||||
|     #[error("Underlying storage error: {0}")] | ||||
|     Store(#[from] Box<dyn std::error::Error + Send + Sync>), | ||||
| } | ||||
| 
 | ||||
| // The trait is now async!
 | ||||
| #[async_trait] | ||||
| pub trait SecretStore: fmt::Debug + Send + Sync { | ||||
|     async fn get_raw(&self, namespace: &str, key: &str) -> Result<Vec<u8>, SecretStoreError>; | ||||
|     async fn set_raw( | ||||
|         &self, | ||||
|         namespace: &str, | ||||
|         key: &str, | ||||
|         value: &[u8], | ||||
|     ) -> Result<(), SecretStoreError>; | ||||
| } | ||||
| 
 | ||||
| // Use OnceCell for async-friendly, one-time initialization.
 | ||||
| static SECRET_MANAGER: OnceCell<SecretManager> = OnceCell::const_new(); | ||||
| 
 | ||||
| /// Initializes and returns a reference to the global SecretManager.
 | ||||
| async fn get_secret_manager() -> &'static SecretManager { | ||||
|     SECRET_MANAGER.get_or_init(init_secret_manager).await | ||||
| } | ||||
| 
 | ||||
| /// The async initialization function for the SecretManager.
 | ||||
| async fn init_secret_manager() -> SecretManager { | ||||
|     let default_secret_score = "infisical".to_string(); | ||||
|     let store_type = SECRET_STORE.as_ref().unwrap_or(&default_secret_score); | ||||
| 
 | ||||
|     let store: Box<dyn SecretStore> = match store_type.as_str() { | ||||
|         "file" => Box::new(LocalFileSecretStore::default()), | ||||
|         "infisical" | _ => { | ||||
|             let store = InfisicalSecretStore::new( | ||||
|                 INFISICAL_URL.clone().expect("Infisical url must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_URL"), | ||||
|                 INFISICAL_PROJECT_ID.clone().expect("Infisical project id must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_PROJECT_ID"), | ||||
|                 INFISICAL_ENVIRONMENT.clone().expect("Infisical environment must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_ENVIRONMENT"), | ||||
|                 INFISICAL_CLIENT_ID.clone().expect("Infisical client id must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_CLIENT_ID"), | ||||
|                 INFISICAL_CLIENT_SECRET.clone().expect("Infisical client secret must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_CLIENT_SECRET"), | ||||
|             ) | ||||
|             .await | ||||
|             .expect("Failed to initialize Infisical secret store"); | ||||
|             Box::new(store) | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     SecretManager::new(SECRET_NAMESPACE.clone(), store) | ||||
| } | ||||
| 
 | ||||
| /// Manages the lifecycle of secrets, providing a simple static API.
 | ||||
| #[derive(Debug)] | ||||
| pub struct SecretManager { | ||||
|     namespace: String, | ||||
|     store: Box<dyn SecretStore>, | ||||
| } | ||||
| 
 | ||||
| impl SecretManager { | ||||
|     fn new(namespace: String, store: Box<dyn SecretStore>) -> Self { | ||||
|         Self { namespace, store } | ||||
|     } | ||||
| 
 | ||||
|     /// Retrieves and deserializes a secret.
 | ||||
|     pub async fn get<T: Secret>() -> Result<T, SecretStoreError> { | ||||
|         let manager = get_secret_manager().await; | ||||
|         let raw_value = manager.store.get_raw(&manager.namespace, T::KEY).await?; | ||||
|         serde_json::from_slice(&raw_value).map_err(|e| SecretStoreError::Deserialization { | ||||
|             key: T::KEY.to_string(), | ||||
|             source: e, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /// Serializes and stores a secret.
 | ||||
|     pub async fn set<T: Secret>(secret: &T) -> Result<(), SecretStoreError> { | ||||
|         let manager = get_secret_manager().await; | ||||
|         let raw_value = | ||||
|             serde_json::to_vec(secret).map_err(|e| SecretStoreError::Serialization { | ||||
|                 key: T::KEY.to_string(), | ||||
|                 source: e, | ||||
|             })?; | ||||
|         manager | ||||
|             .store | ||||
|             .set_raw(&manager.namespace, T::KEY, &raw_value) | ||||
|             .await | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use super::*; | ||||
|     use pretty_assertions::assert_eq; | ||||
|     use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
|     #[derive(Serialize, Deserialize, Debug, PartialEq)] | ||||
|     struct TestUserMeta { | ||||
|         labels: Vec<String>, | ||||
|     } | ||||
| 
 | ||||
|     #[derive(Secret, Serialize, Deserialize, Debug, PartialEq)] | ||||
|     struct TestSecret { | ||||
|         user: String, | ||||
|         password: String, | ||||
|         metadata: TestUserMeta, | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(secrete2etest)] | ||||
|     #[tokio::test] | ||||
|     async fn set_and_retrieve_secret() { | ||||
|         let secret = TestSecret { | ||||
|             user: String::from("user"), | ||||
|             password: String::from("password"), | ||||
|             metadata: TestUserMeta { | ||||
|                 labels: vec![ | ||||
|                     String::from("label1"), | ||||
|                     String::from("label2"), | ||||
|                     String::from( | ||||
|                         "some longet label with \" special @#%$)(udiojcia[]]] \"'asdij'' characters Nдs はにほへとちり าฟันพัฒนา yağız şoföre ç <20> <20> <20> <20> <20> <20> <20> <20> <20> <20> <20> <20> <20>  👩👩👧👦  /span>  👩👧👦 and why not emojis ", | ||||
|                     ), | ||||
|                 ], | ||||
|             }, | ||||
|         }; | ||||
| 
 | ||||
|         SecretManager::set(&secret).await.unwrap(); | ||||
|         let value = SecretManager::get::<TestSecret>().await.unwrap(); | ||||
| 
 | ||||
|         assert_eq!(value, secret); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										129
									
								
								harmony_secret/src/store/infisical.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								harmony_secret/src/store/infisical.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| use crate::{SecretStore, SecretStoreError}; | ||||
| use async_trait::async_trait; | ||||
| use infisical::{ | ||||
|     AuthMethod, InfisicalError, | ||||
|     client::Client, | ||||
|     secrets::{CreateSecretRequest, GetSecretRequest, UpdateSecretRequest}, | ||||
| }; | ||||
| use log::{info, warn}; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct InfisicalSecretStore { | ||||
|     client: Client, | ||||
|     project_id: String, | ||||
|     environment: String, | ||||
| } | ||||
| 
 | ||||
| impl InfisicalSecretStore { | ||||
|     /// Creates a new, authenticated Infisical client.
 | ||||
|     pub async fn new( | ||||
|         base_url: String, | ||||
|         project_id: String, | ||||
|         environment: String, | ||||
|         client_id: String, | ||||
|         client_secret: String, | ||||
|     ) -> Result<Self, InfisicalError> { | ||||
|         info!("INFISICAL_STORE: Initializing client for URL: {base_url}"); | ||||
| 
 | ||||
|         // The builder and login logic remains the same.
 | ||||
|         let mut client = Client::builder().base_url(base_url).build().await?; | ||||
|         let auth_method = AuthMethod::new_universal_auth(client_id, client_secret); | ||||
|         client.login(auth_method).await?; | ||||
| 
 | ||||
|         info!("INFISICAL_STORE: Client authenticated successfully."); | ||||
|         Ok(Self { | ||||
|             client, | ||||
|             project_id, | ||||
|             environment, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl SecretStore for InfisicalSecretStore { | ||||
|     async fn get_raw(&self, _environment: &str, key: &str) -> Result<Vec<u8>, SecretStoreError> { | ||||
|         let environment = &self.environment; | ||||
|         info!("INFISICAL_STORE: Getting key '{key}' from environment '{environment}'"); | ||||
| 
 | ||||
|         let request = GetSecretRequest::builder(key, &self.project_id, environment).build(); | ||||
| 
 | ||||
|         match self.client.secrets().get(request).await { | ||||
|             Ok(secret) => Ok(secret.secret_value.into_bytes()), | ||||
|             Err(e) => { | ||||
|                 // Correctly match against the actual InfisicalError enum.
 | ||||
|                 match e { | ||||
|                     // The specific case for a 404 Not Found error.
 | ||||
|                     InfisicalError::HttpError { status, .. } | ||||
|                         if status == http::StatusCode::NOT_FOUND => | ||||
|                     { | ||||
|                         Err(SecretStoreError::NotFound { | ||||
|                             namespace: environment.to_string(), | ||||
|                             key: key.to_string(), | ||||
|                         }) | ||||
|                     } | ||||
|                     // For all other errors, wrap them in our generic Store error.
 | ||||
|                     _ => Err(SecretStoreError::Store(Box::new(e))), | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async fn set_raw( | ||||
|         &self, | ||||
|         _environment: &str, | ||||
|         key: &str, | ||||
|         val: &[u8], | ||||
|     ) -> Result<(), SecretStoreError> { | ||||
|         info!( | ||||
|             "INFISICAL_STORE: Setting key '{key}' in environment '{}'", | ||||
|             self.environment | ||||
|         ); | ||||
|         let value_str = | ||||
|             String::from_utf8(val.to_vec()).map_err(|e| SecretStoreError::Store(Box::new(e)))?; | ||||
| 
 | ||||
|         // --- Upsert Logic ---
 | ||||
|         // First, attempt to update the secret.
 | ||||
|         let update_req = UpdateSecretRequest::builder(key, &self.project_id, &self.environment) | ||||
|             .secret_value(&value_str) | ||||
|             .build(); | ||||
| 
 | ||||
|         match self.client.secrets().update(update_req).await { | ||||
|             Ok(_) => { | ||||
|                 info!("INFISICAL_STORE: Successfully updated secret '{key}'."); | ||||
|                 Ok(()) | ||||
|             } | ||||
|             Err(e) => { | ||||
|                 // If the update failed, check if it was because the secret doesn't exist.
 | ||||
|                 match e { | ||||
|                     InfisicalError::HttpError { status, .. } | ||||
|                         if status == http::StatusCode::NOT_FOUND => | ||||
|                     { | ||||
|                         // The secret was not found, so we create it instead.
 | ||||
|                         warn!( | ||||
|                             "INFISICAL_STORE: Secret '{key}' not found for update, attempting to create it." | ||||
|                         ); | ||||
|                         let create_req = CreateSecretRequest::builder( | ||||
|                             key, | ||||
|                             &value_str, | ||||
|                             &self.project_id, | ||||
|                             &self.environment, | ||||
|                         ) | ||||
|                         .build(); | ||||
| 
 | ||||
|                         // Handle potential errors during creation.
 | ||||
|                         self.client | ||||
|                             .secrets() | ||||
|                             .create(create_req) | ||||
|                             .await | ||||
|                             .map_err(|create_err| SecretStoreError::Store(Box::new(create_err)))?; | ||||
| 
 | ||||
|                         info!("INFISICAL_STORE: Successfully created secret '{key}'."); | ||||
|                         Ok(()) | ||||
|                     } | ||||
|                     // Any other error during update is a genuine failure.
 | ||||
|                     _ => Err(SecretStoreError::Store(Box::new(e))), | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										105
									
								
								harmony_secret/src/store/local_file.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								harmony_secret/src/store/local_file.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,105 @@ | ||||
| use async_trait::async_trait; | ||||
| use log::info; | ||||
| use std::path::{Path, PathBuf}; | ||||
| 
 | ||||
| use crate::{SecretStore, SecretStoreError}; | ||||
| 
 | ||||
| #[derive(Debug, Default)] | ||||
| pub struct LocalFileSecretStore; | ||||
| 
 | ||||
| impl LocalFileSecretStore { | ||||
|     /// Helper to consistently generate the secret file path.
 | ||||
|     fn get_file_path(base_dir: &Path, ns: &str, key: &str) -> PathBuf { | ||||
|         base_dir.join(format!("{ns}_{key}.json")) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl SecretStore for LocalFileSecretStore { | ||||
|     async fn get_raw(&self, ns: &str, key: &str) -> Result<Vec<u8>, SecretStoreError> { | ||||
|         let data_dir = directories::BaseDirs::new() | ||||
|             .expect("Could not find a valid home directory") | ||||
|             .data_dir() | ||||
|             .join("harmony") | ||||
|             .join("secrets"); | ||||
| 
 | ||||
|         let file_path = Self::get_file_path(&data_dir, ns, key); | ||||
|         info!( | ||||
|             "LOCAL_STORE: Getting key '{key}' from namespace '{ns}' at {}", | ||||
|             file_path.display() | ||||
|         ); | ||||
| 
 | ||||
|         tokio::fs::read(&file_path) | ||||
|             .await | ||||
|             .map_err(|_| SecretStoreError::NotFound { | ||||
|                 namespace: ns.to_string(), | ||||
|                 key: key.to_string(), | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     async fn set_raw(&self, ns: &str, key: &str, val: &[u8]) -> Result<(), SecretStoreError> { | ||||
|         let data_dir = directories::BaseDirs::new() | ||||
|             .expect("Could not find a valid home directory") | ||||
|             .data_dir() | ||||
|             .join("harmony") | ||||
|             .join("secrets"); | ||||
| 
 | ||||
|         let file_path = Self::get_file_path(&data_dir, ns, key); | ||||
|         info!( | ||||
|             "LOCAL_STORE: Setting key '{key}' in namespace '{ns}' at {}", | ||||
|             file_path.display() | ||||
|         ); | ||||
| 
 | ||||
|         if let Some(parent_dir) = file_path.parent() { | ||||
|             tokio::fs::create_dir_all(parent_dir) | ||||
|                 .await | ||||
|                 .map_err(|e| SecretStoreError::Store(Box::new(e)))?; | ||||
|         } | ||||
| 
 | ||||
|         tokio::fs::write(&file_path, val) | ||||
|             .await | ||||
|             .map_err(|e| SecretStoreError::Store(Box::new(e))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use tempfile::tempdir; | ||||
| 
 | ||||
|     #[tokio::test] | ||||
|     async fn test_set_and_get_raw_successfully() { | ||||
|         let dir = tempdir().unwrap(); | ||||
|         let store = LocalFileSecretStore::default(); | ||||
|         let ns = "test-ns"; | ||||
|         let key = "test-key"; | ||||
|         let value = b"{\"data\":\"test-value\"}"; | ||||
| 
 | ||||
|         // To test the store directly, we override the base directory logic.
 | ||||
|         // For this test, we'll manually construct the path within our temp dir.
 | ||||
|         let file_path = LocalFileSecretStore::get_file_path(dir.path(), ns, key); | ||||
| 
 | ||||
|         // Manually write to the temp path to simulate the store's behavior
 | ||||
|         tokio::fs::create_dir_all(file_path.parent().unwrap()) | ||||
|             .await | ||||
|             .unwrap(); | ||||
|         tokio::fs::write(&file_path, value).await.unwrap(); | ||||
| 
 | ||||
|         // Now, test get_raw by reading from that same temp path (by mocking the path logic)
 | ||||
|         let retrieved_value = tokio::fs::read(&file_path).await.unwrap(); | ||||
|         assert_eq!(retrieved_value, value); | ||||
|     } | ||||
| 
 | ||||
|     #[tokio::test] | ||||
|     async fn test_get_raw_not_found() { | ||||
|         let dir = tempdir().unwrap(); | ||||
|         let ns = "test-ns"; | ||||
|         let key = "non-existent-key"; | ||||
| 
 | ||||
|         // We need to check if reading a non-existent file gives the correct error
 | ||||
|         let file_path = LocalFileSecretStore::get_file_path(dir.path(), ns, key); | ||||
|         let result = tokio::fs::read(&file_path).await; | ||||
| 
 | ||||
|         assert!(matches!(result, Err(_))); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										4
									
								
								harmony_secret/src/store/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								harmony_secret/src/store/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| mod infisical; | ||||
| mod local_file; | ||||
| pub use infisical::*; | ||||
| pub use local_file::*; | ||||
							
								
								
									
										8
									
								
								harmony_secret/test_harmony_secret_infisical.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								harmony_secret/test_harmony_secret_infisical.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| export HARMONY_SECRET_NAMESPACE=harmony_test_secrets | ||||
| export HARMONY_SECRET_INFISICAL_URL=http://localhost | ||||
| export HARMONY_SECRET_INFISICAL_PROJECT_ID=eb4723dc-eede-44d7-98cc-c8e0caf29ccb | ||||
| export HARMONY_SECRET_INFISICAL_ENVIRONMENT=dev | ||||
| export HARMONY_SECRET_INFISICAL_CLIENT_ID=dd16b07f-0e38-4090-a1d0-922de9f44d91 | ||||
| export HARMONY_SECRET_INFISICAL_CLIENT_SECRET=bd2ae054e7759b11ca2e908494196337cc800bab138cb1f59e8d9b15ca3f286f | ||||
| 
 | ||||
| cargo test | ||||
							
								
								
									
										13
									
								
								harmony_secret_derive/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								harmony_secret_derive/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| [package] | ||||
| name = "harmony-secret-derive" | ||||
| version = "0.1.0" | ||||
| edition = "2024" | ||||
| 
 | ||||
| [lib] | ||||
| proc-macro = true | ||||
| 
 | ||||
| [dependencies] | ||||
| quote = "1.0" | ||||
| proc-macro2 = "1.0" | ||||
| proc-macro-crate = "3.3" | ||||
| syn = "2.0" | ||||
							
								
								
									
										38
									
								
								harmony_secret_derive/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								harmony_secret_derive/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| use proc_macro::TokenStream; | ||||
| use proc_macro_crate::{FoundCrate, crate_name}; | ||||
| use quote::quote; | ||||
| use syn::{DeriveInput, Ident, parse_macro_input}; | ||||
| 
 | ||||
| #[proc_macro_derive(Secret)] | ||||
| pub fn derive_secret(input: TokenStream) -> TokenStream { | ||||
|     let input = parse_macro_input!(input as DeriveInput); | ||||
|     let struct_ident = &input.ident; | ||||
| 
 | ||||
|     // The key for the secret will be the stringified name of the struct itself.
 | ||||
|     // e.g., `struct OKDClusterSecret` becomes key `"OKDClusterSecret"`.
 | ||||
|     let key = struct_ident.to_string(); | ||||
| 
 | ||||
|     // Find the path to the `harmony_secret` crate.
 | ||||
|     let secret_crate_path = match crate_name("harmony-secret") { | ||||
|         Ok(FoundCrate::Itself) => quote!(crate), | ||||
|         Ok(FoundCrate::Name(name)) => { | ||||
|             let ident = Ident::new(&name, proc_macro2::Span::call_site()); | ||||
|             quote!(::#ident) | ||||
|         } | ||||
|         Err(e) => { | ||||
|             return syn::Error::new(proc_macro2::Span::call_site(), e.to_string()) | ||||
|                 .to_compile_error() | ||||
|                 .into(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // The generated code now implements `Secret` for the struct itself.
 | ||||
|     // The struct must also derive `Serialize` and `Deserialize` for this to be useful.
 | ||||
|     let expanded = quote! { | ||||
|         impl #secret_crate_path::Secret for #struct_ident { | ||||
|             const KEY: &'static str = #key; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     TokenStream::from(expanded) | ||||
| } | ||||
| @ -12,7 +12,7 @@ env_logger = { workspace = true } | ||||
| yaserde = { git = "https://github.com/jggc/yaserde.git" } | ||||
| yaserde_derive = { git = "https://github.com/jggc/yaserde.git" } | ||||
| xml-rs = "0.8" | ||||
| thiserror = "1.0" | ||||
| thiserror.workspace = true | ||||
| async-trait = { workspace = true } | ||||
| tokio = { workspace = true } | ||||
| uuid = { workspace = true } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user