diff --git a/.fluentci/e2e.ts b/.fluentci/e2e.ts index af439e1..0b810a1 100644 --- a/.fluentci/e2e.ts +++ b/.fluentci/e2e.ts @@ -140,11 +140,10 @@ const queries = [ "proto", ]; -const engine = await dag +const engine = dag .pipeline("fluentci-service") .withExec(["fluentci-engine", "serve"]) - .asService("fluentci-engine") - .id(); + .asService("fluentci-engine"); let query = dag .pkgx() diff --git a/Cargo.lock b/Cargo.lock index be7b877..f8b4c06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ dependencies = [ "futures-sink", "futures-task", "futures-util", - "log", + "log 0.4.21", "once_cell", "parking_lot", "pin-project-lite", @@ -64,7 +64,7 @@ dependencies = [ "actix-web", "derive_more", "futures-util", - "log", + "log 0.4.21", "once_cell", "smallvec", ] @@ -96,10 +96,10 @@ dependencies = [ "itoa", "language-tags", "local-channel", - "mime", - "percent-encoding", + "mime 0.3.17", + "percent-encoding 2.3.1", "pin-project-lite", - "rand", + "rand 0.8.5", "sha1", "smallvec", "tokio", @@ -205,8 +205,8 @@ dependencies = [ "futures-util", "itoa", "language-tags", - "log", - "mime", + "log 0.4.21", + "mime 0.3.17", "once_cell", "pin-project-lite", "regex", @@ -216,7 +216,7 @@ dependencies = [ "smallvec", "socket2", "time", - "url", + "url 2.5.0", ] [[package]] @@ -282,7 +282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.12", "once_cell", "version_check", "zerocopy", @@ -356,7 +356,7 @@ name = "archive" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -395,7 +395,7 @@ dependencies = [ "handlebars", "http 1.1.0", "indexmap 2.2.5", - "mime", + "mime 0.3.17", "multer", "num-traits", "once_cell", @@ -469,6 +469,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -493,9 +502,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -538,7 +547,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand", + "fastrand 2.0.1", "hex", "http 0.2.12", "hyper 0.14.28", @@ -576,10 +585,10 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand", + "fastrand 2.0.1", "http 0.2.12", "http-body 0.4.6", - "percent-encoding", + "percent-encoding 2.3.1", "pin-project-lite", "tracing", "uuid", @@ -606,18 +615,18 @@ dependencies = [ "aws-smithy-xml", "aws-types", "bytes", - "fastrand", + "fastrand 2.0.1", "hex", - "hmac", + "hmac 0.12.1", "http 0.2.12", "http-body 0.4.6", "lru", "once_cell", - "percent-encoding", + "percent-encoding 2.3.1", "regex-lite", - "sha2", + "sha2 0.10.8", "tracing", - "url", + "url 2.5.0", ] [[package]] @@ -702,14 +711,14 @@ dependencies = [ "crypto-bigint 0.5.5", "form_urlencoded", "hex", - "hmac", + "hmac 0.12.1", "http 0.2.12", "http 1.1.0", "once_cell", "p256", - "percent-encoding", + "percent-encoding 2.3.1", "ring 0.17.8", - "sha2", + "sha2 0.10.8", "subtle", "time", "tracing", @@ -741,10 +750,10 @@ dependencies = [ "hex", "http 0.2.12", "http-body 0.4.6", - "md-5", + "md-5 0.10.6", "pin-project-lite", "sha1", - "sha2", + "sha2 0.10.8", "tracing", ] @@ -774,7 +783,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "once_cell", - "percent-encoding", + "percent-encoding 2.3.1", "pin-project-lite", "pin-utils", "tracing", @@ -810,7 +819,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "bytes", - "fastrand", + "fastrand 2.0.1", "h2", "http 0.2.12", "http-body 0.4.6", @@ -905,8 +914,8 @@ dependencies = [ "itoa", "matchit", "memchr", - "mime", - "percent-encoding", + "mime 0.3.17", + "percent-encoding 2.3.1", "pin-project-lite", "rustversion", "serde", @@ -927,12 +936,77 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "mime", + "mime 0.3.17", "rustversion", "tower-layer", "tower-service", ] +[[package]] +name = "azure_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6dd496e30132b9403c478ea87121834669fa1a307ee2d0b6721c3aca47cccd" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bytes", + "dyn-clone", + "futures", + "getrandom 0.2.12", + "http-types", + "log 0.4.21", + "paste", + "pin-project", + "rand 0.8.5", + "reqwest 0.11.26", + "rustc_version", + "serde", + "serde_json", + "time", + "url 2.5.0", + "uuid", +] + +[[package]] +name = "azure_identity" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e5c1eebcb9f425b9d8faecda921e782c66e15af5050d53f44027c66d5d7ca9" +dependencies = [ + "async-lock", + "async-trait", + "azure_core", + "base64 0.13.1", + "fix-hidden-lifetime-bug", + "futures", + "log 0.4.21", + "oauth2", + "pin-project", + "serde", + "serde_json", + "time", + "url 2.5.0", + "uuid", +] + +[[package]] +name = "azure_security_keyvault" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4df4abcfcd3585a4e45b0f16c3b10dbf3874c86467be4a5512e1953fbc4f50f" +dependencies = [ + "async-trait", + "azure_core", + "base64 0.13.1", + "const_format", + "futures", + "serde", + "serde_json", + "time", + "url 2.5.0", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -1015,6 +1089,15 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1139,7 +1222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20e5695565f0cd7106bc3c7170323597540e772bb73e0be2cd2c662a0f8fa4ca" dependencies = [ "ambient-authority", - "rand", + "rand 0.8.5", ] [[package]] @@ -1176,7 +1259,7 @@ checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" dependencies = [ "heck", "indexmap 1.9.3", - "log", + "log 0.4.21", "proc-macro2", "quote", "serde", @@ -1207,7 +1290,7 @@ name = "chmod" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -1265,7 +1348,7 @@ dependencies = [ "jsonwebtoken", "lazy_static", "pem", - "percent-encoding", + "percent-encoding 2.3.1", "reqwest 0.11.26", "ring 0.16.20", "serde", @@ -1288,6 +1371,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -1300,7 +1403,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ - "percent-encoding", + "percent-encoding 2.3.1", "time", "version_check", ] @@ -1363,7 +1466,7 @@ dependencies = [ "cranelift-isle", "gimli", "hashbrown 0.14.3", - "log", + "log 0.4.21", "regalloc2", "smallvec", "target-lexicon", @@ -1410,7 +1513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d555819f3a49c01826ce5bf0f3e52a4e17be9c4ee09381d6a1d88549793f3c" dependencies = [ "cranelift-codegen", - "log", + "log 0.4.21", "smallvec", "target-lexicon", ] @@ -1442,7 +1545,7 @@ dependencies = [ "cranelift-entity", "cranelift-frontend", "itertools 0.10.5", - "log", + "log 0.4.21", "smallvec", "wasmparser 0.118.2", "wasmtime-types", @@ -1507,7 +1610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -1518,7 +1621,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1532,6 +1635,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "darling" version = "0.20.8" @@ -1593,6 +1706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -1613,7 +1727,16 @@ name = "devbox" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", ] [[package]] @@ -1622,7 +1745,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -1655,6 +1778,16 @@ dependencies = [ "dirs-sys 0.4.1", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -1695,6 +1828,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "ecdsa" version = "0.14.8" @@ -1722,12 +1861,12 @@ dependencies = [ "base16ct", "crypto-bigint 0.4.9", "der", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1747,7 +1886,7 @@ name = "env" version = "0.1.0" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -1786,12 +1925,12 @@ dependencies = [ "libc", "serde", "serde_json", - "sha2", + "sha2 0.10.8", "toml 0.8.12", "tracing", "tracing-subscriber", "ureq", - "url", + "url 2.5.0", "uuid", "wasmtime", "wasmtime-wasi", @@ -1878,6 +2017,15 @@ dependencies = [ "ascii_utils", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -1901,10 +2049,30 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fix-hidden-lifetime-bug" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ae9c2016a663983d4e40a9ff967d6dcac59819672f0b47f2b17574e99c33c8" +dependencies = [ + "fix-hidden-lifetime-bug-proc_macros", +] + +[[package]] +name = "fix-hidden-lifetime-bug-proc_macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c81935e123ab0741c4c4f0d9b8377e5fb21d3de7e062fa4b1263b1fbcba1ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "flate2" version = "1.0.28" @@ -1920,18 +2088,19 @@ name = "flox" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] name = "fluentci-common" -version = "0.1.8" +version = "0.2.0" dependencies = [ "anyhow", "dirs 5.0.1", "fluentci-core", "fluentci-ext", - "fluentci-types 0.1.6", + "fluentci-secrets", + "fluentci-types 0.1.7", "regex", "sha256", "uuid", @@ -1939,14 +2108,17 @@ dependencies = [ [[package]] name = "fluentci-core" -version = "0.2.1" +version = "0.3.0" dependencies = [ "anyhow", "chrono", "dirs 5.0.1", "fluentci-ext", "fluentci-logging", - "fluentci-types 0.1.6", + "fluentci-secrets", + "fluentci-types 0.1.7", + "hex", + "hmac 0.12.1", "opentelemetry", "opentelemetry-jaeger", "opentelemetry-otlp", @@ -1954,11 +2126,14 @@ dependencies = [ "opentelemetry_sdk", "owo-colors", "serde_yaml", + "sha2 0.10.8", + "tokio", + "uuid", ] [[package]] name = "fluentci-engine" -version = "0.3.1" +version = "0.4.0" dependencies = [ "anyhow", "clap", @@ -1984,13 +2159,13 @@ dependencies = [ "cloud-storage", "dirs 5.0.1", "fluentci-logging", - "fluentci-types 0.1.6", + "fluentci-types 0.1.7", "futures", "md5", "mime_guess", "owo-colors", "regex", - "sha2", + "sha2 0.10.8", "sha256", "tokio", "users", @@ -1998,7 +2173,7 @@ dependencies = [ [[package]] name = "fluentci-graphql" -version = "0.2.1" +version = "0.3.0" dependencies = [ "anyhow", "async-graphql", @@ -2007,7 +2182,8 @@ dependencies = [ "fluentci-common", "fluentci-core", "fluentci-ext", - "fluentci-types 0.1.6", + "fluentci-secrets", + "fluentci-types 0.1.7", "regex", "sha256", "tokio", @@ -2038,16 +2214,40 @@ dependencies = [ [[package]] name = "fluentci-pdk" -version = "0.1.13" +version = "0.2.0" dependencies = [ "extism-pdk", - "fluentci-types 0.1.6", + "fluentci-types 0.1.7", + "serde", +] + +[[package]] +name = "fluentci-secrets" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "azure_core", + "azure_identity", + "azure_security_keyvault", + "base64 0.21.7", + "fluentci-types 0.1.7", + "futures", + "google-secretmanager1", + "reqwest 0.11.26", + "rusoto_core", + "rusoto_credential", + "rusoto_secretsmanager", "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", ] [[package]] name = "fluentci-server" -version = "0.2.1" +version = "0.3.0" dependencies = [ "actix-cors", "actix-web", @@ -2064,14 +2264,16 @@ dependencies = [ [[package]] name = "fluentci-shared" -version = "0.1.9" +version = "0.2.0" dependencies = [ + "anyhow", "extism", "extism-pdk", "fluentci-common", "fluentci-core", "fluentci-ext", - "fluentci-types 0.1.6", + "fluentci-secrets", + "fluentci-types 0.1.7", "serde", "uuid", ] @@ -2087,7 +2289,7 @@ dependencies = [ [[package]] name = "fluentci-types" -version = "0.1.6" +version = "0.1.7" dependencies = [ "serde", "serde_yaml", @@ -2105,7 +2307,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "percent-encoding", + "percent-encoding 2.3.1", ] [[package]] @@ -2167,6 +2369,21 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -2246,6 +2463,17 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "888123007db34fbff15b5a347d46364dfbad531d6cb43de52cc0b62558f570e2" +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -2253,8 +2481,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2273,7 +2503,7 @@ name = "git" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -2282,6 +2512,26 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "google-secretmanager1" +version = "4.0.1+20220226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f559e1b92f35b35024adc7307db958ab65319f570732a9acdd1d1e99e3fb6ab8" +dependencies = [ + "http 0.2.12", + "hyper 0.14.28", + "hyper-rustls 0.23.2", + "itertools 0.10.5", + "mime 0.2.6", + "serde", + "serde_derive", + "serde_json", + "tokio", + "tower-service", + "url 1.7.2", + "yup-oauth2", +] + [[package]] name = "group" version = "0.12.1" @@ -2289,7 +2539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2318,7 +2568,7 @@ version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" dependencies = [ - "log", + "log 0.4.21", "pest", "pest_derive", "serde", @@ -2331,7 +2581,7 @@ name = "hash" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -2386,13 +2636,23 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2400,7 +2660,7 @@ name = "http" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -2459,6 +2719,26 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-types" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" +dependencies = [ + "anyhow", + "async-channel", + "base64 0.13.1", + "futures-lite", + "infer", + "pin-project-lite", + "rand 0.7.3", + "serde", + "serde_json", + "serde_qs", + "serde_urlencoded", + "url 2.5.0", +] + [[package]] name = "httparse" version = "1.8.0" @@ -2514,6 +2794,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http 0.2.12", + "hyper 0.14.28", + "log 0.4.21", + "rustls 0.20.9", + "rustls-native-certs", + "tokio", + "tokio-rustls 0.23.4", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -2523,7 +2818,7 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.28", - "log", + "log 0.4.21", "rustls 0.21.10", "rustls-native-certs", "tokio", @@ -2614,6 +2909,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -2645,6 +2951,21 @@ dependencies = [ "serde", ] +[[package]] +name = "infer" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "integer-encoding" version = "3.0.4" @@ -2705,7 +3026,7 @@ checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" dependencies = [ "anyhow", "ittapi-sys", - "log", + "log 0.4.21", ] [[package]] @@ -2817,6 +3138,15 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.21", +] + [[package]] name = "log" version = "0.4.21" @@ -2873,6 +3203,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "matchit" version = "0.7.3" @@ -2885,6 +3221,17 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2892,7 +3239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -2925,6 +3272,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] + [[package]] name = "mime" version = "0.3.17" @@ -2937,7 +3293,7 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" dependencies = [ - "mime", + "mime 0.3.17", "unicase", ] @@ -2957,8 +3313,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "log", - "wasi", + "log 0.4.21", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -2967,7 +3323,7 @@ name = "mise" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -2991,9 +3347,9 @@ dependencies = [ "futures-util", "http 1.1.0", "httparse", - "log", + "log 0.4.21", "memchr", - "mime", + "mime 0.3.17", "spin 0.9.8", "version_check", ] @@ -3003,8 +3359,8 @@ name = "nix" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", - "fluentci-types 0.1.6", + "fluentci-pdk 0.2.0", + "fluentci-types 0.1.7", ] [[package]] @@ -3062,6 +3418,34 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom 0.2.12", + "http 0.2.12", + "rand 0.8.5", + "serde", + "serde_json", + "serde_path_to_error", + "sha2 0.10.8", + "thiserror", + "url 2.5.0", +] + [[package]] name = "object" version = "0.32.2" @@ -3080,6 +3464,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl-probe" version = "0.1.5" @@ -3204,8 +3594,8 @@ dependencies = [ "once_cell", "opentelemetry", "ordered-float 4.2.0", - "percent-encoding", - "rand", + "percent-encoding 2.3.1", + "rand 0.8.5", "thiserror", "tokio", "tokio-stream", @@ -3267,9 +3657,15 @@ checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ "ecdsa", "elliptic-curve", - "sha2", + "sha2 0.10.8", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.1" @@ -3310,6 +3706,12 @@ dependencies = [ "regex", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3358,7 +3760,7 @@ checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" dependencies = [ "once_cell", "pest", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -3398,7 +3800,7 @@ name = "pixi" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -3422,7 +3824,7 @@ name = "pkgx" version = "0.1.3" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -3504,7 +3906,7 @@ name = "proto" version = "0.1.0" dependencies = [ "extism-pdk", - "fluentci-pdk 0.1.13", + "fluentci-pdk 0.2.0", ] [[package]] @@ -3525,6 +3927,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -3532,8 +3947,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -3543,7 +3968,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -3552,7 +3986,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.12", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -3590,7 +4033,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom", + "getrandom 0.2.12", "libredox", "thiserror", ] @@ -3602,7 +4045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" dependencies = [ "hashbrown 0.13.2", - "log", + "log 0.4.21", "rustc-hash", "slice-group-by", "smallvec", @@ -3676,10 +4119,10 @@ dependencies = [ "hyper-rustls 0.24.2", "ipnet", "js-sys", - "log", - "mime", + "log 0.4.21", + "mime 0.3.17", "once_cell", - "percent-encoding", + "percent-encoding 2.3.1", "pin-project-lite", "rustls 0.21.10", "rustls-native-certs", @@ -3693,7 +4136,7 @@ dependencies = [ "tokio-rustls 0.24.1", "tokio-util", "tower-service", - "url", + "url 2.5.0", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", @@ -3721,10 +4164,10 @@ dependencies = [ "hyper-util", "ipnet", "js-sys", - "log", - "mime", + "log 0.4.21", + "mime 0.3.17", "once_cell", - "percent-encoding", + "percent-encoding 2.3.1", "pin-project-lite", "rustls 0.22.3", "rustls-pemfile 2.1.2", @@ -3736,7 +4179,7 @@ dependencies = [ "tokio", "tokio-rustls 0.25.0", "tower-service", - "url", + "url 2.5.0", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -3751,7 +4194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint 0.4.9", - "hmac", + "hmac 0.12.1", "zeroize", ] @@ -3778,7 +4221,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.12", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -3807,6 +4250,89 @@ dependencies = [ "serde", ] +[[package]] +name = "rusoto_core" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bytes", + "crc32fast", + "futures", + "http 0.2.12", + "hyper 0.14.28", + "hyper-rustls 0.23.2", + "lazy_static", + "log 0.4.21", + "rusoto_credential", + "rusoto_signature", + "rustc_version", + "serde", + "serde_json", + "tokio", + "xml-rs", +] + +[[package]] +name = "rusoto_credential" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05" +dependencies = [ + "async-trait", + "chrono", + "dirs-next", + "futures", + "hyper 0.14.28", + "serde", + "serde_json", + "shlex", + "tokio", + "zeroize", +] + +[[package]] +name = "rusoto_secretsmanager" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3331ce698e92491f53bf3abad5bb2bc3f72e9c8794a89d0137de1bbb3f3e3b" +dependencies = [ + "async-trait", + "bytes", + "futures", + "rusoto_core", + "serde", + "serde_json", +] + +[[package]] +name = "rusoto_signature" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272" +dependencies = [ + "base64 0.13.1", + "bytes", + "chrono", + "digest 0.9.0", + "futures", + "hex", + "hmac 0.11.0", + "http 0.2.12", + "hyper 0.14.28", + "log 0.4.21", + "md-5 0.9.1", + "percent-encoding 2.3.1", + "pin-project-lite", + "rusoto_credential", + "rustc_version", + "serde", + "sha2 0.9.9", + "tokio", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3843,13 +4369,25 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log 0.4.21", + "ring 0.16.20", + "sct", + "webpki", +] + [[package]] name = "rustls" version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ - "log", + "log 0.4.21", "ring 0.17.8", "rustls-webpki 0.101.7", "sct", @@ -3861,7 +4399,7 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ - "log", + "log 0.4.21", "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.2", @@ -3881,6 +4419,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +dependencies = [ + "base64 0.13.1", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -3964,6 +4511,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "sec1" version = "0.3.0" @@ -4038,6 +4591,27 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" +dependencies = [ + "percent-encoding 2.3.1", + "serde", + "thiserror", +] + [[package]] name = "serde_spanned" version = "0.6.5" @@ -4080,7 +4654,20 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", ] [[package]] @@ -4091,7 +4678,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -4103,7 +4690,7 @@ dependencies = [ "async-trait", "bytes", "hex", - "sha2", + "sha2 0.10.8", "tokio", ] @@ -4125,6 +4712,12 @@ dependencies = [ "dirs 4.0.0", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4140,8 +4733,8 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest", - "rand_core", + "digest 0.10.7", + "rand_core 0.6.4", ] [[package]] @@ -4338,7 +4931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.1", "rustix", "windows-sys 0.52.0", ] @@ -4405,7 +4998,7 @@ checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" dependencies = [ "byteorder", "integer-encoding", - "log", + "log 0.4.21", "ordered-float 2.10.1", "threadpool", ] @@ -4418,7 +5011,9 @@ checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -4496,6 +5091,17 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.9", + "tokio", + "webpki", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -4623,7 +5229,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.28", "hyper-timeout", - "percent-encoding", + "percent-encoding 2.3.1", "pin-project", "prost", "tokio", @@ -4645,7 +5251,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -4672,7 +5278,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", + "log 0.4.21", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -4705,7 +5311,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "log", + "log 0.4.21", "once_cell", "tracing-core", ] @@ -4834,15 +5440,26 @@ checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" dependencies = [ "base64 0.21.7", "flate2", - "log", + "log 0.4.21", "once_cell", "rustls 0.22.3", "rustls-pki-types", "rustls-webpki 0.102.2", - "url", + "url 2.5.0", "webpki-roots 0.26.1", ] +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + [[package]] name = "url" version = "2.5.0" @@ -4850,8 +5467,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna", - "percent-encoding", + "idna 0.5.0", + "percent-encoding 2.3.1", + "serde", ] [[package]] @@ -4867,7 +5485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" dependencies = [ "libc", - "log", + "log 0.4.21", ] [[package]] @@ -4876,8 +5494,8 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ - "getrandom", - "rand", + "getrandom 0.2.12", + "rand 0.8.5", "uuid-macro-internal", ] @@ -4898,6 +5516,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vault" +version = "0.1.0" +dependencies = [ + "anyhow", + "extism-pdk", + "fluentci-pdk 0.2.0", +] + [[package]] name = "version_check" version = "0.9.4" @@ -4910,6 +5537,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "want" version = "0.3.1" @@ -4919,6 +5552,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4959,7 +5598,7 @@ dependencies = [ "cap-rand", "cap-std", "io-extras", - "log", + "log 0.4.21", "rustix", "thiserror", "tracing", @@ -4985,7 +5624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", - "log", + "log 0.4.21", "once_cell", "proc-macro2", "quote", @@ -5111,7 +5750,7 @@ dependencies = [ "fxprof-processed-profile", "indexmap 2.2.5", "libc", - "log", + "log 0.4.21", "object", "once_cell", "paste", @@ -5154,11 +5793,11 @@ dependencies = [ "base64 0.21.7", "bincode", "directories-next", - "log", + "log 0.4.21", "rustix", "serde", "serde_derive", - "sha2", + "sha2 0.10.8", "toml 0.5.11", "windows-sys 0.52.0", "zstd 0.11.2+zstd.1.5.2", @@ -5200,7 +5839,7 @@ dependencies = [ "cranelift-native", "cranelift-wasm", "gimli", - "log", + "log 0.4.21", "object", "target-lexicon", "thiserror", @@ -5236,7 +5875,7 @@ dependencies = [ "cranelift-entity", "gimli", "indexmap 2.2.5", - "log", + "log 0.4.21", "object", "serde", "serde_derive", @@ -5277,7 +5916,7 @@ dependencies = [ "cpp_demangle", "gimli", "ittapi", - "log", + "log 0.4.21", "object", "rustc-demangle", "rustix", @@ -5326,7 +5965,7 @@ dependencies = [ "encoding_rs", "indexmap 2.2.5", "libc", - "log", + "log 0.4.21", "mach", "memfd", "memoffset", @@ -5388,14 +6027,14 @@ dependencies = [ "io-extras", "io-lifetimes", "libc", - "log", + "log 0.4.21", "once_cell", "rustix", "system-interface", "thiserror", "tokio", "tracing", - "url", + "url 2.5.0", "wasi-cap-std-sync", "wasi-common", "wasmtime", @@ -5479,6 +6118,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + [[package]] name = "webpki-roots" version = "0.25.4" @@ -5781,7 +6430,7 @@ dependencies = [ "anyhow", "id-arena", "indexmap 2.2.5", - "log", + "log 0.4.21", "semver", "serde", "serde_derive", @@ -5796,17 +6445,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ "anyhow", - "log", + "log 0.4.21", "thiserror", "wast 35.0.2", ] +[[package]] +name = "xml-rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" + [[package]] name = "xmlparser" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "yup-oauth2" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98748970d2ddf05253e6525810d989740334aa7509457864048a829902db76f3" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.13.1", + "futures", + "http 0.2.12", + "hyper 0.14.28", + "hyper-rustls 0.23.2", + "itertools 0.10.5", + "log 0.4.21", + "percent-encoding 2.3.1", + "rustls 0.20.9", + "rustls-pemfile 0.3.0", + "seahash", + "serde", + "serde_json", + "time", + "tokio", + "tower-service", + "url 2.5.0", +] + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/README.md b/README.md index f8460bb..64b65e8 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ FluentCI Engine is a programmable CI/CD engine that is designed to be simple, fl - [x] Flexible - [x] No containerization or virtualization - [x] Built-in support for Nix, Pkgx, Devbox, Flox, Devenv, Envhub, Mise and Pixi +- [x] Built-in support for Secrets (backends: Google Secret Manager, AWS Secrets Manager, Azure Key Vault and HashiCorp Vault) +- [x] Built-in support for Services - [x] Cache support (backends: local, S3, GCS, R2) - [x] SDK for writing pipelines in TypeScript, see [@fluentci/sdk](./sdk/typescript) - [x] GraphQL API, see [API Documentation](./docs/api.md) diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a403b49..a18d28c 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -8,7 +8,7 @@ license = "MPL-2.0" name = "fluentci-engine" readme = "../../README.md" repository = "https://github.com/fluentci-io/fluentci-engine" -version = "0.3.1" +version = "0.4.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,10 +16,10 @@ version = "0.3.1" anyhow = "1.0.81" clap = "3.2.20" extism = "1.2.0" -fluentci-core = {path = "../core", version = "0.2.1"} +fluentci-core = {path = "../core", version = "0.3.0"} fluentci-ext = {path = "../ext", version = "0.2.0"} -fluentci-server = {path = "../server", version = "0.2.1"} -fluentci-shared = {path = "../shared", version = "0.1.9"} +fluentci-server = {path = "../server", version = "0.3.0"} +fluentci-shared = {path = "../shared", version = "0.2.0"} get-port = "4.0.0" md5 = "0.7.0" regex = "1.10.3" diff --git a/crates/cli/src/cmd/call.rs b/crates/cli/src/cmd/call.rs index fab4bf3..fcfbcfa 100644 --- a/crates/cli/src/cmd/call.rs +++ b/crates/cli/src/cmd/call.rs @@ -99,6 +99,29 @@ pub fn call(module: &str, command: &str) { .with_function("as_service", [PTR], [PTR], user_data.clone(), as_service) .with_function("with_service", [PTR], [], user_data.clone(), with_service) .with_function("wait_on", [PTR], [], user_data.clone(), wait_on) + .with_function( + "add_secretmanager", + [PTR], + [PTR], + user_data.clone(), + add_secretmanager, + ) + .with_function("get_secret", [PTR], [PTR], user_data.clone(), get_secret) + .with_function("set_secret", [PTR], [PTR], user_data.clone(), set_secret) + .with_function( + "with_secret_variable", + [PTR], + [], + user_data.clone(), + with_secret_variable, + ) + .with_function( + "get_secret_plaintext", + [PTR], + [PTR], + user_data.clone(), + get_secret_plaintext, + ) .build() .unwrap(); diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index e783ee6..2ec3b11 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -7,16 +7,17 @@ keywords = ["nix", "environment", "ci", "wasm", "devops"] license = "MPL-2.0" name = "fluentci-common" repository = "https://github.com/fluentci-io/fluentci-engine" -version = "0.1.8" +version = "0.2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0.81" dirs = "5.0.1" -fluentci-core = {path = "../core", version = "0.2.1"} +fluentci-core = {path = "../core", version = "0.3.0"} fluentci-ext = {path = "../ext", version = "0.2.0"} -fluentci-types = {path = "../types", version = "0.1.6"} +fluentci-secrets = {path = "../secrets", version = "0.1.0"} +fluentci-types = {path = "../types", version = "0.1.7"} regex = "1.10.4" sha256 = "1.5.0" uuid = {version = "1.8.0", features = [ diff --git a/crates/common/src/cache.rs b/crates/common/src/cache.rs index 982230d..12fedaf 100644 --- a/crates/common/src/cache.rs +++ b/crates/common/src/cache.rs @@ -30,13 +30,13 @@ pub fn cache(graph: Arc>, key: &str) -> Result { key.into(), vec![], Arc::new(Box::new(CacheExt::default())), - )); + ))?; graph.execute(GraphCommand::AddVolume( id.clone(), "cache".into(), key.into(), - )); + ))?; let cache = Cache { id, diff --git a/crates/common/src/common.rs b/crates/common/src/common.rs index a978959..b7be59f 100644 --- a/crates/common/src/common.rs +++ b/crates/common/src/common.rs @@ -10,14 +10,15 @@ use fluentci_ext::cache::Cache as CacheExt; use fluentci_ext::Extension; use fluentci_ext::{archive::tar::czvf::TarCzvf as TarCzvfExt, runner::Runner}; use fluentci_ext::{archive::zip::Zip as ZipExt, pkgx::Pkgx as PkgxExt}; -use fluentci_types::{file::File, service::Service, Output}; +use fluentci_secrets::Provider; +use fluentci_types::{file::File, secret::Secret, service::Service, Output}; use uuid::Uuid; pub fn with_exec( graph: Arc>, args: Vec, ext: Arc>, -) { +) -> Result<(), Error> { let mut graph = graph.lock().unwrap(); let id = Uuid::new_v4().to_string(); @@ -32,13 +33,14 @@ pub fn with_exec( args.join(" "), deps, ext, - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } + Ok(()) } pub fn with_workdir( @@ -71,12 +73,12 @@ pub fn with_workdir( path, deps, ext, - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } graph.execute_vertex(&id)?; @@ -104,11 +106,11 @@ pub fn with_cache(graph: Arc>, cache_id: String, path: String) -> R cache_key_path, deps, Arc::new(Box::new(CacheExt::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; graph.execute_vertex(&id)?; graph.runner = runner; @@ -138,11 +140,11 @@ pub fn with_file(graph: Arc>, file_id: String, path: String) -> Res copy_file, deps, Arc::new(Box::new(Runner::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; graph.execute_vertex(&id)?; @@ -158,7 +160,7 @@ pub fn stdout( rx: Arc>>, ) -> Result { let mut graph = graph.lock().unwrap(); - graph.execute(GraphCommand::Execute(Output::Stdout)); + graph.execute(GraphCommand::Execute(Output::Stdout))?; let rx = rx.lock().unwrap(); let (stdout, code) = rx.recv().unwrap(); @@ -176,7 +178,7 @@ pub fn stderr( rx: Arc>>, ) -> Result { let mut graph = graph.lock().unwrap(); - graph.execute(GraphCommand::Execute(Output::Stderr)); + graph.execute(GraphCommand::Execute(Output::Stderr))?; let rx = rx.lock().unwrap(); let (stderr, code) = rx.recv().unwrap(); @@ -204,11 +206,11 @@ pub fn zip(graph: Arc>, path: String) -> Result { path.clone(), vec![dep_id], Arc::new(Box::new(ZipExt::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; graph.execute_vertex(&id)?; @@ -230,7 +232,7 @@ pub fn zip(graph: Arc>, path: String) -> Result { id, "file".into(), file.path.clone(), - )); + ))?; Ok(file) } @@ -249,11 +251,11 @@ pub fn tar_czvf(graph: Arc>, path: String) -> Result { path.clone(), vec![dep_id], Arc::new(Box::new(TarCzvfExt::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; graph.execute_vertex(&id)?; @@ -275,7 +277,7 @@ pub fn tar_czvf(graph: Arc>, path: String) -> Result { id, "file".into(), file.path.clone(), - )); + ))?; Ok(file) } @@ -298,12 +300,12 @@ pub fn as_service(graph: Arc>, name: String) -> Result 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; graph.register_service(&id); } @@ -318,7 +320,7 @@ pub fn with_service(graph: Arc>, service_id: String) -> Result<(), let mut graph = graph.lock().unwrap(); match graph.services.iter().find(|s| s.id == service_id) { Some(_) => { - graph.execute(GraphCommand::EnableService(service_id.clone())); + graph.execute(GraphCommand::EnableService(service_id.clone()))?; Ok(()) } None => Err(Error::msg("Service not found")), @@ -327,7 +329,7 @@ pub fn with_service(graph: Arc>, service_id: String) -> Result<(), pub fn with_env_variable(graph: Arc>, key: &str, value: &str) -> Result<(), Error> { let mut graph = graph.lock().unwrap(); - graph.execute(GraphCommand::AddEnvVariable(key.into(), value.into())); + graph.execute(GraphCommand::AddEnvVariable(key.into(), value.into()))?; Ok(()) } @@ -354,14 +356,62 @@ pub fn wait_on(graph: Arc>, port: u32, timeout: Option) -> Res cmd, deps, Arc::new(Box::new(PkgxExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } graph.runner = runner; return Ok(()); } + +pub fn add_secretmanager(graph: Arc>, provider: Provider) -> Result { + let mut graph = graph.lock().unwrap(); + let id = Uuid::new_v4().to_string(); + graph.execute(GraphCommand::AddSecretManager(id.clone(), provider))?; + Ok(id) +} + +pub fn get_secret( + graph: Arc>, + secret_manager_id: &str, + name: &str, +) -> Result, Error> { + let mut graph = graph.lock().unwrap(); + let secret = graph.get_secret(secret_manager_id, name.to_string())?; + Ok(secret) +} + +pub fn with_secret_variable( + graph: Arc>, + env_name: &str, + secret_id: &str, + secret_name: &str, +) -> Result<(), Error> { + let mut graph = graph.lock().unwrap(); + graph.execute(GraphCommand::AddSecretVariable( + env_name.into(), + secret_id.into(), + secret_name.into(), + ))?; + Ok(()) +} + +pub fn set_secret(graph: Arc>, name: &str, value: &str) -> Result { + let mut graph = graph.lock().unwrap(); + let id = graph.set_secret(name.into(), value.into())?; + Ok(id) +} + +pub fn get_secret_plaintext( + graph: Arc>, + secret_id: &str, + name: &str, +) -> Result { + let graph = graph.lock().unwrap(); + let secret = graph.get_secret_plaintext(secret_id.into(), name.into())?; + Ok(secret) +} diff --git a/crates/common/src/devbox.rs b/crates/common/src/devbox.rs index 41f520d..f620cd8 100644 --- a/crates/common/src/devbox.rs +++ b/crates/common/src/devbox.rs @@ -23,7 +23,7 @@ pub fn devbox(graph: Arc>, reset: bool) -> Result { "".into(), vec![], Arc::new(Box::new(DevboxExt::default())), - )); + ))?; let devbox = Devbox { id }; Ok(devbox) diff --git a/crates/common/src/devenv.rs b/crates/common/src/devenv.rs index e7087a3..87d5d45 100644 --- a/crates/common/src/devenv.rs +++ b/crates/common/src/devenv.rs @@ -23,7 +23,7 @@ pub fn devenv(graph: Arc>, reset: bool) -> Result { "".into(), vec![], Arc::new(Box::new(DevenvExt::default())), - )); + ))?; let devenv = Devenv { id }; Ok(devenv) diff --git a/crates/common/src/directory.rs b/crates/common/src/directory.rs index ffe8f11..cab8b61 100644 --- a/crates/common/src/directory.rs +++ b/crates/common/src/directory.rs @@ -41,13 +41,13 @@ pub fn directory(graph: Arc>, path: String, reset: bool) -> Result< "".into(), vec![], Arc::new(Box::new(Runner::default())), - )); + ))?; graph.execute(GraphCommand::AddVolume( id.clone(), "directory".into(), path.clone(), - )); + ))?; let path = canonicalize(path).unwrap().to_str().unwrap().to_string(); let directory = Directory { id, path }; diff --git a/crates/common/src/envhub.rs b/crates/common/src/envhub.rs index b0aa924..86872dc 100644 --- a/crates/common/src/envhub.rs +++ b/crates/common/src/envhub.rs @@ -23,7 +23,7 @@ pub fn envhub(graph: Arc>, reset: bool) -> Result { "".into(), vec![], Arc::new(Box::new(EnvhubExt::default())), - )); + ))?; let envhub = Envhub { id }; Ok(envhub) diff --git a/crates/common/src/file.rs b/crates/common/src/file.rs index 6afe311..3461b6d 100644 --- a/crates/common/src/file.rs +++ b/crates/common/src/file.rs @@ -42,13 +42,13 @@ pub fn file(graph: Arc>, path: String, reset: bool) -> Result>, path: String) -> Result { path.clone(), vec![dep_id], Arc::new(Box::new(Md5Ext::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; let hash = graph.execute_vertex(&id)?; Ok(hash) @@ -92,11 +92,11 @@ pub fn sha256(graph: Arc>, path: String) -> Result { path.clone(), vec![dep_id], Arc::new(Box::new(Sha256Ext::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; let hash = graph.execute_vertex(&id)?; Ok(hash) @@ -120,11 +120,11 @@ pub fn tar_xzvf( path.clone(), vec![dep_id], Arc::new(Box::new(TarXzvfExt::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; let output_dir = match output_dir { Some(dir) => dir, @@ -147,7 +147,7 @@ pub fn tar_xzvf( id, "directory".into(), dir.path.clone(), - )); + ))?; Ok(dir) } @@ -170,11 +170,11 @@ pub fn unzip( path.clone(), vec![dep_id], Arc::new(Box::new(UnzipExt::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; let output_dir = match output_dir { Some(dir) => dir, @@ -197,7 +197,7 @@ pub fn unzip( id, "directory".into(), dir.path.clone(), - )); + ))?; Ok(dir) } @@ -216,11 +216,11 @@ pub fn chmod(graph: Arc>, path: String, mode: String) -> Result>, reset: bool) -> Result { "".into(), vec![], Arc::new(Box::new(FloxExt::default())), - )); + ))?; let flox = Flox { id }; Ok(flox) diff --git a/crates/common/src/git.rs b/crates/common/src/git.rs index 6faf1ce..132a60f 100644 --- a/crates/common/src/git.rs +++ b/crates/common/src/git.rs @@ -43,7 +43,7 @@ pub fn git(graph: Arc>, url: String, reset: bool) -> Result>, name: String) -> Result<(), Error> { name, deps, Arc::new(Box::new(GitCheckoutExt::default())), - )); + ))?; graph.execute_vertex(&id)?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } Ok(()) } @@ -101,12 +101,12 @@ pub fn commit(graph: Arc>) -> Result { "".into(), deps, Arc::new(Box::new(GitLastCommitExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } graph.execute_vertex(&id) @@ -124,11 +124,11 @@ pub fn tree(graph: Arc>) -> Result { "".into(), vec![dep_id], Arc::new(Box::new(RunnerExt::default())), - )); + ))?; let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; graph.runner = Arc::new(Box::new(RunnerExt::default())); let path = graph.work_dir.clone(); @@ -137,7 +137,7 @@ pub fn tree(graph: Arc>) -> Result { id.clone(), "directory".into(), path.clone(), - )); + ))?; let directory = Directory { id, path }; diff --git a/crates/common/src/http.rs b/crates/common/src/http.rs index e69998e..104395c 100644 --- a/crates/common/src/http.rs +++ b/crates/common/src/http.rs @@ -31,7 +31,7 @@ pub fn http(graph: Arc>, url: String, reset: bool) -> Result>, url: String, reset: bool) -> Result>, reset: bool) -> Result { "".into(), vec![], Arc::new(Box::new(MiseExt::default())), - )); + ))?; let mise = Mise { id }; Ok(mise) diff --git a/crates/common/src/nix.rs b/crates/common/src/nix.rs index 262ad1d..370078d 100644 --- a/crates/common/src/nix.rs +++ b/crates/common/src/nix.rs @@ -23,7 +23,7 @@ pub fn nix(graph: Arc>, reset: bool, args: NixArgs) -> Result>, name: String) -> Result>, reset: bool) -> Result { "".into(), vec![], Arc::new(Box::new(PixiExt::default())), - )); + ))?; let pixi = Pixi { id }; Ok(pixi) diff --git a/crates/common/src/pkgx.rs b/crates/common/src/pkgx.rs index 00f357b..95a8bc3 100644 --- a/crates/common/src/pkgx.rs +++ b/crates/common/src/pkgx.rs @@ -23,7 +23,7 @@ pub fn pkgx(graph: Arc>, reset: bool) -> Result { "".into(), vec![], Arc::new(Box::new(PkgxExt::default())), - )); + ))?; let pkgx = Pkgx { id }; Ok(pkgx) @@ -44,12 +44,12 @@ pub fn with_packages(graph: Arc>, packages: Vec) -> Result< format!("pkgx install {}", packages.join(" ")), deps, Arc::new(Box::new(PkgxExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } Ok(()) diff --git a/crates/common/src/proto.rs b/crates/common/src/proto.rs index 89fc808..74202f5 100644 --- a/crates/common/src/proto.rs +++ b/crates/common/src/proto.rs @@ -23,7 +23,7 @@ pub fn proto(graph: Arc>, reset: bool) -> Result { "".into(), vec![], Arc::new(Box::new(ProtoExt::default())), - )); + ))?; let proto = Proto { id }; Ok(proto) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index ef2d4cf..e7bc608 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -7,7 +7,7 @@ keywords = ["nix", "environment", "ci", "wasm", "devops"] license = "MPL-2.0" name = "fluentci-core" repository = "https://github.com/fluentci-io/fluentci-engine" -version = "0.2.1" +version = "0.3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,7 +17,10 @@ chrono = "0.4.35" dirs = "5.0.1" fluentci-ext = {path = "../ext", version = "0.2.0"} fluentci-logging = {path = "../logging", version = "0.1.0"} -fluentci-types = {path = "../types", version = "0.1.6"} +fluentci-secrets = {path = "../secrets", version = "0.1.0"} +fluentci-types = {path = "../types", version = "0.1.7"} +hex = "0.4.3" +hmac = "0.12.1" opentelemetry = {version = "0.22.0", features = ["trace", "pin-project-lite"]} opentelemetry-jaeger = "0.21.0" opentelemetry-otlp = {version = "0.15.0", features = ["http-proto", "reqwest-rustls", "reqwest-client", "grpc-tonic"], default-features = false} @@ -25,3 +28,10 @@ opentelemetry-zipkin = {version = "0.20.0", features = ["reqwest-rustls", "reqwe opentelemetry_sdk = {version = "0.22.1", features = ["tokio", "rt-tokio", "trace"]} owo-colors = "4.0.0" serde_yaml = "0.9.34" +sha2 = "0.10.8" +tokio = "1.36.0" +uuid = {version = "1.7.0", features = [ + "v4", + "fast-rng", + "macro-diagnostics", +]} diff --git a/crates/core/src/deps.rs b/crates/core/src/deps.rs index 0f2b970..675f786 100644 --- a/crates/core/src/deps.rs +++ b/crates/core/src/deps.rs @@ -1,14 +1,19 @@ use anyhow::Error; +use fluentci_secrets::{Provider, Vault, VaultConfig}; use opentelemetry::{ global, trace::{Span, TraceContextExt, Tracer, TracerProvider}, Context, KeyValue, }; use owo_colors::OwoColorize; -use std::env::{self, current_dir}; use std::sync::mpsc::{self, Sender}; use std::sync::Arc; +use std::{ + collections::HashMap, + env::{self, current_dir}, +}; use std::{path::Path, thread}; +use uuid::Uuid; use fluentci_ext::envhub::Envhub; use fluentci_ext::service::Service as ServiceExt; @@ -16,9 +21,12 @@ use fluentci_ext::Extension; use fluentci_types::{ nix::NixArgs, process_compose::{self, Process}, + secret::Secret, Output, }; +use crate::get_hmac; + use super::edge::Edge; use super::vertex::{Runnable, Vertex}; @@ -64,9 +72,11 @@ pub enum GraphCommand { Arc>, ), AddEnvVariable(String, String), + AddSecretVariable(String, String, String), EnableService(String), AddEdge(usize, usize), Execute(Output), + AddSecretManager(String, Provider), } #[derive(Clone)] @@ -76,6 +86,9 @@ pub struct Graph { pub volumes: Vec, pub services: Vec, pub enabled_services: Vec, + pub vaults: HashMap>>, + pub secrets: HashMap, + pub secret_names: HashMap, tx: Sender<(String, usize)>, pub runner: Arc>, @@ -96,6 +109,70 @@ impl Graph { runner, work_dir, nix_args: NixArgs::default(), + vaults: HashMap::new(), + secrets: HashMap::new(), + secret_names: HashMap::new(), + } + } + + pub fn set_secret(&mut self, name: String, value: String) -> Result { + let id = Uuid::new_v4().to_string(); + let key = get_hmac(id.clone(), name.clone())?; + self.secrets.insert(key, value); + self.secret_names.insert(id.clone(), name); + Ok(id) + } + + pub fn get_secret_plaintext(&self, secret_id: String, name: String) -> Result { + let key = get_hmac(secret_id, name)?; + match self.secrets.get(&key) { + Some(value) => Ok(value.clone()), + None => Err(Error::msg("Secret not found")), + } + } + + pub fn get_secret( + &mut self, + secret_manager_id: &str, + name: String, + ) -> Result, Error> { + match self.vaults.get(secret_manager_id) { + Some(provider) => { + let provider = provider.clone(); + let cloned_name = name.clone(); + let handler = thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(provider.download_json(&name)) + }); + let secrets = match handler.join() { + Ok(Ok(secrets)) => secrets, + Ok(Err(e)) => return Err(Error::msg(e)), + Err(_) => return Err(Error::msg("Failed to join thread")), + }; + + if secrets.is_empty() { + return Err(Error::msg(format!("Secret {} not found", cloned_name))); + } + + let mut results = Vec::new(); + secrets.iter().for_each(|(key, value)| { + let id = Uuid::new_v4().to_string(); + let hmac = get_hmac(id.clone(), key.clone()).unwrap(); + self.secrets.insert(hmac, value.clone()); + self.secret_names.insert(id.clone(), key.clone()); + results.push(Secret { + id: id.clone(), + name: key.clone(), + mount: cloned_name.clone(), + }); + }); + + Ok(results) + } + None => Err(Error::msg(format!( + "Secret manager {} not found", + secret_manager_id + ))), } } @@ -203,7 +280,7 @@ impl Graph { self.services.push(service); } - pub fn execute(&mut self, command: GraphCommand) { + pub fn execute(&mut self, command: GraphCommand) -> Result<(), Error> { match command { GraphCommand::AddVolume(id, label, path) => { self.volumes.push(Volume { @@ -231,6 +308,10 @@ impl Graph { GraphCommand::AddEnvVariable(key, value) => { env::set_var(key, value); } + GraphCommand::AddSecretVariable(env_name, secret_id, secret_name) => { + let value = self.get_secret_plaintext(secret_id, secret_name)?; + env::set_var(env_name, value); + } GraphCommand::Execute(Output::Stdout) => { self.execute_graph(Output::Stdout); } @@ -239,9 +320,28 @@ impl Graph { } GraphCommand::EnableService(id) => match self.services.iter().find(|s| s.id == id) { Some(service) => self.enabled_services.push(service.clone()), - None => println!("Service not found"), + None => return Err(Error::msg("Service not found")), + }, + GraphCommand::AddSecretManager(id, provider) => match provider { + Provider::Aws(config) => { + self.vaults + .insert(id, Arc::new(Box::new(config.into_vault()?))); + } + Provider::Google(config) => { + self.vaults + .insert(id, Arc::new(Box::new(config.into_vault()?))); + } + Provider::Hashicorp(config) => { + self.vaults + .insert(id, Arc::new(Box::new(config.into_vault()?))); + } + Provider::Azure(config) => { + self.vaults + .insert(id, Arc::new(Box::new(config.into_vault()?))); + } }, } + Ok(()) } pub fn execute_services(&mut self, ctx: &Context) -> Result<(), Error> { @@ -576,7 +676,7 @@ mod tests { use super::*; #[test] - fn test_graph() { + fn test_graph() -> Result<(), Error> { let (tx, _) = mpsc::channel(); let mut graph = Graph::new(tx, Arc::new(Box::new(Runner::default()))); graph.execute(GraphCommand::AddVertex( @@ -585,32 +685,32 @@ mod tests { "echo A".into(), vec![], Arc::new(Box::new(Runner::default())), - )); + ))?; graph.execute(GraphCommand::AddVertex( "2".into(), "B".into(), "echo B".into(), vec!["1".into()], Arc::new(Box::new(Runner::default())), - )); + ))?; graph.execute(GraphCommand::AddVertex( "3".into(), "C".into(), "echo C".into(), vec!["1".into()], Arc::new(Box::new(Runner::default())), - )); + ))?; graph.execute(GraphCommand::AddVertex( "4".into(), "D".into(), "echo D".into(), vec!["2".into(), "3".into()], Arc::new(Box::new(Runner::default())), - )); - graph.execute(GraphCommand::AddEdge(0, 1)); - graph.execute(GraphCommand::AddEdge(0, 2)); - graph.execute(GraphCommand::AddEdge(1, 3)); - graph.execute(GraphCommand::AddEdge(2, 3)); + ))?; + graph.execute(GraphCommand::AddEdge(0, 1))?; + graph.execute(GraphCommand::AddEdge(0, 2))?; + graph.execute(GraphCommand::AddEdge(1, 3))?; + graph.execute(GraphCommand::AddEdge(2, 3))?; assert_eq!(graph.size(), 4); assert_eq!(graph.vertices[0].id, "1"); @@ -626,10 +726,11 @@ mod tests { assert_eq!(graph.vertices[2].command, "echo C"); assert_eq!(graph.vertices[3].command, "echo D"); - graph.execute(GraphCommand::Execute(Output::Stdout)); + graph.execute(GraphCommand::Execute(Output::Stdout))?; graph.reset(); assert_eq!(graph.size(), 0); + Ok(()) } } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 8fae605..f4e7779 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -3,17 +3,21 @@ use std::env; use std::process::Command; use anyhow::Error; +use hmac::{Hmac, Mac}; use opentelemetry::trace::noop::NoopTracerProvider; use opentelemetry::trace::TraceError; use opentelemetry::{global, KeyValue}; use opentelemetry_otlp::{Protocol, WithExportConfig}; use opentelemetry_sdk::trace::config; use opentelemetry_sdk::Resource; +use sha2::Sha256; pub mod deps; pub mod edge; pub mod vertex; +type HmacSha256 = Hmac; + pub fn init_tracer() -> Result<(), TraceError> { if let Ok(endpoint) = env::var("OTEL_EXPORTER_OTLP_ENDPOINT") { let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL") { @@ -152,3 +156,10 @@ pub fn set_git_repo_metadata() -> Result<(), Error> { env::set_var("GIT_AUTHOR", author.trim()); Ok(()) } + +pub fn get_hmac(id: String, name: String) -> Result { + let mut mac = HmacSha256::new_from_slice(&id.into_bytes())?; + mac.update(name.as_bytes()); + let key = mac.finalize().into_bytes(); + Ok(hex::encode(key)) +} diff --git a/crates/graphql/Cargo.toml b/crates/graphql/Cargo.toml index 78f1eff..17cdae2 100644 --- a/crates/graphql/Cargo.toml +++ b/crates/graphql/Cargo.toml @@ -7,7 +7,7 @@ keywords = ["nix", "environment", "ci", "wasm", "devops"] license = "MPL-2.0" name = "fluentci-graphql" repository = "https://github.com/fluentci-io/fluentci-engine" -version = "0.2.1" +version = "0.3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,10 +16,11 @@ anyhow = "1.0.80" async-graphql = "7.0.2" async-graphql-actix-web = "7.0.2" dirs = "5.0.1" -fluentci-common = {path = "../common", version = "0.1.8"} -fluentci-core = {path = "../core", version = "0.2.1"} +fluentci-common = {path = "../common", version = "0.2.0"} +fluentci-core = {path = "../core", version = "0.3.0"} fluentci-ext = {path = "../ext", version = "0.2.0"} -fluentci-types = {path = "../types", version = "0.1.6"} +fluentci-secrets = {path = "../secrets", version = "0.1.0"} +fluentci-types = {path = "../types", version = "0.1.7"} regex = "1.10.3" sha256 = "1.5.0" tokio = "1.36.0" diff --git a/crates/graphql/src/schema/mod.rs b/crates/graphql/src/schema/mod.rs index 6238d89..719abdc 100644 --- a/crates/graphql/src/schema/mod.rs +++ b/crates/graphql/src/schema/mod.rs @@ -4,6 +4,7 @@ use self::{ cache::CacheQuery, devbox::DevboxQuery, devenv::DevenvQuery, directory::DirectoryQuery, file::FileQuery, flox::FloxQuery, git::GitQuery, http::HttpQuery, mise::MiseQuery, nix::NixQuery, pipeline::PipelineQuery, pixi::PixiQuery, pkgx::PkgxQuery, proto::ProtoQuery, + secrets::SecretsQuery, }; pub mod cache; @@ -22,6 +23,7 @@ pub mod pipeline; pub mod pixi; pub mod pkgx; pub mod proto; +pub mod secrets; #[derive(Default, MergedObject)] pub struct Query( @@ -39,4 +41,5 @@ pub struct Query( MiseQuery, PixiQuery, ProtoQuery, + SecretsQuery, ); diff --git a/crates/graphql/src/schema/objects/cache.rs b/crates/graphql/src/schema/objects/cache.rs index 9be0b52..fa8b638 100644 --- a/crates/graphql/src/schema/objects/cache.rs +++ b/crates/graphql/src/schema/objects/cache.rs @@ -1,6 +1,15 @@ -use async_graphql::{Object, ID}; +use async_graphql::{InputObject, Object, ID}; use fluentci_types::cache as types; +pub mod input { + use super::*; + + #[derive(Debug, Clone, InputObject)] + pub struct CacheInput { + pub id: ID, + } +} + #[derive(Debug, Clone, Default)] pub struct Cache { pub id: ID, diff --git a/crates/graphql/src/schema/objects/devbox.rs b/crates/graphql/src/schema/objects/devbox.rs index 6fd6a16..becf755 100644 --- a/crates/graphql/src/schema/objects/devbox.rs +++ b/crates/graphql/src/schema/objects/devbox.rs @@ -25,7 +25,7 @@ impl Devbox { graph.clone(), args, Arc::new(Box::new(DevboxExt::default())), - ); + )?; Ok(self) } @@ -49,10 +49,10 @@ impl Devbox { &self, ctx: &Context<'_>, path: String, - cache_id: ID, + cache: ID, ) -> Result<&Devbox, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -106,6 +106,20 @@ impl Devbox { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Devbox, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Devbox { diff --git a/crates/graphql/src/schema/objects/devenv.rs b/crates/graphql/src/schema/objects/devenv.rs index 504486e..5ced8e0 100644 --- a/crates/graphql/src/schema/objects/devenv.rs +++ b/crates/graphql/src/schema/objects/devenv.rs @@ -25,7 +25,7 @@ impl Devenv { graph.clone(), args, Arc::new(Box::new(DevenvExt::default())), - ); + )?; Ok(self) } @@ -39,9 +39,9 @@ impl Devenv { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Devenv, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Devenv, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } @@ -49,10 +49,10 @@ impl Devenv { &self, ctx: &Context<'_>, path: String, - cache_id: ID, + cache: ID, ) -> Result<&Devenv, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -106,6 +106,20 @@ impl Devenv { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Devenv, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Devenv { diff --git a/crates/graphql/src/schema/objects/directory.rs b/crates/graphql/src/schema/objects/directory.rs index 5ee00f2..c32de03 100644 --- a/crates/graphql/src/schema/objects/directory.rs +++ b/crates/graphql/src/schema/objects/directory.rs @@ -125,13 +125,13 @@ impl Directory { async fn with_exec(&self, ctx: &Context<'_>, args: Vec) -> Result<&Directory, Error> { let graph = ctx.data::>>().unwrap(); - common::with_exec(graph.clone(), args, Arc::new(Box::new(Runner::default()))); + common::with_exec(graph.clone(), args, Arc::new(Box::new(Runner::default())))?; Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Directory, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Directory, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } @@ -139,10 +139,10 @@ impl Directory { &self, ctx: &Context<'_>, path: String, - cache_id: ID, + cache: ID, ) -> Result<&Directory, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -208,6 +208,20 @@ impl Directory { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Directory, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Directory { diff --git a/crates/graphql/src/schema/objects/envhub.rs b/crates/graphql/src/schema/objects/envhub.rs index 7d65646..17f976d 100644 --- a/crates/graphql/src/schema/objects/envhub.rs +++ b/crates/graphql/src/schema/objects/envhub.rs @@ -36,12 +36,12 @@ impl Envhub { environment, deps, Arc::new(Box::new(EnvhubExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } Ok(self) @@ -53,7 +53,7 @@ impl Envhub { graph.clone(), args, Arc::new(Box::new(EnvhubExt::default())), - ); + )?; Ok(self) } @@ -67,9 +67,9 @@ impl Envhub { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Envhub, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Envhub, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } @@ -77,10 +77,10 @@ impl Envhub { &self, ctx: &Context<'_>, path: String, - cache_id: ID, + cache: ID, ) -> Result<&Envhub, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -134,6 +134,20 @@ impl Envhub { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Envhub, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Envhub { diff --git a/crates/graphql/src/schema/objects/flox.rs b/crates/graphql/src/schema/objects/flox.rs index 3e92d69..37e245d 100644 --- a/crates/graphql/src/schema/objects/flox.rs +++ b/crates/graphql/src/schema/objects/flox.rs @@ -21,7 +21,7 @@ impl Flox { async fn with_exec(&self, ctx: &Context<'_>, args: Vec) -> Result<&Flox, Error> { let graph = ctx.data::>>().unwrap(); - common::with_exec(graph.clone(), args, Arc::new(Box::new(FloxExt::default()))); + common::with_exec(graph.clone(), args, Arc::new(Box::new(FloxExt::default())))?; Ok(self) } @@ -31,20 +31,15 @@ impl Flox { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Flox, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Flox, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } - async fn with_cache( - &self, - ctx: &Context<'_>, - path: String, - cache_id: ID, - ) -> Result<&Flox, Error> { + async fn with_cache(&self, ctx: &Context<'_>, path: String, cache: ID) -> Result<&Flox, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -98,6 +93,20 @@ impl Flox { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Flox, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Flox { diff --git a/crates/graphql/src/schema/objects/mise.rs b/crates/graphql/src/schema/objects/mise.rs index 15d3b8c..c7c0bef 100644 --- a/crates/graphql/src/schema/objects/mise.rs +++ b/crates/graphql/src/schema/objects/mise.rs @@ -21,7 +21,7 @@ impl Mise { async fn with_exec(&self, ctx: &Context<'_>, args: Vec) -> Result<&Mise, Error> { let graph = ctx.data::>>().unwrap(); - common::with_exec(graph.clone(), args, Arc::new(Box::new(MiseExt::default()))); + common::with_exec(graph.clone(), args, Arc::new(Box::new(MiseExt::default())))?; Ok(self) } @@ -31,19 +31,14 @@ impl Mise { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Mise, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Mise, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } - async fn with_cache( - &self, - ctx: &Context<'_>, - path: String, - cache_id: ID, - ) -> Result<&Mise, Error> { + async fn with_cache(&self, ctx: &Context<'_>, path: String, cache: ID) -> Result<&Mise, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -97,6 +92,20 @@ impl Mise { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Mise, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Mise { diff --git a/crates/graphql/src/schema/objects/mod.rs b/crates/graphql/src/schema/objects/mod.rs index 78f6645..880a4d7 100644 --- a/crates/graphql/src/schema/objects/mod.rs +++ b/crates/graphql/src/schema/objects/mod.rs @@ -12,4 +12,6 @@ pub mod pipeline; pub mod pixi; pub mod pkgx; pub mod proto; +pub mod secret; +pub mod secret_manager; pub mod service; diff --git a/crates/graphql/src/schema/objects/nix.rs b/crates/graphql/src/schema/objects/nix.rs index 9716f08..fb792ae 100644 --- a/crates/graphql/src/schema/objects/nix.rs +++ b/crates/graphql/src/schema/objects/nix.rs @@ -41,7 +41,7 @@ impl Nix { graph.clone(), args, Arc::new(Box::new(NixExt::new(nix_args))), - ); + )?; Ok(self) } @@ -51,20 +51,15 @@ impl Nix { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Nix, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Nix, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } - async fn with_cache( - &self, - ctx: &Context<'_>, - path: String, - cache_id: ID, - ) -> Result<&Nix, Error> { + async fn with_cache(&self, ctx: &Context<'_>, path: String, cache: ID) -> Result<&Nix, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -113,6 +108,20 @@ impl Nix { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Nix, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Nix { diff --git a/crates/graphql/src/schema/objects/pipeline.rs b/crates/graphql/src/schema/objects/pipeline.rs index 0e9c56d..ebed5cd 100644 --- a/crates/graphql/src/schema/objects/pipeline.rs +++ b/crates/graphql/src/schema/objects/pipeline.rs @@ -67,13 +67,13 @@ impl Pipeline { url.clone(), deps, Arc::new(Box::new(HttpExt::default())), - )); + ))?; graph.execute_vertex(&id)?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let filename = sha256::digest(url).to_string(); let work_dir = graph.work_dir.clone(); @@ -87,7 +87,7 @@ impl Pipeline { id, "file".into(), file.path.clone(), - )); + ))?; Ok(file) } @@ -123,13 +123,13 @@ impl Pipeline { url.clone(), deps, Arc::new(Box::new(GitExt::default())), - )); + ))?; graph.execute_vertex(&id)?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } graph.work_dir = format!( @@ -161,12 +161,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(DevboxExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let devbox = Devbox { id: ID(id) }; @@ -192,12 +192,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(DevenvExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let devenv = Devenv { id: ID(id) }; @@ -223,12 +223,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(FloxExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let flox = Flox { id: ID(id) }; @@ -257,12 +257,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(NixExt::new(args))), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let nix = Nix { id: ID(id) }; @@ -288,12 +288,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(PkgxExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let pkgx = Pkgx { id: ID(id) }; @@ -319,12 +319,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(PixiExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let pixi = Pixi { id: ID(id) }; @@ -350,12 +350,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(ProtoExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let proto = Proto { id: ID(id) }; @@ -381,12 +381,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(MiseExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let mise = Mise { id: ID(id) }; @@ -412,12 +412,12 @@ impl Pipeline { "".into(), deps, Arc::new(Box::new(EnvhubExt::default())), - )); + ))?; if graph.size() > 2 { let x = graph.size() - 2; let y = graph.size() - 1; - graph.execute(GraphCommand::AddEdge(x, y)); + graph.execute(GraphCommand::AddEdge(x, y))?; } let envhub = Envhub { id: ID(id) }; @@ -426,7 +426,7 @@ impl Pipeline { async fn with_exec(&self, ctx: &Context<'_>, args: Vec) -> Result<&Pipeline, Error> { let graph = ctx.data::>>().unwrap(); - common::with_exec(graph.clone(), args, Arc::new(Box::new(Runner::default()))); + common::with_exec(graph.clone(), args, Arc::new(Box::new(Runner::default())))?; Ok(self) } @@ -436,9 +436,9 @@ impl Pipeline { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Pipeline, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Pipeline, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } @@ -446,10 +446,10 @@ impl Pipeline { &self, ctx: &Context<'_>, path: String, - cache_id: ID, + cache: ID, ) -> Result<&Pipeline, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -503,6 +503,20 @@ impl Pipeline { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Pipeline, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Pipeline { diff --git a/crates/graphql/src/schema/objects/pixi.rs b/crates/graphql/src/schema/objects/pixi.rs index 5f6a9e2..ae18667 100644 --- a/crates/graphql/src/schema/objects/pixi.rs +++ b/crates/graphql/src/schema/objects/pixi.rs @@ -21,7 +21,7 @@ impl Pixi { async fn with_exec(&self, ctx: &Context<'_>, args: Vec) -> Result<&Pixi, Error> { let graph = ctx.data::>>().unwrap(); - common::with_exec(graph.clone(), args, Arc::new(Box::new(PixiExt::default()))); + common::with_exec(graph.clone(), args, Arc::new(Box::new(PixiExt::default())))?; Ok(self) } @@ -31,20 +31,15 @@ impl Pixi { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Pixi, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Pixi, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } - async fn with_cache( - &self, - ctx: &Context<'_>, - path: String, - cache_id: ID, - ) -> Result<&Pixi, Error> { + async fn with_cache(&self, ctx: &Context<'_>, path: String, cache: ID) -> Result<&Pixi, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -98,6 +93,20 @@ impl Pixi { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Pixi, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Pixi { diff --git a/crates/graphql/src/schema/objects/pkgx.rs b/crates/graphql/src/schema/objects/pkgx.rs index 0a0fb70..2f81443 100644 --- a/crates/graphql/src/schema/objects/pkgx.rs +++ b/crates/graphql/src/schema/objects/pkgx.rs @@ -1,13 +1,12 @@ use std::sync::{mpsc::Receiver, Arc, Mutex}; +use super::service::Service; use async_graphql::{Context, Error, Object, ID}; use fluentci_common::{common, pkgx as common_pkgx}; use fluentci_core::deps::Graph; use fluentci_ext::pkgx::Pkgx as PkgxExt; use fluentci_types::pkgx as types; -use super::service::Service; - #[derive(Debug, Clone, Default)] pub struct Pkgx { pub id: ID, @@ -21,7 +20,7 @@ impl Pkgx { async fn with_exec(&self, ctx: &Context<'_>, args: Vec) -> Result<&Pkgx, Error> { let graph = ctx.data::>>().unwrap(); - common::with_exec(graph.clone(), args, Arc::new(Box::new(PkgxExt::default()))); + common::with_exec(graph.clone(), args, Arc::new(Box::new(PkgxExt::default())))?; Ok(self) } @@ -31,20 +30,15 @@ impl Pkgx { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Pkgx, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Pkgx, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } - async fn with_cache( - &self, - ctx: &Context<'_>, - path: String, - cache_id: ID, - ) -> Result<&Pkgx, Error> { + async fn with_cache(&self, ctx: &Context<'_>, path: String, cache: ID) -> Result<&Pkgx, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -108,6 +102,20 @@ impl Pkgx { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Pkgx, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Pkgx { diff --git a/crates/graphql/src/schema/objects/proto.rs b/crates/graphql/src/schema/objects/proto.rs index 6311cd0..1910610 100644 --- a/crates/graphql/src/schema/objects/proto.rs +++ b/crates/graphql/src/schema/objects/proto.rs @@ -21,7 +21,7 @@ impl Proto { async fn with_exec(&self, ctx: &Context<'_>, args: Vec) -> Result<&Proto, Error> { let graph = ctx.data::>>().unwrap(); - common::with_exec(graph.clone(), args, Arc::new(Box::new(ProtoExt::default()))); + common::with_exec(graph.clone(), args, Arc::new(Box::new(ProtoExt::default())))?; Ok(self) } @@ -31,9 +31,9 @@ impl Proto { Ok(self) } - async fn with_service(&self, ctx: &Context<'_>, service_id: ID) -> Result<&Proto, Error> { + async fn with_service(&self, ctx: &Context<'_>, service: ID) -> Result<&Proto, Error> { let graph = ctx.data::>>().unwrap(); - common::with_service(graph.clone(), service_id.into())?; + common::with_service(graph.clone(), service.into())?; Ok(self) } @@ -41,10 +41,10 @@ impl Proto { &self, ctx: &Context<'_>, path: String, - cache_id: ID, + cache: ID, ) -> Result<&Proto, Error> { let graph = ctx.data::>>().unwrap(); - common::with_cache(graph.clone(), cache_id.into(), path)?; + common::with_cache(graph.clone(), cache.into(), path)?; Ok(self) } @@ -98,6 +98,20 @@ impl Proto { common::wait_on(graph.clone(), port, timeout)?; Ok(self) } + + async fn with_secret_variable( + &self, + ctx: &Context<'_>, + name: String, + secret: ID, + ) -> Result<&Proto, Error> { + let graph = ctx.data::>>().unwrap(); + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(secret.as_str()).unwrap().clone(); + drop(g); + common::with_secret_variable(graph.clone(), &name, secret.as_str(), &secret_name)?; + Ok(self) + } } impl From for Proto { diff --git a/crates/graphql/src/schema/objects/secret.rs b/crates/graphql/src/schema/objects/secret.rs new file mode 100644 index 0000000..6b7fbc1 --- /dev/null +++ b/crates/graphql/src/schema/objects/secret.rs @@ -0,0 +1,44 @@ +use std::sync::{Arc, Mutex}; + +use async_graphql::{Context, Error, Object, ID}; +use fluentci_core::deps::Graph; +use fluentci_types::secret as types; + +#[derive(Debug, Clone, Default)] +pub struct Secret { + pub id: ID, + pub name: String, + pub mount: String, +} + +impl From for Secret { + fn from(secret: types::Secret) -> Self { + Self { + id: secret.id.into(), + name: secret.name, + mount: secret.mount, + } + } +} + +#[Object] +impl Secret { + async fn id(&self) -> &ID { + &self.id + } + + async fn plaintext(&self, ctx: &Context<'_>) -> Result { + let graph = ctx.data::>>().unwrap(); + let graph = graph.lock().unwrap(); + let value = graph.get_secret_plaintext(self.id.to_string().clone(), self.name.clone())?; + Ok(value) + } + + async fn name(&self) -> &str { + &self.name + } + + async fn mount(&self) -> &str { + &self.mount + } +} diff --git a/crates/graphql/src/schema/objects/secret_manager.rs b/crates/graphql/src/schema/objects/secret_manager.rs new file mode 100644 index 0000000..7ea46ac --- /dev/null +++ b/crates/graphql/src/schema/objects/secret_manager.rs @@ -0,0 +1,24 @@ +use std::sync::{Arc, Mutex}; + +use crate::schema::objects::secret::Secret; +use async_graphql::{Context, Error, Object, ID}; +use fluentci_common::common; +use fluentci_core::deps::Graph; + +#[derive(Debug, Clone, Default)] +pub struct SecretManager { + pub id: ID, +} + +#[Object] +impl SecretManager { + async fn id(&self) -> &ID { + &self.id + } + + async fn get_secret(&self, ctx: &Context<'_>, name: String) -> Result, Error> { + let graph = ctx.data::>>().unwrap(); + let secret = common::get_secret(graph.clone(), self.id.as_str(), &name)?; + Ok(secret.into_iter().map(Into::into).collect()) + } +} diff --git a/crates/graphql/src/schema/secrets.rs b/crates/graphql/src/schema/secrets.rs new file mode 100644 index 0000000..7e2c828 --- /dev/null +++ b/crates/graphql/src/schema/secrets.rs @@ -0,0 +1,114 @@ +use std::{ + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use async_graphql::{Context, Error, Object, ID}; +use fluentci_common::common; +use fluentci_core::deps::Graph; +use fluentci_secrets::{ + aws::AwsConfig, + azure::{AzureConfig, AzureCredential}, + google::GoogleConfig, + vault::HashicorpVaultConfig, + Provider, +}; + +use crate::schema::objects::secret_manager::SecretManager; + +use super::objects::secret::Secret; + +#[derive(Default, Clone)] +pub struct SecretsQuery; + +#[Object] +impl SecretsQuery { + async fn set_secret( + &self, + ctx: &Context<'_>, + name: String, + value: String, + ) -> Result { + let graph = ctx.data::>>().unwrap(); + let secret_id = common::set_secret(graph.clone(), &name, &value)?; + Ok(Secret { + id: ID(secret_id), + name, + mount: "default".to_string(), + }) + } + + async fn google_cloud_secret_manager( + &self, + ctx: &Context<'_>, + project: String, + google_credentials_file: String, + ) -> Result { + let graph = ctx.data::>>().unwrap(); + let provider = Provider::Google(GoogleConfig { + google_project: Some(project), + google_credentials_file: Some(PathBuf::from(google_credentials_file)), + google_credentials_json: None, + }); + let id = common::add_secretmanager(graph.clone(), provider)?; + Ok(SecretManager { id: ID(id) }) + } + + async fn aws_secrets_manager( + &self, + ctx: &Context<'_>, + region: String, + access_key_id: String, + secret_access_key: String, + ) -> Result { + let graph = ctx.data::>>().unwrap(); + let provider = Provider::Aws(AwsConfig { + aws_region: region, + aws_access_key_id: Some(access_key_id), + aws_secret_access_key: Some(secret_access_key), + }); + let id = common::add_secretmanager(graph.clone(), provider)?; + Ok(SecretManager { id: ID(id) }) + } + + async fn azure_keyvault( + &self, + ctx: &Context<'_>, + client_id: String, + client_secret: String, + tenant_id: String, + keyvault_name: String, + keyvault_url: String, + ) -> Result { + let graph = ctx.data::>>().unwrap(); + let credential = AzureCredential { + azure_client_id: Some(client_id), + azure_client_secret: Some(client_secret), + azure_tenant_id: Some(tenant_id), + }; + let provider = Provider::Azure(AzureConfig { + credential, + azure_keyvault_name: Some(keyvault_name), + azure_keyvault_url: Some(keyvault_url), + }); + let id = common::add_secretmanager(graph.clone(), provider)?; + Ok(SecretManager { id: ID(id) }) + } + + async fn hashicorp_vault( + &self, + ctx: &Context<'_>, + address: String, + token: String, + cacerts: Option, + ) -> Result { + let graph = ctx.data::>>().unwrap(); + let provider = Provider::Hashicorp(HashicorpVaultConfig { + vault_address: Some(address), + vault_token: Some(token), + vault_cacert: cacerts.map(|x| PathBuf::from(x)), + }); + let id = common::add_secretmanager(graph.clone(), provider)?; + Ok(SecretManager { id: ID(id) }) + } +} diff --git a/crates/pdk/Cargo.toml b/crates/pdk/Cargo.toml index 3169617..a549fc5 100644 --- a/crates/pdk/Cargo.toml +++ b/crates/pdk/Cargo.toml @@ -7,11 +7,11 @@ keywords = ["nix", "environment", "ci", "wasm", "devops"] license = "MPL-2.0" name = "fluentci-pdk" repository = "https://github.com/fluentci-io/fluentci-engine" -version = "0.1.13" +version = "0.2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] extism-pdk = "1.1.0" -fluentci-types = {path = "../types", version = "0.1.6"} +fluentci-types = {path = "../types", version = "0.1.7"} serde = {version = "1.0.197", features = ["serde_derive", "derive"]} diff --git a/crates/pdk/src/devbox.rs b/crates/pdk/src/devbox.rs index 8026b07..9be598c 100644 --- a/crates/pdk/src/devbox.rs +++ b/crates/pdk/src/devbox.rs @@ -15,6 +15,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -112,4 +113,13 @@ impl Devbox { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Devbox { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/devenv.rs b/crates/pdk/src/devenv.rs index ba8bc31..ecb4e67 100644 --- a/crates/pdk/src/devenv.rs +++ b/crates/pdk/src/devenv.rs @@ -15,6 +15,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -112,4 +113,13 @@ impl Devenv { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Devenv { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/directory.rs b/crates/pdk/src/directory.rs index c57e15f..e2f4ded 100644 --- a/crates/pdk/src/directory.rs +++ b/crates/pdk/src/directory.rs @@ -32,6 +32,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -202,4 +203,14 @@ impl Directory { path: self.path.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Directory { + id: self.id.clone(), + path: self.path.clone(), + }) + } } diff --git a/crates/pdk/src/envhub.rs b/crates/pdk/src/envhub.rs index 2f6e5e4..b8321ce 100644 --- a/crates/pdk/src/envhub.rs +++ b/crates/pdk/src/envhub.rs @@ -15,6 +15,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -110,4 +111,13 @@ impl Envhub { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Envhub { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/flox.rs b/crates/pdk/src/flox.rs index 954441b..e3a9763 100644 --- a/crates/pdk/src/flox.rs +++ b/crates/pdk/src/flox.rs @@ -15,6 +15,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -112,4 +113,13 @@ impl Flox { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Flox { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/lib.rs b/crates/pdk/src/lib.rs index 5285819..5ee0860 100644 --- a/crates/pdk/src/lib.rs +++ b/crates/pdk/src/lib.rs @@ -1,6 +1,8 @@ use extism_pdk::*; -use fluentci_types::{nix::NixArgs, Module}; +use fluentci_types::{nix::NixArgs, secret::*, Module}; use proto::Proto; +use secret::Secret; +use secret_manager::SecretManager; use self::{ cache::Cache, devbox::Devbox, directory::Directory, envhub::Envhub, file::File, flox::Flox, @@ -21,6 +23,8 @@ pub mod pipeline; pub mod pixi; pub mod pkgx; pub mod proto; +pub mod secret; +pub mod secret_manager; #[host_fn] extern "ExtismHost" { @@ -45,6 +49,8 @@ extern "ExtismHost" { fn get_os() -> String; fn get_arch() -> String; fn call(opts: Json) -> String; + fn add_secretmanager(provider: Json) -> String; + fn set_secret(params: Json>) -> String; } pub struct Client {} @@ -143,4 +149,78 @@ impl Client { })) } } + + pub fn google_secret_manager( + &self, + project: &str, + google_credentials_file: &str, + ) -> Result { + let provider = Provider::Google(GoogleConfig { + google_project: Some(project.to_string()), + google_credentials_file: Some(google_credentials_file.into()), + ..Default::default() + }); + let id = unsafe { add_secretmanager(Json(provider))? }; + Ok(SecretManager { id }) + } + + pub fn aws_secrets_manager( + &self, + region: &str, + access_key_id: &str, + secret_access_key: &str, + ) -> Result { + let provider = Provider::Aws(AwsConfig { + aws_region: region.into(), + aws_access_key_id: Some(access_key_id.to_string()), + aws_secret_access_key: Some(secret_access_key.to_string()), + }); + let id = unsafe { add_secretmanager(Json(provider))? }; + Ok(SecretManager { id }) + } + + pub fn azure_keyvault( + &self, + client_id: &str, + client_secret: &str, + tenant_id: &str, + keyvault_name: &str, + keyvault_url: &str, + ) -> Result { + let provider = Provider::Azure(AzureConfig { + credential: AzureCredential { + azure_client_id: Some(client_id.to_string()), + azure_client_secret: Some(client_secret.to_string()), + azure_tenant_id: Some(tenant_id.to_string()), + }, + azure_keyvault_name: Some(keyvault_name.to_string()), + azure_keyvault_url: Some(keyvault_url.to_string()), + }); + let id = unsafe { add_secretmanager(Json(provider))? }; + Ok(SecretManager { id }) + } + + pub fn hashicorp_vault( + &self, + address: &str, + token: &str, + cacert: Option<&str>, + ) -> Result { + let provider = Provider::Hashicorp(HashicorpVaultConfig { + vault_address: Some(address.to_string()), + vault_token: Some(token.to_string()), + vault_cacert: cacert.map(|x| x.into()), + }); + let id = unsafe { add_secretmanager(Json(provider))? }; + Ok(SecretManager { id }) + } + + pub fn set_secret(&self, name: &str, value: &str) -> Result { + let id = unsafe { set_secret(Json(vec![name.into(), value.into()]))? }; + Ok(Secret { + id, + name: name.into(), + mount: "default".into(), + }) + } } diff --git a/crates/pdk/src/mise.rs b/crates/pdk/src/mise.rs index 22d5ca8..4328fa5 100644 --- a/crates/pdk/src/mise.rs +++ b/crates/pdk/src/mise.rs @@ -15,6 +15,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -110,4 +111,13 @@ impl Mise { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Mise { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/nix.rs b/crates/pdk/src/nix.rs index b629292..8bd1988 100644 --- a/crates/pdk/src/nix.rs +++ b/crates/pdk/src/nix.rs @@ -15,6 +15,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -110,4 +111,13 @@ impl Nix { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Nix { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/pipeline.rs b/crates/pdk/src/pipeline.rs index 674bf03..f4434eb 100644 --- a/crates/pdk/src/pipeline.rs +++ b/crates/pdk/src/pipeline.rs @@ -30,6 +30,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -184,4 +185,13 @@ impl Pipeline { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Pipeline { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/pixi.rs b/crates/pdk/src/pixi.rs index 83aa0c4..65a9d72 100644 --- a/crates/pdk/src/pixi.rs +++ b/crates/pdk/src/pixi.rs @@ -15,6 +15,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -110,4 +111,13 @@ impl Pixi { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Pixi { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/pkgx.rs b/crates/pdk/src/pkgx.rs index 9104d3e..8d1d5ba 100644 --- a/crates/pdk/src/pkgx.rs +++ b/crates/pdk/src/pkgx.rs @@ -16,6 +16,7 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -127,4 +128,13 @@ impl Pkgx { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Pkgx { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/proto.rs b/crates/pdk/src/proto.rs index 93b6fa6..03071ff 100644 --- a/crates/pdk/src/proto.rs +++ b/crates/pdk/src/proto.rs @@ -1,5 +1,7 @@ use extism_pdk::*; -use fluentci_types::{cache::Cache, file::File, proto as types, service::Service}; +use fluentci_types::{ + cache::Cache, file::File, proto as types, secret::Provider, service::Service, +}; use serde::{Deserialize, Serialize}; #[host_fn] @@ -15,6 +17,8 @@ extern "ExtismHost" { fn with_service(service_id: String); fn set_envs(envs: Json>); fn wait_on(args: Json>); + fn add_secretmanager(provider: Json) -> String; + fn with_secret_variable(params: Json>); } #[derive(Serialize, Deserialize)] @@ -110,4 +114,13 @@ impl Proto { id: self.id.clone(), }) } + + pub fn with_secret_variable(&self, name: &str, secret_id: &str) -> Result { + unsafe { + with_secret_variable(Json(vec![name.into(), secret_id.into()]))?; + } + Ok(Proto { + id: self.id.clone(), + }) + } } diff --git a/crates/pdk/src/secret.rs b/crates/pdk/src/secret.rs new file mode 100644 index 0000000..181c151 --- /dev/null +++ b/crates/pdk/src/secret.rs @@ -0,0 +1,31 @@ +use extism_pdk::{host_fn, Error, Json}; +use fluentci_types::secret as types; +use serde::{Deserialize, Serialize}; + +#[host_fn] +extern "ExtismHost" { + fn get_secret_plaintext(params: Json>) -> String; +} + +#[derive(Serialize, Deserialize)] +pub struct Secret { + pub id: String, + pub name: String, + pub mount: String, +} + +impl From for Secret { + fn from(secret: types::Secret) -> Self { + Secret { + id: secret.id, + name: secret.name, + mount: secret.mount, + } + } +} + +impl Secret { + pub fn plaintext(&self) -> Result { + unsafe { get_secret_plaintext(Json(vec![self.id.clone(), self.name.clone()])) } + } +} diff --git a/crates/pdk/src/secret_manager.rs b/crates/pdk/src/secret_manager.rs new file mode 100644 index 0000000..f4e25dd --- /dev/null +++ b/crates/pdk/src/secret_manager.rs @@ -0,0 +1,22 @@ +use extism_pdk::{host_fn, Error, Json}; +use fluentci_types::secret as types; +use serde::{Deserialize, Serialize}; + +use crate::secret::Secret; + +#[host_fn] +extern "ExtismHost" { + fn get_secret(params: Json>) -> Json>; +} + +#[derive(Serialize, Deserialize)] +pub struct SecretManager { + pub id: String, +} + +impl SecretManager { + pub fn get_secret(&self, name: &str) -> Result, Error> { + let results = unsafe { get_secret(Json(vec![self.id.clone(), name.into()]))? }; + Ok(results.into_inner().into_iter().map(Into::into).collect()) + } +} diff --git a/crates/secrets/Cargo.toml b/crates/secrets/Cargo.toml new file mode 100644 index 0000000..268dccc --- /dev/null +++ b/crates/secrets/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Tsiry Sandratraina "] +categories = ["command-line-utilities"] +description = "Programmable CI/CD engine without Containers, built on top of Nix ❄️" +edition = "2021" +keywords = ["nix", "environment", "ci", "wasm", "devops"] +license = "MPL-2.0" +name = "fluentci-secrets" +repository = "https://github.com/fluentci-io/fluentci-engine" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.69" +async-trait = "0.1.80" +azure_core = {version = "0.8.0", default-features = false, features = ["enable_reqwest_rustls"]} +azure_identity = {version = "0.9.0", default-features = false, features = ["enable_reqwest_rustls"]} +azure_security_keyvault = {version = "0.8.0", default-features = false, features = ["enable_reqwest_rustls"]} +base64 = {version = "0.21.0"} +fluentci-types = {path = "../types", version = "0.1.7"} +futures = "0.3.26" +google-secretmanager1 = {version = "4.0.1"} +reqwest = {version = "0.11.14", default-features = false, features = ["rustls-tls", "json"]} +rusoto_core = {version = "0.48.0", default-features = false, features = ["rustls"]} +rusoto_credential = {version = "0.48.0"} +rusoto_secretsmanager = {version = "0.48.0", default-features = false, features = ["rustls"]} +serde = {version = "1.0.152", features = ["derive"]} +serde_json = "1.0.92" +tempfile = "3.3.0" +thiserror = "1.0.38" +tokio = {version = "1.25.0", features = ["rt", "rt-multi-thread", "macros"]} diff --git a/crates/secrets/src/aws.rs b/crates/secrets/src/aws.rs new file mode 100644 index 0000000..cb22ac1 --- /dev/null +++ b/crates/secrets/src/aws.rs @@ -0,0 +1,149 @@ +use std::str::FromStr; + +use async_trait::async_trait; +use futures::future::try_join_all; +use rusoto_core::{request::TlsError, HttpClient, Region}; +use rusoto_credential::{CredentialsError, DefaultCredentialsProvider, StaticProvider}; +use rusoto_secretsmanager::{ + GetSecretValueError, GetSecretValueRequest, GetSecretValueResponse, ListSecretsError, + ListSecretsRequest, SecretsManager, SecretsManagerClient, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use thiserror::Error; + +use crate::{ + convert::{convert_env_name, decode_env_from_json}, + Vault, VaultConfig, +}; + +#[derive(Serialize, Deserialize)] +pub struct AwsConfig { + pub aws_access_key_id: Option, + pub aws_secret_access_key: Option, + pub aws_region: String, +} + +#[derive(Error, Debug)] +pub enum AwsError { + #[error("rusoto HttpClient error")] + TlsError(#[source] TlsError), + #[error("rusoto HttpClient error")] + CredentialsError(#[source] CredentialsError), + #[error("cannot load secret from Secrets Manager")] + GetSecretError(#[source] rusoto_core::RusotoError), + #[error("the secret does not have string data")] + NoStringData(String), + #[error("the secret name is not valid environment variable name")] + InvalidSecretName(String), + #[error("cannot list secrets from Secrets Manager")] + ListSecretsError(#[source] rusoto_core::RusotoError), + #[error("cannot decode secret - it is not a valid JSON object")] + DecodeError(#[source] serde_json::Error), + #[error("there are no secrets in the Secrets Manager")] + NoSecrets, +} + +pub type Result = std::result::Result; + +pub struct AwsVault { + client: SecretsManagerClient, +} + +impl VaultConfig for AwsConfig { + type Vault = AwsVault; + + fn into_vault(self) -> anyhow::Result { + let http_client = HttpClient::new().map_err(AwsError::TlsError)?; + if let Some(key_id) = self.aws_access_key_id { + let secret = self.aws_secret_access_key.unwrap(); + let provider = StaticProvider::new_minimal(key_id, secret); + Ok(Self::Vault { + client: SecretsManagerClient::new_with( + http_client, + provider, + Region::from_str(&self.aws_region)?, + ), + }) + } else { + let provider = DefaultCredentialsProvider::new().map_err(AwsError::CredentialsError)?; + Ok(Self::Vault { + client: SecretsManagerClient::new_with( + http_client, + provider, + Region::from_str(&self.aws_region)?, + ), + }) + } + } +} + +#[async_trait] +impl Vault for AwsVault { + async fn download_prefixed(&self, prefix: &str) -> anyhow::Result> { + let list = self + .client + .list_secrets(ListSecretsRequest { + max_results: Some(100), + ..Default::default() + }) + .await + .map_err(AwsError::ListSecretsError)?; + let results = list + .secret_list + .ok_or(AwsError::NoSecrets)? + .into_iter() + .filter(|x| { + x.name + .as_ref() + .map(|n| n.starts_with(prefix)) + .unwrap_or(false) + }) + .map(|s| async { + println!("{:?}", s); + let name = s.name.unwrap(); + let secret = self + .client + .get_secret_value(GetSecretValueRequest { + secret_id: name.clone(), + version_id: None, + version_stage: None, + }) + .await + .map_err(AwsError::GetSecretError)?; + println!("{:?}", secret); + let value = secret + .secret_string + .ok_or_else(|| AwsError::NoStringData(name.clone()))?; + let name = convert_env_name(prefix, &name) + .map_err(|_| AwsError::InvalidSecretName(name.clone()))?; + Ok::<_, AwsError>((name, value)) + }); + let values: Vec<_> = try_join_all(results).await?.into_iter().collect(); + Ok(values) + } + + async fn download_json(&self, secret_name: &str) -> anyhow::Result> { + let secret = self + .client + .get_secret_value(GetSecretValueRequest { + secret_id: secret_name.to_string(), + version_id: None, + version_stage: None, + }) + .await + .map_err(AwsError::GetSecretError)?; + let value = decode_secret(secret)?; + decode_env_from_json(secret_name, value) + } +} + +fn decode_secret(secret: GetSecretValueResponse) -> Result { + secret + .secret_string + .as_ref() + .map(|x| serde_json::from_str(&x[..])) + .or_else(|| secret.secret_binary.map(|b| serde_json::from_slice(&b))) + .unwrap() + .map_err(AwsError::DecodeError) +} diff --git a/crates/secrets/src/azure.rs b/crates/secrets/src/azure.rs new file mode 100644 index 0000000..049bbd2 --- /dev/null +++ b/crates/secrets/src/azure.rs @@ -0,0 +1,180 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use azure_core::auth::TokenCredential; +use azure_identity::{ + ClientSecretCredential, DefaultAzureCredentialBuilder, TokenCredentialOptions, +}; +use azure_security_keyvault::prelude::*; +use futures::future::try_join_all; +use futures::stream::StreamExt; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use thiserror::Error; + +use super::{ + convert::{convert_env_name, decode_env_from_json}, + Vault, VaultConfig, +}; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct AzureConfig { + pub credential: AzureCredential, + pub azure_keyvault_name: Option, + pub azure_keyvault_url: Option, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct AzureCredential { + pub azure_tenant_id: Option, + pub azure_client_id: Option, + pub azure_client_secret: Option, +} + +#[derive(Error, Debug)] +pub enum AzureError { + #[error("Azure configuration error")] + WrongConfiguration(#[source] anyhow::Error), + #[error("cannot create Azure KeyVault client")] + ClientError(#[source] azure_core::Error), + #[error("cannot download secret")] + CannotDownloadSecrets(#[source] azure_core::Error), + #[error("cannot decode secret - it is not a valid JSON")] + DecodeError(#[source] serde_json::Error), +} + +pub struct AzureVault { + kv_address: String, + credential: Arc, +} + +pub type Result = std::result::Result; + +impl AzureCredential { + fn is_valid(&self) -> bool { + self.azure_tenant_id.is_some() + && self.azure_client_id.is_some() + && self.azure_client_secret.is_some() + } + + fn validate(&self) -> Result<()> { + let has_some = self.azure_tenant_id.is_some() + || self.azure_client_id.is_some() + || self.azure_client_secret.is_some(); + if has_some && !self.is_valid() { + Err(AzureError::WrongConfiguration(anyhow::Error::msg( + "if you want to use CLI-passed credentials, all need to be specified", + ))) + } else { + Ok(()) + } + } + + fn to_credential(&self) -> Result> { + self.validate()?; + if self.is_valid() { + let creds = ClientSecretCredential::new( + azure_core::new_http_client(), + self.azure_tenant_id.clone().unwrap(), + self.azure_client_id.clone().unwrap(), + self.azure_client_secret.clone().unwrap(), + TokenCredentialOptions::default(), + ); + Ok(Arc::new(creds)) + } else { + let creds = DefaultAzureCredentialBuilder::new() + .exclude_environment_credential() + .build(); + Ok(Arc::new(creds)) + } + } +} + +impl AzureConfig { + fn get_kv_address(&self) -> Result { + if let Some(url) = &self.azure_keyvault_url { + Ok(url.to_string()) + } else if let Some(name) = &self.azure_keyvault_name { + Ok(format!("https://{name}.vault.azure.net")) + } else { + Err(AzureError::WrongConfiguration(anyhow::Error::msg( + "configuration is invalid", + ))) + } + } +} + +impl VaultConfig for AzureConfig { + type Vault = AzureVault; + + fn into_vault(self) -> anyhow::Result { + let kv_address = self.get_kv_address()?; + let credential = self.credential.to_credential()?; + Ok(AzureVault { + kv_address, + credential, + }) + } +} + +impl AzureVault { + fn get_client(&self) -> Result { + SecretClient::new(&self.kv_address, self.credential.clone()) + .map_err(AzureError::ClientError) + } + + fn strip_prefix(name: &str) -> &str { + let idx = name.rfind('/').unwrap(); + &name[(idx + 1)..] + } +} + +#[async_trait] +impl Vault for AzureVault { + async fn download_prefixed(&self, prefix: &str) -> anyhow::Result> { + let client = self.get_client()?; + + let secrets = client + .list_secrets() + .into_stream() + .collect::>() + .await + .into_iter() + .collect::, _>>() + .map_err(AzureError::CannotDownloadSecrets)?; + let secrets: Vec<_> = secrets + .into_iter() + .flat_map(|x| x.value.into_iter().map(|x| x.id)) + .map(|x| AzureVault::strip_prefix(&x).to_string()) + .filter(|x| x.starts_with(prefix)) + .collect(); + let env_names = secrets + .iter() + .map(|x| convert_env_name(prefix, x)) + .collect::>>()?; + let env_values = secrets.iter().map(|s| { + let client = self.get_client(); + async move { + client? + .get(s) + .into_future() + .await + .map_err(AzureError::CannotDownloadSecrets) + } + }); + let env_values = try_join_all(env_values).await?.into_iter().map(|x| x.value); + let from_kv = env_names.into_iter().zip(env_values.into_iter()).collect(); + Ok(from_kv) + } + + async fn download_json(&self, secret_name: &str) -> anyhow::Result> { + let client = self.get_client()?; + let secret = client + .get(secret_name) + .into_future() + .await + .map_err(AzureError::CannotDownloadSecrets)?; + let value: Value = serde_json::from_str(&secret.value).map_err(AzureError::DecodeError)?; + decode_env_from_json(secret_name, value) + } +} diff --git a/crates/secrets/src/convert.rs b/crates/secrets/src/convert.rs new file mode 100644 index 0000000..d7275f4 --- /dev/null +++ b/crates/secrets/src/convert.rs @@ -0,0 +1,205 @@ +use anyhow::{bail, Result}; +use serde_json::Value; + +pub fn as_valid_env_name(name: String) -> Result { + let is_valid = |c: char| c.is_ascii_alphanumeric() || c == '_'; + if !name.is_empty() + && name.chars().all(is_valid) + && name + .chars() + .next() + .map_or(false, |c| c.is_ascii_alphabetic()) + { + Ok(name) + } else { + bail!("secret name '{}' is invalid", name) + } +} + +#[allow(dead_code)] +pub fn value_as_string(name: &str, v: Value) -> Result { + match v { + Value::String(s) => Ok(s), + Value::Bool(b) => Ok(format!("{b}")), + Value::Number(n) => Ok(format!("{n}")), + Value::Null => Ok("null".to_string()), + _ => bail!("secret '{}' value is not convertible", name), + } +} + +#[allow(dead_code)] +pub fn convert_env_name(prefix: &str, name: &str) -> Result { + let name = name[prefix.len()..].replace('-', "_"); + as_valid_env_name(name) +} + +#[allow(dead_code)] +pub fn decode_env_from_json(name: &str, value: Value) -> Result> { + match value { + Value::Object(m) => m + .into_iter() + .map(|(k, v)| Ok((as_valid_env_name(k)?, value_as_string(name, v)?))) + .collect(), + _ => bail!( + "top-level value for secret '{}' must be a JSON object", + name + ), + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use std::collections::HashMap; + + use super::*; + + macro_rules! assert_invalid_secret { + ($a:expr) => { + assert!(matches!($a, Err(_))); + }; + } + + #[test] + fn as_valid_env_name_correct() { + macro_rules! assert_convert { + ($a:expr) => { + assert_eq!($a, as_valid_env_name($a.to_string()).unwrap()); + }; + } + + assert_convert!("abc"); + assert_convert!("abc123"); + assert_convert!("ab_12_ab"); + } + + #[test] + fn as_valid_env_name_invalid() { + macro_rules! assert_fail { + ($a:expr) => { + assert_invalid_secret!(as_valid_env_name($a.to_string())); + }; + } + + assert_fail!(""); + assert_fail!("123abc"); + assert_fail!("ab!"); + assert_fail!("ab-ab"); + } + + #[test] + fn value_as_string_for_normal_values() { + macro_rules! assert_convert { + ($a:expr, $b:expr) => { + assert_eq!($a, value_as_string("ignored", $b).unwrap()); + }; + } + + assert_convert!("abcd", json!("abcd")); + assert_convert!("12", json!(12)); + assert_convert!("12.123", json!(12.123)); + assert_convert!("null", json!(null)); + assert_convert!("false", json!(false)); + assert_convert!("true", json!(true)); + } + + #[test] + fn value_as_string_for_arrays_and_objects() { + macro_rules! assert_fail { + ($a:expr) => { + assert_invalid_secret!(value_as_string("ignored", $a)); + }; + } + + assert_fail!(json!({ "a": 123 })); + assert_fail!(json!([1, 2])); + } + + #[test] + fn decode_env_from_json_correct_values() { + macro_rules! assert_decode { + ($a:expr, $($name:ident = $value:expr),*) => { + #[allow(unused_variables, unused_mut)] + { + let decoded = decode_env_from_json("ignored", $a).unwrap(); + let len = decoded.len(); + let mapped = decoded.into_iter().collect::>(); + let mut total = 0; + $( + total += 1; + let name = stringify!($name); + let value = $value.to_string(); + assert_eq!(Some(&value), mapped.get(&name[..])); + )* + assert_eq!(total, len); + } + }; + } + + assert_decode!(json!({}),); + assert_decode!(json!({"a": 1}), a = "1"); + assert_decode!(json!({"a": 1, "b": true}), a = "1", b = "true"); + assert_decode!( + json!({"a": 1, "b": true, "c": "test"}), + a = "1", + b = "true", + c = "test" + ); + } + + #[test] + fn decode_env_from_json_invalid() { + macro_rules! assert_fail { + ($a:expr) => { + assert_invalid_secret!(decode_env_from_json("ignored", $a)); + }; + } + + assert_fail!(json!([1, 2])); + assert_fail!(json!("test")); + assert_fail!(json!(false)); + assert_fail!(json!(true)); + assert_fail!(json!({"a!": 1})); + assert_fail!(json!({"1a": 1})); + assert_fail!(json!({"a": {"b": 1}})); + } + + #[test] + fn convert_env_name_converts_names() { + macro_rules! assert_convert { + ($a:expr, $b:expr) => { + assert_convert!("", $a, $b); + }; + ($prefix:expr, $a:expr, $b:expr) => { + assert_eq!($a, convert_env_name($prefix, $b).unwrap()); + }; + } + + assert_convert!("abc", "abc"); + assert_convert!("abc123", "abc123"); + assert_convert!("abc_123", "abc-123"); + assert_convert!("abc__123", "abc--123"); + assert_convert!("abc_123", "abc_123"); + + assert_convert!("zxc", "abc", "zxcabc"); + assert_convert!("zxc", "abc", "abcabc"); + } + + #[test] + fn convert_env_name_invalid() { + macro_rules! assert_fail { + ($a:expr) => { + assert_fail!("", $a); + }; + ($prefix:expr, $a:expr) => { + assert_invalid_secret!(convert_env_name($prefix, $a)); + }; + } + + assert_fail!(""); + assert_fail!("!"); + assert_fail!("abc!"); + assert_fail!("123abc"); + assert_fail!("abc", "abc"); + } +} diff --git a/crates/secrets/src/google.rs b/crates/secrets/src/google.rs new file mode 100644 index 0000000..187ff3d --- /dev/null +++ b/crates/secrets/src/google.rs @@ -0,0 +1,187 @@ +use async_trait::async_trait; +use base64::{engine::general_purpose::STANDARD as base64, Engine as _}; +use google_secretmanager1::{ + hyper, hyper::client::HttpConnector, hyper_rustls, hyper_rustls::HttpsConnector, oauth2, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::path::PathBuf; +use thiserror::Error; + +use super::{convert::decode_env_from_json, Vault, VaultConfig}; + +type SecretManager = google_secretmanager1::SecretManager>; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct GoogleConfig { + pub google_credentials_file: Option, + pub google_credentials_json: Option, + pub google_project: Option, +} + +#[derive(Error, Debug)] +pub enum GoogleError { + #[error("Google SA configuration is invalid")] + ConfigurationError(#[source] std::io::Error), + #[error("secret manager operation failed")] + SecretManagerError(#[source] google_secretmanager1::Error), + #[error("the secret is empty")] + EmptySecret, + #[error("there are no secrets in the project")] + NoSecrets, + #[error("secret encoding is invalid")] + WrongEncoding(#[source] anyhow::Error), + #[error("cannot decode secret - it is not a valid JSON")] + DecodeError(#[source] serde_json::Error), +} + +pub type Result = std::result::Result; + +impl VaultConfig for GoogleConfig { + type Vault = GoogleConfig; + + fn into_vault(self) -> anyhow::Result { + Ok(self) + } +} + +impl GoogleConfig { + async fn to_manager(&self) -> Result { + let auth = self + .to_authenticator() + .await + .map_err(GoogleError::ConfigurationError)?; + let manager = SecretManager::new( + hyper::Client::builder().build( + hyper_rustls::HttpsConnectorBuilder::new() + .with_native_roots() + .https_or_http() + .enable_http1() + .enable_http2() + .build(), + ), + auth, + ); + Ok(manager) + } + + async fn to_authenticator( + &self, + ) -> std::io::Result>> { + if let Some(path) = &self.google_credentials_file { + let key = oauth2::read_service_account_key(path).await?; + let auth = oauth2::ServiceAccountAuthenticator::builder(key) + .build() + .await?; + Ok(auth) + } else if let Some(json) = &self.google_credentials_json { + let key = oauth2::parse_service_account_key(json)?; + let auth = oauth2::ServiceAccountAuthenticator::builder(key) + .build() + .await?; + Ok(auth) + } else { + let opts = oauth2::ApplicationDefaultCredentialsFlowOpts::default(); + let auth = match oauth2::ApplicationDefaultCredentialsAuthenticator::builder(opts).await + { + oauth2::authenticator::ApplicationDefaultCredentialsTypes::ServiceAccount(auth) => { + auth.build().await? + } + oauth2::authenticator::ApplicationDefaultCredentialsTypes::InstanceMetadata( + auth, + ) => auth.build().await?, + }; + Ok(auth) + } + } +} + +#[async_trait] +impl Vault for GoogleConfig { + async fn download_prefixed(&self, prefix: &str) -> anyhow::Result> { + let mut manager = self.to_manager().await?; + let project = self.google_project.as_ref().unwrap(); + let response = manager + .projects() + .secrets_list(&format!("projects/{project}")) + .page_size(250) + .doit() + .await + .map_err(GoogleError::SecretManagerError)?; + let secrets: Vec<_> = response + .1 + .secrets + .ok_or(GoogleError::NoSecrets)? + .into_iter() + .filter(|f| f.name.is_some()) + .filter(|f| self.secret_matches(prefix, f.name.as_ref().unwrap())) + .collect(); + let mut from_kv = Vec::with_capacity(secrets.len()); + for secret in secrets { + let value = self + .get_secret_full_name(&mut manager, secret.name.as_ref().unwrap()) + .await?; + let name = self + .strip_prefix(prefix, secret.name.as_ref().unwrap()) + .to_string(); + from_kv.push((name, value)); + } + Ok(from_kv) + } + + async fn download_json(&self, secret_name: &str) -> anyhow::Result> { + let mut manager = self.to_manager().await?; + let secret = self.get_secret(&mut manager, secret_name).await?; + let value: Value = serde_json::from_str(&secret).map_err(GoogleError::DecodeError)?; + decode_env_from_json(secret_name, value) + } +} + +impl GoogleConfig { + fn strip_project<'a>(&'_ self, name: &'a str) -> &'a str { + let idx = name.rfind('/').unwrap(); + &name[(idx + 1)..] + } + + fn secret_matches(&self, prefix: &str, name: &str) -> bool { + self.strip_project(name).starts_with(prefix) + } + + fn strip_prefix<'a>(&'_ self, prefix: &'_ str, name: &'a str) -> &'a str { + &self.strip_project(name)[prefix.len()..] + } + + async fn get_secret(&self, client: &mut SecretManager, secret_name: &str) -> Result { + self.get_secret_full_name( + client, + &format!( + "projects/{}/secrets/{}", + self.google_project.as_ref().unwrap(), + secret_name + ), + ) + .await + } + + async fn get_secret_full_name( + &self, + manager: &mut SecretManager, + name: &str, + ) -> Result { + let data = manager + .projects() + .secrets_versions_access(&format!("{name}/versions/latest")) + .doit() + .await + .map_err(GoogleError::SecretManagerError)? + .1 + .payload + .ok_or(GoogleError::EmptySecret)? + .data + .ok_or(GoogleError::EmptySecret)?; + let raw_bytes = base64 + .decode(data) + .map_err(|e| GoogleError::WrongEncoding(anyhow::anyhow!(e)))?; + String::from_utf8(raw_bytes).map_err(|e| GoogleError::WrongEncoding(anyhow::anyhow!(e))) + } +} diff --git a/crates/secrets/src/lib.rs b/crates/secrets/src/lib.rs new file mode 100644 index 0000000..7919b4a --- /dev/null +++ b/crates/secrets/src/lib.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +pub mod aws; +pub mod azure; +pub mod convert; +pub mod google; +pub mod vault; + +#[derive(Serialize, Deserialize)] +pub enum Provider { + Google(google::GoogleConfig), + Aws(aws::AwsConfig), + Azure(azure::AzureConfig), + Hashicorp(vault::HashicorpVaultConfig), +} + +#[async_trait] +pub trait Vault { + async fn download_prefixed(&self, prefix: &str) -> Result>; + async fn download_json(&self, secret_name: &str) -> Result>; +} + +pub trait VaultConfig { + type Vault: Vault; + fn into_vault(self) -> Result; +} + +pub fn download_env() {} diff --git a/crates/secrets/src/vault.rs b/crates/secrets/src/vault.rs new file mode 100644 index 0000000..84afe44 --- /dev/null +++ b/crates/secrets/src/vault.rs @@ -0,0 +1,193 @@ +use std::{collections::HashMap, path::PathBuf}; + +use async_trait::async_trait; +use futures::future::try_join_all; +use reqwest::{self, StatusCode}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use tokio::io::AsyncReadExt; + +use super::{convert::as_valid_env_name, Vault, VaultConfig}; + +#[derive(Serialize, Deserialize)] +pub struct HashicorpVaultConfig { + pub vault_address: Option, + pub vault_token: Option, + pub vault_cacert: Option, +} + +#[derive(Error, Debug)] +pub enum HashicorpVaultError { + #[error("secret '{0}' does not exist")] + SecretNotFound(String), + + #[error("the vault token is invalid")] + UnauthorizedError, + + #[error("the token does not have access to secret '{0}'")] + ForbiddenError(String), + + #[error("HTTP error occurred")] + HttpError(#[source] reqwest::Error), + + #[error("the Vault returned non-200 error code")] + HttpStatusCodeError(StatusCode), + + #[error("cannot deserialize the response")] + DeserializeError(#[source] reqwest::Error), + + #[error("the keys in the secret are not valid env names")] + InvalidEnv(#[source] anyhow::Error), + + #[error("the configuration is invalid")] + ConfigurationError(#[from] anyhow::Error), +} + +pub struct HashicorpVault { + address: String, + token: String, + cacert: Option, +} + +impl VaultConfig for HashicorpVaultConfig { + type Vault = HashicorpVault; + + fn into_vault(self) -> anyhow::Result { + Ok(Self::Vault { + address: self.vault_address.unwrap(), + token: self.vault_token.unwrap(), + cacert: self.vault_cacert, + }) + } +} + +impl HashicorpVault { + async fn client(&self) -> Result { + let mut builder = reqwest::Client::builder().user_agent("fluentci-engine"); + + if let Some(path) = self.cacert.as_ref() { + let mut buffer = Vec::new(); + { + let mut file = tokio::fs::File::open(path) + .await + .map_err(anyhow::Error::new)?; + file.read_to_end(&mut buffer) + .await + .map_err(anyhow::Error::new)?; + } + let cert = reqwest::Certificate::from_pem(&buffer).map_err(anyhow::Error::new)?; + builder = builder.add_root_certificate(cert); + } + + builder + .build() + .map_err(anyhow::Error::new) + .map_err(HashicorpVaultError::ConfigurationError) + } + + fn parse_secrets(secret: SecretResponse) -> Result, HashicorpVaultError> { + secret + .data + .data + .into_iter() + .map(|(k, v)| as_valid_env_name(k).map(|k| (k, v))) + .collect::>>() + .map_err(HashicorpVaultError::InvalidEnv) + } + + async fn get_single_key( + &self, + client: &reqwest::Client, + secret_name: impl AsRef, + ) -> Result, HashicorpVaultError> { + let response = client + .get(format!( + "{}/v1/secret/data/{}", + self.address, + secret_name.as_ref() + )) + .header("X-Vault-Token", &self.token) + .send() + .await + .map_err(HashicorpVaultError::HttpError)?; + handle_common_errors(secret_name.as_ref(), &response)?; + + let data: SecretResponse = response + .json() + .await + .map_err(HashicorpVaultError::DeserializeError)?; + Self::parse_secrets(data) + } +} + +#[async_trait] +impl Vault for HashicorpVault { + async fn download_prefixed(&self, prefix: &str) -> anyhow::Result> { + let client = self.client().await?; + + let response = client + .get(format!("{}/v1/secret/metadata?list=true", self.address)) + .header("X-Vault-Token", &self.token) + .send() + .await + .map_err(HashicorpVaultError::HttpError)?; + handle_common_errors(prefix, &response)?; + + let list: ListResponse = response + .json() + .await + .map_err(HashicorpVaultError::DeserializeError)?; + + let env_values = list + .data + .keys + .into_iter() + .filter(|p| p.starts_with(prefix)) + .map(|s| self.get_single_key(&client, s)); + let env_values: Vec<_> = try_join_all(env_values) + .await? + .into_iter() + .flatten() + .collect(); + Ok(env_values) + } + + async fn download_json(&self, secret_name: &str) -> anyhow::Result> { + let client = self.client().await?; + let result = self.get_single_key(&client, secret_name).await?; + Ok(result) + } +} + +fn handle_common_errors( + secret_name: &str, + response: &reqwest::Response, +) -> Result<(), HashicorpVaultError> { + match response.status() { + StatusCode::NOT_FOUND => Err(HashicorpVaultError::SecretNotFound(secret_name.to_string())), + StatusCode::UNAUTHORIZED => Err(HashicorpVaultError::UnauthorizedError), + StatusCode::FORBIDDEN => Err(HashicorpVaultError::ForbiddenError(secret_name.to_string())), + StatusCode::OK => Ok(()), + other => Err(HashicorpVaultError::HttpStatusCodeError(other)), + } +} + +#[derive(Deserialize, Debug)] +struct SecretResponse { + pub data: Secret, +} + +#[derive(Deserialize, Debug)] +struct Secret { + pub data: HashMap, +} + +#[derive(Deserialize, Debug)] +struct ListResponse { + pub data: KeyList, +} + +#[derive(Deserialize, Debug)] +struct KeyList { + pub keys: Vec, +} diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index e0121e7..7f5327d 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -7,7 +7,7 @@ keywords = ["nix", "environment", "ci", "wasm", "devops"] license = "MPL-2.0" name = "fluentci-server" repository = "https://github.com/fluentci-io/fluentci-engine" -version = "0.2.1" +version = "0.3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,9 +17,9 @@ actix-web = "4.5.1" anyhow = "1.0.81" async-graphql = "7.0.2" async-graphql-actix-web = "7.0.2" -fluentci-core = {path = "../core", version = "0.2.1"} +fluentci-core = {path = "../core", version = "0.3.0"} fluentci-ext = {path = "../ext", version = "0.2.0"} -fluentci-graphql = {path = "../graphql", version = "0.2.1"} +fluentci-graphql = {path = "../graphql", version = "0.3.0"} mime_guess = "2.0.4" owo-colors = "4.0.0" tokio = "1.36.0" diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index ab2e0e4..cd89e76 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -7,17 +7,19 @@ keywords = ["nix", "environment", "ci", "wasm", "devops"] license = "MPL-2.0" name = "fluentci-shared" repository = "https://github.com/fluentci-io/fluentci-engine" -version = "0.1.9" +version = "0.2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.82" extism = "1.2.0" extism-pdk = "1.1.0" -fluentci-common = {path = "../common", version = "0.1.8"} -fluentci-core = {path = "../core", version = "0.2.1"} +fluentci-common = {path = "../common", version = "0.2.0"} +fluentci-core = {path = "../core", version = "0.3.0"} fluentci-ext = {path = "../ext", version = "0.2.0"} -fluentci-types = {path = "../types", version = "0.1.6"} +fluentci-secrets = {path = "../secrets", version = "0.1.0"} +fluentci-types = {path = "../types", version = "0.1.7"} serde = {version = "1.0.197", features = ["serde_derive", "derive"]} uuid = {version = "1.8.0", features = [ "v4", diff --git a/crates/shared/src/shared.rs b/crates/shared/src/shared.rs index 2722b74..a72ee3e 100644 --- a/crates/shared/src/shared.rs +++ b/crates/shared/src/shared.rs @@ -1,5 +1,4 @@ -use std::sync::Arc; - +use anyhow::Error; use extism::{convert::Json, host_fn}; use extism::{Manifest, PluginBuilder, Wasm, PTR}; use fluentci_common::common; @@ -15,9 +14,11 @@ use fluentci_ext::pixi::Pixi as PixiExt; use fluentci_ext::pkgx::Pkgx as PkgxExt; use fluentci_ext::runner::Runner as RunnerExt; use fluentci_ext::Extension; +use fluentci_secrets::Provider; use fluentci_types::cache::Cache; use fluentci_types::file::File; use fluentci_types::Module; +use std::sync::Arc; use crate::{ cache::*, devbox::*, devenv::*, directory::*, envhub::*, file::*, flox::*, git::*, http::*, @@ -50,7 +51,7 @@ host_fn!(pub with_exec(user_data: State; args: Json>) { "pkgx" => Arc::new(Box::new(PkgxExt::default())), _ => Arc::new(Box::new(RunnerExt::default())) }; - common::with_exec(graph, args.into_inner(), runner); + common::with_exec(graph, args.into_inner(), runner)?; Ok(()) }); @@ -228,6 +229,12 @@ host_fn!(pub call(user_data: State; opts: Json) -> String { .with_function("with_packages", [PTR], [], user_data.clone(), with_packages) .with_function("as_service", [PTR], [PTR], user_data.clone(), as_service) .with_function("with_service", [PTR], [], user_data.clone(), with_service) + .with_function("wait_on", [PTR], [], user_data.clone(), wait_on) + .with_function("add_secretmanager", [PTR], [PTR], user_data.clone(), add_secretmanager) + .with_function("get_secret", [PTR], [PTR], user_data.clone(), get_secret) + .with_function("set_secret", [PTR], [PTR], user_data.clone(), set_secret) + .with_function("with_secret_variable", [PTR], [], user_data.clone(), with_secret_variable) + .with_function("get_secret_plaintext", [PTR], [PTR], user_data.clone(), get_secret_plaintext) .build() .unwrap(); @@ -252,3 +259,71 @@ host_fn!(pub wait_on(user_data: State; args: Json>) { } Ok(()) }); + +host_fn!(pub add_secretmanager(user_data: State; provider: Json) -> String { + let state = user_data.get()?; + let state = state.lock().unwrap(); + let graph = state.graph.clone(); + let provider = provider.into_inner(); + let id = common::add_secretmanager(graph, provider)?; + Ok(id) +}); + +host_fn!(pub get_secret(user_data: State; params: Json>) -> Json> { + let state = user_data.get()?; + let state = state.lock().unwrap(); + let graph = state.graph.clone(); + let params = params.into_inner(); + + if params.len() != 2 { + return Err(Error::msg("Invalid number of arguments")); + } + + let secret = common::get_secret(graph, ¶ms[0], ¶ms[1])?; + Ok(Json(secret)) +}); + +host_fn!(pub with_secret_variable(user_data: State; params: Json>) { + let state = user_data.get()?; + let state = state.lock().unwrap(); + let graph = state.graph.clone(); + let params = params.into_inner(); + if params.len() != 2 { + return Err(Error::msg("Invalid number of arguments")); + } + let g = graph.lock().unwrap(); + let secret_name = g.secret_names.get(¶ms[1]).unwrap(); + let secret_name = secret_name.clone(); + drop(g); + + common::with_secret_variable(graph, ¶ms[0], ¶ms[1], &secret_name)?; + Ok(()) +}); + +host_fn!(pub set_secret(user_data: State; params: Json>) -> String { + let state = user_data.get()?; + let state = state.lock().unwrap(); + let graph = state.graph.clone(); + let params = params.into_inner(); + + if params.len() != 2 { + return Err(Error::msg("Invalid number of arguments")); + } + + let secret_id = common::set_secret(graph, ¶ms[0], ¶ms[1])?; + Ok(secret_id) +}); + +host_fn!(pub get_secret_plaintext(user_data: State; params: Json>) -> String { + let state = user_data.get()?; + let state = state.lock().unwrap(); + let graph = state.graph.clone(); + let params = params.into_inner(); + + if params.len() != 2 { + return Err(Error::msg("Invalid number of arguments")); + } + + let secret = common::get_secret_plaintext(graph, ¶ms[0], ¶ms[1])?; + Ok(secret) +}); diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index dea536b..f352a93 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" keywords = ["nix", "environment", "ci", "wasm", "devops"] license = "MPL-2.0" name = "fluentci-types" -version = "0.1.6" +version = "0.1.7" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 8f6da02..1569937 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -15,6 +15,7 @@ pub mod pixi; pub mod pkgx; pub mod process_compose; pub mod proto; +pub mod secret; pub mod service; #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/crates/types/src/secret.rs b/crates/types/src/secret.rs new file mode 100644 index 0000000..d9160cd --- /dev/null +++ b/crates/types/src/secret.rs @@ -0,0 +1,53 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub enum Provider { + Google(GoogleConfig), + Aws(AwsConfig), + Azure(AzureConfig), + Hashicorp(HashicorpVaultConfig), +} + +#[derive(Serialize, Deserialize)] +pub struct Secret { + pub id: String, + pub name: String, + pub mount: String, +} + +#[derive(Serialize, Deserialize, Default)] +pub struct AwsConfig { + pub aws_access_key_id: Option, + pub aws_secret_access_key: Option, + pub aws_region: String, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct AzureConfig { + pub credential: AzureCredential, + pub azure_keyvault_name: Option, + pub azure_keyvault_url: Option, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct AzureCredential { + pub azure_tenant_id: Option, + pub azure_client_id: Option, + pub azure_client_secret: Option, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct GoogleConfig { + pub google_credentials_file: Option, + pub google_credentials_json: Option, + pub google_project: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct HashicorpVaultConfig { + pub vault_address: Option, + pub vault_token: Option, + pub vault_cacert: Option, +} diff --git a/examples/archive/Cargo.toml b/examples/archive/Cargo.toml index 5e11e1e..1558896 100644 --- a/examples/archive/Cargo.toml +++ b/examples/archive/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/chmod/Cargo.toml b/examples/chmod/Cargo.toml index 7e8a9b0..be0d5f7 100644 --- a/examples/chmod/Cargo.toml +++ b/examples/chmod/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/devbox/Cargo.toml b/examples/devbox/Cargo.toml index 52b409c..15ab974 100644 --- a/examples/devbox/Cargo.toml +++ b/examples/devbox/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/env/Cargo.toml b/examples/env/Cargo.toml index f0f5440..13289a2 100644 --- a/examples/env/Cargo.toml +++ b/examples/env/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.5"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/flox/Cargo.toml b/examples/flox/Cargo.toml index e7e917a..8cd4302 100644 --- a/examples/flox/Cargo.toml +++ b/examples/flox/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/flox/src/lib.rs b/examples/flox/src/lib.rs index 5318a24..189658b 100644 --- a/examples/flox/src/lib.rs +++ b/examples/flox/src/lib.rs @@ -3,11 +3,9 @@ use fluentci_pdk::dag; #[plugin_fn] pub fn exec(command: String) -> FnResult { - let cache_id = dag().cache("flox")?.id; let stdout = dag() .flox()? .with_workdir("./flox-demo")? - .with_cache("./.flox", &cache_id)? .with_exec(command.split_whitespace().collect())? .stdout()?; Ok(stdout) diff --git a/examples/git/Cargo.toml b/examples/git/Cargo.toml index 86dfa20..9fbff63 100644 --- a/examples/git/Cargo.toml +++ b/examples/git/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/hash/Cargo.toml b/examples/hash/Cargo.toml index 39651a8..189b173 100644 --- a/examples/hash/Cargo.toml +++ b/examples/hash/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/http/Cargo.toml b/examples/http/Cargo.toml index 88a0f97..ea3e593 100644 --- a/examples/http/Cargo.toml +++ b/examples/http/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/mise/Cargo.toml b/examples/mise/Cargo.toml index 951e457..2089a01 100644 --- a/examples/mise/Cargo.toml +++ b/examples/mise/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/nix/Cargo.toml b/examples/nix/Cargo.toml index 28832eb..e6763b8 100644 --- a/examples/nix/Cargo.toml +++ b/examples/nix/Cargo.toml @@ -10,5 +10,5 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} fluentci-types = {path = "../../crates/types", version = "0.1.4"} diff --git a/examples/pixi/Cargo.toml b/examples/pixi/Cargo.toml index 1e21671..4a3d1a0 100644 --- a/examples/pixi/Cargo.toml +++ b/examples/pixi/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/pkgx/Cargo.toml b/examples/pkgx/Cargo.toml index 8c6b05e..d276d54 100644 --- a/examples/pkgx/Cargo.toml +++ b/examples/pkgx/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/proto/Cargo.toml b/examples/proto/Cargo.toml index 9a4fa6d..3882d33 100644 --- a/examples/proto/Cargo.toml +++ b/examples/proto/Cargo.toml @@ -10,4 +10,4 @@ crate_type = ["cdylib"] [dependencies] extism-pdk = "1.1.0" -fluentci-pdk = {path = "../../crates/pdk", version = "0.1.3"} +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/vault/Cargo.toml b/examples/vault/Cargo.toml new file mode 100644 index 0000000..3ea0ed9 --- /dev/null +++ b/examples/vault/Cargo.toml @@ -0,0 +1,14 @@ +[package] +edition = "2021" +name = "vault" +version = "0.1.0" + +[lib] +crate_type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.82" +extism-pdk = "1.1.0" +fluentci-pdk = {path = "../../crates/pdk", version = "0.2.0"} diff --git a/examples/vault/src/lib.rs b/examples/vault/src/lib.rs new file mode 100644 index 0000000..0dc9da6 --- /dev/null +++ b/examples/vault/src/lib.rs @@ -0,0 +1,24 @@ +use anyhow::Error; +use extism_pdk::*; +use fluentci_pdk::dag; + +#[plugin_fn] +pub fn get_secret(name: String) -> FnResult { + let address = dag().get_env("VAULT_ADDR")?; + let token = dag().get_env("VAULT_TOKEN")?; + let secrets = dag() + .hashicorp_vault(&address, &token, None)? + .get_secret(&name)?; + + if secrets.is_empty() { + return Err(WithReturnCode::from(Error::msg("secret not found")).into()); + } + + let stdout = dag() + .pipeline("demo")? + .with_secret_variable("DEMO", &secrets[0].id)? + .with_exec(vec!["echo", "$DEMO"])? + .stdout()?; + + Ok(stdout) +} diff --git a/sdk/typescript/demo/main.ts b/sdk/typescript/demo/main.ts index 215fc91..bab7dab 100644 --- a/sdk/typescript/demo/main.ts +++ b/sdk/typescript/demo/main.ts @@ -6,27 +6,35 @@ async function main() { Deno.env.set("FLUENTCI_SESSION_TOKEN", "token"); } - const cacheId = await dag.cache("pixi").id(); + const secret = dag.setSecret("DEMO", "12345"); - console.log("cacheId: ", cacheId); + console.log(await secret.plaintext()); - const ping = await dag + const secretDemo = await dag + .pipeline("secret-demo") + .withSecretVariable("DEMO", secret) + .withExec(["echo", "$DEMO"]) + .stdout(); + + console.log("secretDemo: ", secretDemo); + + const cache = dag.cache("pixi"); + + console.log("cacheId: ", await cache.id()); + + const ping = dag .pipeline("demo") .nix() .withWorkdir("nix-demo") .withExec(["ping", "fluentci.io"]) - .asService("ping_fluentci") - .id(); - console.log(ping); + .asService("ping_fluentci"); - const pingGh = await dag + const pingGh = dag .pipeline("demo") .pkgx() .withPackages(["ping"]) .withExec(["ping", "github.com"]) - .asService("ping_gh") - .id(); - console.log(pingGh); + .asService("ping_gh"); const stdout = await dag .pipeline("demo") @@ -61,7 +69,7 @@ async function main() { .pipeline("pixi-demo") .pixi() .withWorkdir("./pixi-demo") - .withCache("./.pixi", cacheId) + .withCache("./.pixi", cache) .withExec(["pixi", "--version"]) .withExec(["which", "php"]) .stdout(); diff --git a/sdk/typescript/src/client.gen.ts b/sdk/typescript/src/client.gen.ts index f8f12f4..8725c01 100644 --- a/sdk/typescript/src/client.gen.ts +++ b/sdk/typescript/src/client.gen.ts @@ -77,7 +77,7 @@ export class Client extends BaseClient { pipeline = (name: string): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "pipeline", args: { name }, @@ -90,7 +90,7 @@ export class Client extends BaseClient { cache = (key: string): Cache => { return new Cache({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "cache", args: { key }, @@ -103,7 +103,7 @@ export class Client extends BaseClient { http = (url: string): File => { return new File({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "http", args: { url }, @@ -116,7 +116,7 @@ export class Client extends BaseClient { directory = (path: string): Directory => { return new Directory({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "directory", args: { path }, @@ -129,7 +129,7 @@ export class Client extends BaseClient { file = (path: string): File => { return new File({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "file", args: { path }, @@ -142,7 +142,7 @@ export class Client extends BaseClient { git = (url: string): Git => { return new Git({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "git", args: { url }, @@ -155,7 +155,7 @@ export class Client extends BaseClient { devbox = (): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "devbox", }, @@ -167,7 +167,7 @@ export class Client extends BaseClient { devenv = (): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "devenv", }, @@ -179,7 +179,7 @@ export class Client extends BaseClient { flox = (): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "flox", }, @@ -191,7 +191,7 @@ export class Client extends BaseClient { nix = (args?: NixArgs): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "nix", args: { args }, @@ -204,7 +204,7 @@ export class Client extends BaseClient { pkgx = (): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "pkgx", }, @@ -212,6 +212,102 @@ export class Client extends BaseClient { ctx: this._ctx, }); }; + + setSecret = (name: string, value: string): Secret => { + return new Secret({ + queryTree: [ + ...this._queryTree, + { + operation: "setSecret", + args: { name, value }, + }, + ], + ctx: this._ctx, + }); + }; + + googleCloudSecretManager = ( + project: string, + googleCredentialsFile: string + ): SecretManager => { + return new SecretManager({ + queryTree: [ + ...this._queryTree, + { + operation: "googleCloudSecretManager", + args: { project, googleCredentialsFile }, + }, + ], + ctx: this._ctx, + }); + }; + + awsSecretsManager = ( + region: string, + accessKeyId: string, + secretAccessKey: string + ): SecretManager => { + return new SecretManager({ + queryTree: [ + ...this._queryTree, + { + operation: "awsSecretsManager", + args: { + region, + accessKeyId, + secretAccessKey, + }, + }, + ], + ctx: this._ctx, + }); + }; + + azureKeyvault = ( + clientId: string, + clientSecret: string, + tenantId: string, + keyvaultName: string, + keyvaultUrl: string + ): SecretManager => { + return new SecretManager({ + queryTree: [ + ...this._queryTree, + { + operation: "azureKeyvault", + args: { + clientId, + clientSecret, + tenantId, + keyvaultName, + keyvaultUrl, + }, + }, + ], + ctx: this._ctx, + }); + }; + + hashicorpVault = ( + address: string, + token: string, + cacerts: string + ): SecretManager => { + return new SecretManager({ + queryTree: [ + ...this._queryTree, + { + operation: "hashicorpVault", + args: { + address, + token, + cacerts, + }, + }, + ], + ctx: this._ctx, + }); + }; } /** @@ -227,7 +323,7 @@ export class Cache extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -240,7 +336,7 @@ export class Cache extends BaseClient { key = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "key", }, @@ -251,6 +347,101 @@ export class Cache extends BaseClient { }; } +export class Secret extends BaseClient { + private readonly _id?: string = undefined; + + constructor(parent?: { queryTree?: QueryTree[]; ctx: Context }) { + super(parent); + } + + id = async (): Promise => { + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "id", + }, + ], + await this._ctx.connection() + ); + return response; + }; + + plaintext = async (): Promise => { + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "plaintext", + }, + ], + await this._ctx.connection() + ); + return response; + }; + + name = async (): Promise => { + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "name", + }, + ], + await this._ctx.connection() + ); + return response; + }; + + mount = async (): Promise => { + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "mount", + }, + ], + await this._ctx.connection() + ); + return response; + }; +} + +export class SecretManager extends BaseClient { + private readonly _id?: string = undefined; + + constructor(parent?: { queryTree?: QueryTree[]; ctx: Context }) { + super(parent); + } + + id = async (): Promise => { + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "id", + }, + ], + await this._ctx.connection() + ); + return response; + }; + + getSecret = async (name: string): Promise => { + const response: Awaited = await computeQuery( + [ + ...this._queryTree, + { + operation: "getSecret", + args: { name }, + }, + ], + await this._ctx.connection() + ); + return response; + }; +} + /** * A devbox environment. */ @@ -264,7 +455,7 @@ export class Devbox extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -277,7 +468,7 @@ export class Devbox extends BaseClient { withExec = (args: string[]): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -290,7 +481,7 @@ export class Devbox extends BaseClient { withWorkdir = (path: string): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -300,23 +491,36 @@ export class Devbox extends BaseClient { }); }; - withService = (serviceId: string): Devbox => { + withService = (service: Service): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, + }, + ], + ctx: this._ctx, + }); + }; + + withSecretVariable = (name: string, secret: Secret): Devbox => { + return new Devbox({ + queryTree: [ + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Devbox => { + withEnvVariable = (name: string, value: string): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -326,13 +530,13 @@ export class Devbox extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Devbox => { + withCache = (path: string, cache: Cache): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -342,7 +546,7 @@ export class Devbox extends BaseClient { withFile = (path: string, fileId: string): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -355,7 +559,7 @@ export class Devbox extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -368,7 +572,7 @@ export class Devbox extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -381,7 +585,7 @@ export class Devbox extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { name }, @@ -394,7 +598,7 @@ export class Devbox extends BaseClient { waitOn = (port: number, timeout?: number): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -418,7 +622,7 @@ export class Devenv extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -431,7 +635,7 @@ export class Devenv extends BaseClient { withExec = (args: string[]): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -444,7 +648,7 @@ export class Devenv extends BaseClient { withWorkdir = (path: string): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -454,23 +658,35 @@ export class Devenv extends BaseClient { }); }; - withService = (serviceId: string): Devenv => { + withService = (service: Service): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Devenv => { + withSecretVariable = (name: string, secret: Secret): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, + }, + ], + ctx: this._ctx, + }); + }; + withEnvVariable = (name: string, value: string): Devenv => { + return new Devenv({ + queryTree: [ + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -480,13 +696,13 @@ export class Devenv extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Devenv => { + withCache = (path: string, cache: Cache): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -496,7 +712,7 @@ export class Devenv extends BaseClient { withFile = (path: string, fileId: string): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -509,7 +725,7 @@ export class Devenv extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -522,7 +738,7 @@ export class Devenv extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -535,7 +751,7 @@ export class Devenv extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { @@ -550,7 +766,7 @@ export class Devenv extends BaseClient { waitOn = (port: number, timeout?: number): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -574,7 +790,7 @@ export class Directory extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -587,7 +803,7 @@ export class Directory extends BaseClient { directory = (path: string): Directory => { return new Directory({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "directory", args: { path }, @@ -600,7 +816,7 @@ export class Directory extends BaseClient { entries = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "entries", }, @@ -613,7 +829,7 @@ export class Directory extends BaseClient { devbox = (): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "devbox", }, @@ -625,7 +841,7 @@ export class Directory extends BaseClient { devenv = (): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "devenv", }, @@ -637,7 +853,7 @@ export class Directory extends BaseClient { flox = (): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "flox", }, @@ -649,7 +865,7 @@ export class Directory extends BaseClient { nix = (args?: NixArgs): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "nix", args: { args }, @@ -662,7 +878,7 @@ export class Directory extends BaseClient { pkgx = (): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "pkgx", }, @@ -674,7 +890,7 @@ export class Directory extends BaseClient { pixi = (): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "pixi", }, @@ -686,7 +902,7 @@ export class Directory extends BaseClient { mise = (): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "mise", }, @@ -698,7 +914,7 @@ export class Directory extends BaseClient { envhub = (): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "envhub", }, @@ -710,7 +926,7 @@ export class Directory extends BaseClient { withExec = (args: string[]): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -723,7 +939,7 @@ export class Directory extends BaseClient { withWorkdir = (path: string): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -733,23 +949,35 @@ export class Directory extends BaseClient { }); }; - withService = (serviceId: string): Pipeline => { + withService = (service: Service): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Directory => { + withSecretVariable = (name: string, secret: Secret): Directory => { + return new Directory({ + queryTree: [ + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, + }, + ], + ctx: this._ctx, + }); + }; + withEnvVariable = (name: string, value: string): Directory => { return new Directory({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -759,13 +987,13 @@ export class Directory extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Pipeline => { + withCache = (path: string, cache: Cache): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -775,7 +1003,7 @@ export class Directory extends BaseClient { withFile = (path: string, fileId: string): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -788,7 +1016,7 @@ export class Directory extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -801,7 +1029,7 @@ export class Directory extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -814,7 +1042,7 @@ export class Directory extends BaseClient { zip = (): File => { return new File({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "zip", }, @@ -826,7 +1054,7 @@ export class Directory extends BaseClient { tarCzvf = (): File => { return new File({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "tarCzvf", }, @@ -838,7 +1066,7 @@ export class Directory extends BaseClient { waitOn = (port: number, timeout?: number): Directory => { return new Directory({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -862,7 +1090,7 @@ export class Service extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -886,7 +1114,7 @@ export class File extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -899,7 +1127,7 @@ export class File extends BaseClient { path = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "path", }, @@ -912,7 +1140,7 @@ export class File extends BaseClient { zip = (): File => { return new File({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "zip", }, @@ -924,7 +1152,7 @@ export class File extends BaseClient { tarCzvf = (): File => { return new File({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "tarCzvf", }, @@ -933,10 +1161,10 @@ export class File extends BaseClient { }); }; - unzip = (outputDir?: String): Directory => { + unzip = (outputDir?: string): Directory => { return new Directory({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "unzip", args: { @@ -948,10 +1176,10 @@ export class File extends BaseClient { }); }; - tarXzvf = (outputDir?: String): Directory => { + tarXzvf = (outputDir?: string): Directory => { return new Directory({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "tarXzvf", args: { @@ -966,7 +1194,7 @@ export class File extends BaseClient { md5 = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "md5", }, @@ -979,7 +1207,7 @@ export class File extends BaseClient { sha256 = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "sha256", }, @@ -992,7 +1220,7 @@ export class File extends BaseClient { chmod = (mode: string): File => { return new File({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "chmod", args: { mode }, @@ -1016,7 +1244,7 @@ export class Flox extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -1029,7 +1257,7 @@ export class Flox extends BaseClient { withExec = (args: string[]): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -1042,7 +1270,7 @@ export class Flox extends BaseClient { withWorkdir = (path: string): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -1052,23 +1280,36 @@ export class Flox extends BaseClient { }); }; - withService = (serviceId: string): Flox => { + withService = (service: Service): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Flox => { + withSecretVariable = (name: string, secret: Secret): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, + }, + ], + ctx: this._ctx, + }); + }; + + withEnvVariable = (name: string, value: string): Flox => { + return new Flox({ + queryTree: [ + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -1078,13 +1319,13 @@ export class Flox extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Flox => { + withCache = (path: string, cache: Cache): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -1094,7 +1335,7 @@ export class Flox extends BaseClient { withFile = (path: string, fileId: string): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -1107,7 +1348,7 @@ export class Flox extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -1120,7 +1361,7 @@ export class Flox extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -1133,7 +1374,7 @@ export class Flox extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { name }, @@ -1146,7 +1387,7 @@ export class Flox extends BaseClient { waitOn = (port: number, timeout?: number): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -1170,7 +1411,7 @@ export class Git extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -1183,7 +1424,7 @@ export class Git extends BaseClient { branch = (name: string): Git => { return new Git({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "branch", args: { name }, @@ -1196,7 +1437,7 @@ export class Git extends BaseClient { commit = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "commit", }, @@ -1209,7 +1450,7 @@ export class Git extends BaseClient { tree = (): Directory => { return new Directory({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "tree", }, @@ -1232,7 +1473,7 @@ export class Nix extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -1245,7 +1486,7 @@ export class Nix extends BaseClient { withExec = (args: string[]): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -1258,7 +1499,7 @@ export class Nix extends BaseClient { withWorkdir = (path: string): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -1268,23 +1509,36 @@ export class Nix extends BaseClient { }); }; - withService = (serviceId: string): Nix => { + withService = (service: Service): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, + }, + ], + ctx: this._ctx, + }); + }; + + withSecretVariable = (name: string, secret: Secret): Nix => { + return new Nix({ + queryTree: [ + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Nix => { + withEnvVariable = (name: string, value: string): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -1294,13 +1548,13 @@ export class Nix extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Nix => { + withCache = (path: string, cache: Cache): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -1310,7 +1564,7 @@ export class Nix extends BaseClient { withFile = (path: string, fileId: string): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -1323,7 +1577,7 @@ export class Nix extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -1336,7 +1590,7 @@ export class Nix extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -1349,7 +1603,7 @@ export class Nix extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { name }, @@ -1362,7 +1616,7 @@ export class Nix extends BaseClient { waitOn = (port: number, timeout?: number): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -1386,7 +1640,7 @@ export class Pipeline extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -1399,7 +1653,7 @@ export class Pipeline extends BaseClient { http = (url: string): File => { return new File({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "http", args: { url }, @@ -1412,7 +1666,7 @@ export class Pipeline extends BaseClient { git = (url: string): Git => { return new Git({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "git", args: { url }, @@ -1425,7 +1679,7 @@ export class Pipeline extends BaseClient { devbox = (): Devbox => { return new Devbox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "devbox", }, @@ -1437,7 +1691,7 @@ export class Pipeline extends BaseClient { devenv = (): Devenv => { return new Devenv({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "devenv", }, @@ -1449,7 +1703,7 @@ export class Pipeline extends BaseClient { flox = (): Flox => { return new Flox({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "flox", }, @@ -1461,7 +1715,7 @@ export class Pipeline extends BaseClient { nix = (args?: NixArgs): Nix => { return new Nix({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "nix", args: { args }, @@ -1474,7 +1728,7 @@ export class Pipeline extends BaseClient { pkgx = (): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "pkgx", }, @@ -1486,7 +1740,7 @@ export class Pipeline extends BaseClient { pixi = (): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "pixi", }, @@ -1498,7 +1752,7 @@ export class Pipeline extends BaseClient { mise = (): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "mise", }, @@ -1510,7 +1764,7 @@ export class Pipeline extends BaseClient { envhub = (): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "envhub", }, @@ -1522,7 +1776,7 @@ export class Pipeline extends BaseClient { withExec = (args: string[]): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -1535,7 +1789,7 @@ export class Pipeline extends BaseClient { withWorkdir = (path: string): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -1545,23 +1799,36 @@ export class Pipeline extends BaseClient { }); }; - withService = (serviceId: string): Pipeline => { + withService = (service: Service): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Pipeline => { + withSecretVariable = (name: string, secret: Secret): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, + }, + ], + ctx: this._ctx, + }); + }; + + withEnvVariable = (name: string, value: string): Pipeline => { + return new Pipeline({ + queryTree: [ + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -1571,13 +1838,13 @@ export class Pipeline extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Pipeline => { + withCache = (path: string, cache: Cache): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -1587,7 +1854,7 @@ export class Pipeline extends BaseClient { withFile = (path: string, fileId: string): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -1600,7 +1867,7 @@ export class Pipeline extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -1613,7 +1880,7 @@ export class Pipeline extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -1626,7 +1893,7 @@ export class Pipeline extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { name }, @@ -1639,7 +1906,7 @@ export class Pipeline extends BaseClient { waitOn = (port: number, timeout?: number): Pipeline => { return new Pipeline({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -1663,7 +1930,7 @@ export class Pkgx extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -1676,7 +1943,7 @@ export class Pkgx extends BaseClient { withExec = (args: string[]): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -1689,7 +1956,7 @@ export class Pkgx extends BaseClient { withWorkdir = (path: string): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -1699,23 +1966,36 @@ export class Pkgx extends BaseClient { }); }; - withService = (serviceId: string): Pkgx => { + withService = (service: Service): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Pkgx => { + withSecretVariable = (name: string, secret: Secret): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, + }, + ], + ctx: this._ctx, + }); + }; + + withEnvVariable = (name: string, value: string): Pkgx => { + return new Pkgx({ + queryTree: [ + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -1725,13 +2005,13 @@ export class Pkgx extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Pkgx => { + withCache = (path: string, cache: Cache): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -1741,7 +2021,7 @@ export class Pkgx extends BaseClient { withFile = (path: string, fileId: string): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -1754,7 +2034,7 @@ export class Pkgx extends BaseClient { withPackages = (packages: string[]): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withPackages", args: { packages }, @@ -1767,7 +2047,7 @@ export class Pkgx extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -1780,7 +2060,7 @@ export class Pkgx extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -1793,7 +2073,7 @@ export class Pkgx extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { name }, @@ -1806,7 +2086,7 @@ export class Pkgx extends BaseClient { waitOn = (port: number, timeout?: number): Pkgx => { return new Pkgx({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -1830,7 +2110,7 @@ export class Pixi extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -1843,7 +2123,7 @@ export class Pixi extends BaseClient { withExec = (args: string[]): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -1856,7 +2136,7 @@ export class Pixi extends BaseClient { withWorkdir = (path: string): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -1866,23 +2146,36 @@ export class Pixi extends BaseClient { }); }; - withService = (serviceId: string): Pixi => { + withService = (service: Service): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Pixi => { + withSecretVariable = (name: string, secret: Secret): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, + }, + ], + ctx: this._ctx, + }); + }; + + withEnvVariable = (name: string, value: string): Pixi => { + return new Pixi({ + queryTree: [ + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -1892,13 +2185,13 @@ export class Pixi extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Pixi => { + withCache = (path: string, cache: Cache): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -1908,7 +2201,7 @@ export class Pixi extends BaseClient { withFile = (path: string, fileId: string): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -1921,7 +2214,7 @@ export class Pixi extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -1934,7 +2227,7 @@ export class Pixi extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -1947,7 +2240,7 @@ export class Pixi extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { name }, @@ -1960,7 +2253,7 @@ export class Pixi extends BaseClient { waitOn = (port: number, timeout?: number): Pixi => { return new Pixi({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -1984,7 +2277,7 @@ export class Mise extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -1997,7 +2290,7 @@ export class Mise extends BaseClient { withExec = (args: string[]): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -2010,7 +2303,7 @@ export class Mise extends BaseClient { withWorkdir = (path: string): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -2020,23 +2313,36 @@ export class Mise extends BaseClient { }); }; - withService = (serviceId: string): Mise => { + withService = (service: Service): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, + }, + ], + ctx: this._ctx, + }); + }; + + withSecretVariable = (name: string, secret: Secret): Mise => { + return new Mise({ + queryTree: [ + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Mise => { + withEnvVariable = (name: string, value: string): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -2046,13 +2352,13 @@ export class Mise extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Mise => { + withCache = (path: string, cache: Cache): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -2062,7 +2368,7 @@ export class Mise extends BaseClient { withFile = (path: string, fileId: string): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -2075,7 +2381,7 @@ export class Mise extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -2088,7 +2394,7 @@ export class Mise extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -2101,7 +2407,7 @@ export class Mise extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { name }, @@ -2114,7 +2420,7 @@ export class Mise extends BaseClient { waitOn = (port: number, timeout?: number): Mise => { return new Mise({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, @@ -2138,7 +2444,7 @@ export class Envhub extends BaseClient { id = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "id", }, @@ -2151,7 +2457,7 @@ export class Envhub extends BaseClient { use = (environment: string): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "use", args: { environment }, @@ -2164,7 +2470,7 @@ export class Envhub extends BaseClient { withExec = (args: string[]): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withExec", args: { args }, @@ -2177,7 +2483,7 @@ export class Envhub extends BaseClient { withWorkdir = (path: string): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withWorkdir", args: { path }, @@ -2187,23 +2493,36 @@ export class Envhub extends BaseClient { }); }; - withService = (serviceId: string): Envhub => { + withService = (service: Service): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withService", - args: { serviceId }, + args: { service }, + }, + ], + ctx: this._ctx, + }); + }; + + withSecretVariable = (name: string, secret: Secret): Envhub => { + return new Envhub({ + queryTree: [ + ...this._queryTree, + { + operation: "withSecretVariable", + args: { name, secret }, }, ], ctx: this._ctx, }); }; - withEnvVariable = (name: String, value: String): Envhub => { + withEnvVariable = (name: string, value: string): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withEnvVariable", args: { name, value }, @@ -2213,13 +2532,13 @@ export class Envhub extends BaseClient { }); }; - withCache = (path: string, cacheId: string): Envhub => { + withCache = (path: string, cache: Cache): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withCache", - args: { path, cacheId }, + args: { path, cache }, }, ], ctx: this._ctx, @@ -2229,7 +2548,7 @@ export class Envhub extends BaseClient { withFile = (path: string, fileId: string): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "withFile", args: { path, fileId }, @@ -2242,7 +2561,7 @@ export class Envhub extends BaseClient { stdout = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stdout", }, @@ -2255,7 +2574,7 @@ export class Envhub extends BaseClient { stderr = async (): Promise => { const response: Awaited = await computeQuery( [ - ...this.queryTree, + ...this._queryTree, { operation: "stderr", }, @@ -2268,7 +2587,7 @@ export class Envhub extends BaseClient { asService = (name: string): Service => { return new Service({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "asService", args: { name }, @@ -2281,7 +2600,7 @@ export class Envhub extends BaseClient { waitOn = (port: number, timeout?: number): Envhub => { return new Envhub({ queryTree: [ - ...this.queryTree, + ...this._queryTree, { operation: "waitOn", args: { port, timeout }, diff --git a/sdk/typescript/src/plugin.ts b/sdk/typescript/src/plugin.ts index 3bbdda0..3edea88 100644 --- a/sdk/typescript/src/plugin.ts +++ b/sdk/typescript/src/plugin.ts @@ -47,6 +47,11 @@ declare const Host: { as_service: (ptr: I64) => I64; with_service: (ptr: I64) => void; wait_on: (ptr: I64) => void; + add_secretmanager: (ptr: I64) => I64; + get_secret: (ptr: I64) => I64; + set_secret: (ptr: I64) => I64; + with_secret_variable: (ptr: I64) => void; + get_secret_plaintext: (ptr: I64) => I64; }; }; @@ -93,6 +98,11 @@ export const call: (ptr: I64) => I64 = fn.call; export const as_service: (ptr: I64) => I64 = fn.as_service; export const with_service: (ptr: I64) => void = fn.with_service; export const wait_on: (ptr: I64) => void = fn.wait_on; +export const add_secretmanager: (ptr: I64) => I64 = fn.add_secretmanager; +export const get_secret: (ptr: I64) => I64 = fn.get_secret; +export const set_secret: (ptr: I64) => I64 = fn.set_secret; +export const with_secret_variable: (ptr: I64) => void = fn.with_secret_variable; +export const get_secret_plaintext: (ptr: I64) => I64 = fn.get_secret_plaintext; export interface NixArgs { impure?: boolean; @@ -430,6 +440,123 @@ export class Client extends BaseClient { const offset = call(mem.offset); return Memory.find(offset).readString(); }; + + googleCloudSecretManager = ( + project: string, + googleCredentialsFile: string + ): SecretManager => { + const mem = Memory.fromJsonObject({ + google_project: project, + google_credentials_file: googleCredentialsFile, + }); + const offset = add_secretmanager(mem.offset); + const { id } = Memory.find(offset).readJsonObject(); + return new SecretManager({ id }); + }; + + awsSecretsManager = ( + region: string, + accessKeyId: string, + secretAccessKey: string + ): SecretManager => { + const mem = Memory.fromJsonObject({ + aws_region: region, + aws_access_key_id: accessKeyId, + aws_secret_access_key: secretAccessKey, + }); + const offset = add_secretmanager(mem.offset); + const { id } = Memory.find(offset).readJsonObject(); + return new SecretManager({ id }); + }; + + azureKeyvault = ( + clientId: string, + clientSecret: string, + tenantId: string, + keyvaultName: string, + keyvaultUrl: string + ): SecretManager => { + const mem = Memory.fromJsonObject({ + azure_client_id: clientId, + azure_client_secret: clientSecret, + azure_tenant_id: tenantId, + azure_keyvault_name: keyvaultName, + azure_keyvault_url: keyvaultUrl, + }); + const offset = add_secretmanager(mem.offset); + const { id } = Memory.find(offset).readJsonObject(); + return new SecretManager({ id }); + }; + + hashicorpVault = ( + address: string, + token: string, + cacerts: string + ): SecretManager => { + const mem = Memory.fromJsonObject({ + vault_address: address, + vault_token: token, + vault_cacerts: cacerts, + }); + const offset = add_secretmanager(mem.offset); + const { id } = Memory.find(offset).readJsonObject(); + return new SecretManager({ id }); + }; + + setSecret = (name: string, value: string): Secret => { + const mem = Memory.fromJsonObject([name, value]); + const offset = set_secret(mem.offset); + const id = Memory.find(offset).readString(); + return new Secret({ id }); + }; +} + +export class SecretManager extends BaseClient { + private _id?: string; + + constructor({ id }: { id: string }) { + super(); + this._id = id; + } + + id = (): string => { + return this._id!; + }; + + getSecret = (name: string): Secret[] => { + const mem = Memory.fromString(name); + const offset = get_secret(mem.offset); + const response = Memory.find(offset).readJsonObject(); + return response; + }; +} + +export class Secret extends BaseClient { + private _id?: string; + private _plaintext?: string; + private _name?: string; + private _mount?: string; + + constructor({ id }: { id: string }) { + super(); + this._id = id; + } + + id = (): string => { + return this._id!; + }; + + plaintext = (): string => { + return this._plaintext!; + }; + + name = (): string => { + return this._name!; + }; + + mount = (): string => { + return this._mount!; + }; } /** @@ -594,6 +721,16 @@ export class Devbox extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Devbox => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Returns the stdout of the last executed command * ```ts @@ -615,8 +752,7 @@ export class Devbox extends BaseClient { * @returns {string} */ stderr = (): string => { - let offset = stderr(); - + const offset = stderr(); return Memory.find(offset).readString(); }; } @@ -757,6 +893,16 @@ export class Devenv extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Devenv => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Returns the stdout of the last executed command * @@ -1119,6 +1265,16 @@ export class Directory extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Directory => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Returns the stdout of the last executed command * ```ts @@ -1417,6 +1573,16 @@ export class Flox extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Flox => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Create file at the given path * ```ts @@ -1661,6 +1827,16 @@ export class Nix extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Nix => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Create file at the given path * ```ts @@ -1942,6 +2118,16 @@ export class Pipeline extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Pipeline => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Create file at the given path * ```ts @@ -2109,6 +2295,16 @@ export class Pkgx extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Pkgx => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Create file at the given path * ```ts @@ -2282,6 +2478,16 @@ export class Pixi extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Pixi => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Create file at the given path * ```ts @@ -2441,6 +2647,16 @@ export class Mise extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Mise => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Create file at the given path * ```ts @@ -2619,6 +2835,16 @@ export class Envhub extends BaseClient { return this; }; + withSecretVariable = ( + name: string, + secretId: string, + secretName: string + ): Envhub => { + const mem = Memory.fromJsonObject([name, secretId, secretName]); + with_secret_variable(mem.offset); + return this; + }; + /** * Create file at the given path * ```ts diff --git a/sdk/typescript/src/utils.ts b/sdk/typescript/src/utils.ts index 0d64c61..b77919d 100644 --- a/sdk/typescript/src/utils.ts +++ b/sdk/typescript/src/utils.ts @@ -33,7 +33,6 @@ function buildArgs(args: any): string { if (key === "__metadata") { return acc; } - if (value !== undefined && value !== null) { acc.push(`${key}: ${formatValue(key, value as string)}`); }