diff --git a/Cargo.lock b/Cargo.lock index c30e568..dbdf065 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -64,38 +64,61 @@ checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + [[package]] name = "anymap2" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" +[[package]] +name = "async-trait" +version = "0.1.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -130,15 +153,21 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64" -version = "0.22.1" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -155,7 +184,7 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0aed08d3adb6ebe0eff737115056652670ae290f177759aac19c30456135f94c" dependencies = [ - "base64 0.22.1", + "base64", "bollard-stubs", "bytes", "futures-core", @@ -199,6 +228,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.6.0" @@ -272,9 +307,9 @@ checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "console" @@ -289,6 +324,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -304,40 +349,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - [[package]] name = "crypto-common" version = "0.1.6" @@ -355,16 +366,18 @@ dependencies = [ "bollard", "chrono", "clap", + "futures", "http-auth", "indicatif", "json", "liquid", "once_cell", - "rayon", "regex", + "reqwest", + "reqwest-middleware", + "reqwest-retry", "termsize", "tokio", - "ureq", "xitca-web", ] @@ -406,6 +419,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -413,21 +435,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "flate2" -version = "1.0.30" +name = "errno" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "crc32fast", - "miniz_oxide", + "libc", + "windows-sys 0.52.0", ] +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -437,6 +480,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -444,6 +502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -452,6 +511,23 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + [[package]] name = "futures-macro" version = "0.3.30" @@ -481,9 +557,13 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -506,8 +586,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -516,6 +598,25 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -572,13 +673,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643c9bbf6a4ea8a656d6b4cd53d34f79e3f841ad5203c1a55fb7d761923bc255" dependencies = [ - "base64 0.21.7", - "digest", - "hex", - "md-5", "memchr", - "rand", - "sha2", ] [[package]] @@ -625,6 +720,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", @@ -650,6 +746,39 @@ dependencies = [ "winapi", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.6" @@ -760,13 +889,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] +[[package]] +name = "ipnet" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" + [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -820,6 +958,12 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "liquid" version = "0.26.9" @@ -878,20 +1022,20 @@ dependencies = [ ] [[package]] -name = "log" -version = "0.4.22" +name = "lock_api" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] -name = "md-5" -version = "0.10.6" +name = "log" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -899,6 +1043,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -919,6 +1069,23 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -975,12 +1142,81 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1064,6 +1300,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "portable-atomic" version = "1.6.0" @@ -1078,9 +1320,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" @@ -1131,23 +1376,12 @@ dependencies = [ ] [[package]] -name = "rayon" -version = "1.10.0" +name = "redox_syscall" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "bitflags 1.3.2", ] [[package]] @@ -1179,6 +1413,94 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "reqwest-middleware" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "thiserror", + "tower-service", +] + +[[package]] +name = "reqwest-retry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a83df1aaec00176d0fabb65dea13f832d2a446ca99107afc17c5d2d4981221d0" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "getrandom", + "http", + "hyper", + "parking_lot", + "reqwest", + "reqwest-middleware", + "retry-policies", + "tokio", + "tracing", + "wasm-timer", +] + +[[package]] +name = "retry-policies" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" +dependencies = [ + "rand", +] + [[package]] name = "ring" version = "0.17.8" @@ -1200,32 +1522,53 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ - "log", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64", + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1238,6 +1581,44 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.204" @@ -1298,7 +1679,7 @@ version = "3.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", @@ -1398,6 +1779,49 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termsize" version = "0.1.8" @@ -1514,6 +1938,27 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.11" @@ -1652,9 +2097,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" @@ -1668,22 +2113,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" -dependencies = [ - "base64 0.22.1", - "flate2", - "log", - "once_cell", - "rustls", - "rustls-pki-types", - "url", - "webpki-roots", -] - [[package]] name = "url" version = "2.5.2" @@ -1707,6 +2136,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1753,6 +2188,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -1783,12 +2230,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] -name = "webpki-roots" -version = "0.26.3" +name = "wasm-timer" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "rustls-pki-types", + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", ] [[package]] @@ -1822,6 +2285,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1840,6 +2333,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2048,6 +2550,27 @@ dependencies = [ "xitca-unsafe-collection", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index 6db6ee6..f1f58bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,18 +6,20 @@ edition = "2021" [dependencies] clap = { version = "4.5.7", features = ["derive"] } indicatif = { version = "0.17.8", optional = true } -tokio = {version = "1.38.0", features = ["rt", "rt-multi-thread", "macros"]} -ureq = { version = "2.9.7", features = ["tls"] } -rayon = "1.10.0" +tokio = {version = "1.38.0", features = ["macros"]} xitca-web = { version = "0.5.0", optional = true, features = ["logger"] } liquid = { version = "0.26.6", optional = true } bollard = "0.16.1" once_cell = "1.19.0" -http-auth = { version = "0.1.9", features = [] } +http-auth = { version = "0.1.9", default-features = false, features = [] } termsize = { version = "0.1.8", optional = true } regex = "1.10.5" chrono = { version = "0.4.38", default-features = false, features = ["std", "alloc", "clock"], optional = true } json = "0.12.4" +reqwest = "0.12.7" +futures = "0.3.30" +reqwest-retry = "0.6.1" +reqwest-middleware = "0.3.3" [features] default = ["server", "cli"] diff --git a/src/check.rs b/src/check.rs index 0184ef2..585c647 100644 --- a/src/check.rs +++ b/src/check.rs @@ -1,16 +1,12 @@ -use std::{ - collections::{HashMap, HashSet}, - sync::Mutex, -}; +use std::collections::{HashMap, HashSet}; use json::JsonValue; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use crate::{ docker::get_images_from_docker_daemon, image::Image, registry::{check_auth, get_latest_digests, get_token}, - utils::unsplit_image, + utils::{new_reqwest_client, unsplit_image}, }; #[cfg(feature = "cli")] @@ -37,49 +33,48 @@ pub async fn get_all_updates( socket: Option, config: &JsonValue, ) -> Vec<(String, Option)> { - let image_map_mutex: Mutex>> = Mutex::new(HashMap::new()); let local_images = get_images_from_docker_daemon(socket).await; - local_images.par_iter().for_each(|image| { + let mut image_map: HashMap> = HashMap::with_capacity(local_images.len()); + for image in &local_images { let img = unsplit_image(&image.registry, &image.repository, &image.tag); - image_map_mutex.lock().unwrap().insert(img, &image.digest); - }); - let image_map = image_map_mutex.lock().unwrap().clone(); + image_map.insert(img, image.digest.clone()); + }; let mut registries: Vec<&String> = local_images - .par_iter() + .iter() .map(|image| &image.registry) .collect(); registries.unique(); - let mut remote_images: Vec = Vec::new(); + let mut remote_images: Vec = Vec::with_capacity(local_images.len()); + let client = new_reqwest_client(); for registry in registries { let images: Vec<&Image> = local_images - .par_iter() + .iter() .filter(|image| &image.registry == registry) .collect(); let credentials = config["authentication"][registry] .clone() .take_string() .or(None); - let mut latest_images = match check_auth(registry, config) { + let mut latest_images = match check_auth(registry, config, &client).await { Some(auth_url) => { - let token = get_token(images.clone(), &auth_url, &credentials); - get_latest_digests(images, Some(&token), config) + let token = get_token(images.clone(), &auth_url, &credentials, &client).await; + get_latest_digests(images, Some(&token), config, &client).await } - None => get_latest_digests(images, None, config), + None => get_latest_digests(images, None, config, &client).await, }; remote_images.append(&mut latest_images); } - let result_mutex: Mutex)>> = Mutex::new(Vec::new()); - remote_images.par_iter().for_each(|image| { + let mut result: Vec<(String, Option)> = Vec::new(); + remote_images.iter().for_each(|image| { let img = unsplit_image(&image.registry, &image.repository, &image.tag); match &image.digest { Some(d) => { let r = d != image_map.get(&img).unwrap().as_ref().unwrap(); - result_mutex.lock().unwrap().push((img, Some(r))) + result.push((img, Some(r))) } - None => result_mutex.lock().unwrap().push((img, None)), + None => result.push((img, None)), } }); - let result = result_mutex.lock().unwrap().clone(); result } @@ -90,13 +85,14 @@ pub async fn get_update(image: &str, socket: Option, config: &JsonValue) .clone() .take_string() .or(None); - let token = match check_auth(&local_image.registry, config) { - Some(auth_url) => get_token(vec![&local_image], &auth_url, &credentials), + let client = new_reqwest_client(); + let token = match check_auth(&local_image.registry, config, &client).await { + Some(auth_url) => get_token(vec![&local_image], &auth_url, &credentials, &client).await, None => String::new(), }; let remote_image = match token.as_str() { - "" => get_latest_digest(&local_image, None, config), - _ => get_latest_digest(&local_image, Some(&token), config), + "" => get_latest_digest(&local_image, None, config, &client).await, + _ => get_latest_digest(&local_image, Some(&token), config, &client).await, }; match &remote_image.digest { Some(d) => Some(d != &local_image.digest.unwrap()), diff --git a/src/docker.rs b/src/docker.rs index c8598cb..8ada41d 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -2,6 +2,7 @@ use bollard::{secret::ImageSummary, ClientVersion, Docker}; #[cfg(feature = "cli")] use bollard::secret::ImageInspect; +use futures::future::join_all; use crate::{error, image::Image, utils::split_image}; @@ -32,27 +33,16 @@ pub async fn get_images_from_docker_daemon(socket: Option) -> Vec error!("Failed to retrieve list of images available!\n{}", e) } }; - let mut result: Vec = Vec::new(); + let mut handles = Vec::new(); for image in images { - if !image.repo_tags.is_empty() && !image.repo_digests.is_empty() { - for t in &image.repo_tags { - let (registry, repository, tag) = split_image(t); - result.push(Image { - registry, - repository, - tag, - digest: Some( - image.repo_digests[0] - .clone() - .split('@') - .collect::>()[1] - .to_string(), - ), - }); - } + handles.push(Image::from(image)) + }; + join_all(handles).await.iter().filter(|img| { + match img { + Some(_) => true, + None => false } - } - result + }).map(|img| img.clone().unwrap()).collect() } #[cfg(feature = "cli")] diff --git a/src/formatting.rs b/src/formatting.rs index 87f78f9..07547bc 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -8,7 +8,7 @@ use crate::utils::{sort_update_vec, to_json}; pub fn print_updates(updates: &[(String, Option)], icons: &bool) { let sorted_updates = sort_update_vec(updates); let term_width: usize = termsize::get() - .unwrap_or(termsize::Size { rows: 24, cols: 80 }) + .unwrap_or_else(|| termsize::Size { rows: 24, cols: 80 }) .cols as usize; for update in sorted_updates { let description = match update.1 { diff --git a/src/image.rs b/src/image.rs index ff46661..933fcaa 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,3 +1,7 @@ +use bollard::secret::ImageSummary; + +use crate::utils::split_image; + #[derive(Clone, Debug)] pub struct Image { pub registry: String, @@ -5,3 +9,27 @@ pub struct Image { pub tag: String, pub digest: Option, } + +impl Image { + pub async fn from(image: ImageSummary) -> Option { + if !image.repo_tags.is_empty() && !image.repo_digests.is_empty() { + for t in &image.repo_tags { + let (registry, repository, tag) = split_image(t); + let image = Image { + registry, + repository, + tag, + digest: Some( + image.repo_digests[0] + .clone() + .split('@') + .collect::>()[1] + .to_string(), + ), + }; + return Some(image) + } + } + None + } +} \ No newline at end of file diff --git a/src/registry.rs b/src/registry.rs index 35f41f1..9270665 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -1,160 +1,136 @@ -use std::sync::Mutex; - +use futures::future::join_all; use json::JsonValue; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use ureq::{Error, ErrorKind}; use http_auth::parse_challenges; +use reqwest_middleware::ClientWithMiddleware; use crate::{error, image::Image, warn}; -pub fn check_auth(registry: &str, config: &JsonValue) -> Option { +pub async fn check_auth(registry: &str, config: &JsonValue, client: &ClientWithMiddleware) -> Option { let protocol = if config["insecure_registries"].contains(registry) { "http" } else { "https" }; - let response = ureq::get(&format!("{}://{}/v2/", protocol, registry)).call(); + let response = client.get(&format!("{}://{}/v2/", protocol, registry)).send().await; match response { - Ok(_) => None, - Err(Error::Status(401, response)) => match response.header("www-authenticate") { - Some(challenge) => Some(parse_www_authenticate(challenge)), - None => error!( - "Unauthorized to access registry {} and no way to authenticate was provided", - registry - ), + Ok(r) => { + let status = r.status().as_u16(); + if status == 401 { + match r.headers().get("www-authenticate") { + Some(challenge) => Some(parse_www_authenticate(challenge.to_str().unwrap())), + None => error!( + "Unauthorized to access registry {} and no way to authenticate was provided", + registry + ), + } + } else if status == 200 { + None + } else { + warn!("Received unexpected status code {}\nResponse: {}", status, r.text().await.unwrap()); + None + } }, - Err(Error::Transport(error)) => { - match error.kind() { - ErrorKind::Dns => { - warn!("Failed to lookup the IP of the registry, retrying."); - return check_auth(registry, config); - } // If something goes really wrong, this can get stuck in a loop - ErrorKind::ConnectionFailed => { - warn!("Connection probably timed out, retrying."); - return check_auth(registry, config); - } // Same here - _ => error!("{}", error), + Err(e) => { + if e.is_connect() { + warn!("Connection to registry {} failed.", ®istry); + None + } else { + error!("Unexpected error: {}", e.to_string()) } } - Err(e) => error!("{}", e), } } -pub fn get_latest_digest(image: &Image, token: Option<&String>, config: &JsonValue) -> Image { +pub async fn get_latest_digest(image: &Image, token: Option<&String>, config: &JsonValue, client: &ClientWithMiddleware) -> Image { let protocol = if config["insecure_registries"].contains(json::JsonValue::from(image.registry.clone())) { "http" } else { "https" }; - let mut request = ureq::head(&format!( + let mut request = client.head(&format!( "{}://{}/v2/{}/manifests/{}", protocol, &image.registry, &image.repository, &image.tag )); if let Some(t) = token { - request = request.set("Authorization", &format!("Bearer {}", t)); + request = request.header("Authorization", &format!("Bearer {}", t)); } let raw_response = match request - .set("Accept", "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.index.v1+json") - .call() + .header("Accept", "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.index.v1+json") + .send().await { - Ok(response) => response, - Err(Error::Status(401, response)) => { - if token.is_some() { - warn!("Failed to authenticate to registry {} with given token!\n{}", &image.registry, token.unwrap()); + Ok(response) => { + let status = response.status(); + if status == 401 { + if token.is_some() { + warn!("Failed to authenticate to registry {} with given token!\n{}", &image.registry, token.unwrap()); + } else { + warn!("Registry requires authentication"); + } + return Image { digest: None, ..image.clone() } + } else if status == 404 { + warn!("Image {:?} not found", &image); return Image { digest: None, ..image.clone() } } else { - return get_latest_digest( - image, - Some(&get_token( - vec![image], - &parse_www_authenticate(response.header("www-authenticate").unwrap()), - &None // I think? - )), - config - ); - } - } - Err(Error::Status(_, _)) => { - return Image { - digest: None, - ..image.clone() + response } }, - Err(Error::Transport(error)) => { - match error.kind() { - ErrorKind::Dns => { - warn!("Failed to lookup the IP of the registry, retrying."); - return get_latest_digest(image, token, config) - }, // If something goes really wrong, this can get stuck in a loop - ErrorKind::ConnectionFailed => { - warn!("Connection probably timed out, retrying."); - return get_latest_digest(image, token, config) - }, // Same here - _ => error!("Failed to retrieve image digest\n{}!", error) + Err(e) => { + if e.is_connect() { + warn!("Connection to registry failed."); + return Image { digest: None, ..image.clone() } + } else { + error!("Unexpected error: {}", e.to_string()) } }, }; - match raw_response.header("docker-content-digest") { + match raw_response.headers().get("docker-content-digest") { Some(digest) => Image { - digest: Some(digest.to_string()), + digest: Some(digest.to_str().unwrap().to_string()), ..image.clone() }, - None => error!("Server returned invalid response! No docker-content-digest!"), + None => error!("Server returned invalid response! No docker-content-digest!\n{:#?}", raw_response), } } -pub fn get_latest_digests( +pub async fn get_latest_digests( images: Vec<&Image>, token: Option<&String>, config: &JsonValue, + client: &ClientWithMiddleware ) -> Vec { - let result: Mutex> = Mutex::new(Vec::new()); - images.par_iter().for_each(|&image| { - let digest = get_latest_digest(image, token, config).digest; - result.lock().unwrap().push(Image { - digest, - ..image.clone() - }); - }); - let r = result.lock().unwrap().clone(); - r + let mut handles = Vec::new(); + for image in images { + handles.push(get_latest_digest(image, token, config, client)) + } + join_all(handles).await } -pub fn get_token(images: Vec<&Image>, auth_url: &str, credentials: &Option) -> String { +pub async fn get_token(images: Vec<&Image>, auth_url: &str, credentials: &Option, client: &ClientWithMiddleware) -> String { let mut final_url = auth_url.to_owned(); for image in &images { final_url = format!("{}&scope=repository:{}:pull", final_url, image.repository); } let mut base_request = - ureq::get(&final_url).set("Accept", "application/vnd.oci.image.index.v1+json"); // Seems to be unnecesarry. Will probably remove in the future + client.get(&final_url).header("Accept", "application/vnd.oci.image.index.v1+json"); // Seems to be unnecessary. Will probably remove in the future base_request = match credentials { - Some(creds) => base_request.set("Authorization", &format!("Basic {}", creds)), + Some(creds) => base_request.header("Authorization", &format!("Basic {}", creds)), None => base_request, }; - let raw_response = match base_request.call() { - Ok(response) => match response.into_string() { + let raw_response = match base_request.send().await { + Ok(response) => match response.text().await { Ok(res) => res, Err(e) => { error!("Failed to parse response into string!\n{}", e) } }, - Err(Error::Transport(error)) => { - match error.kind() { - ErrorKind::Dns => { - warn!("Failed to lookup the IP of the registry, retrying."); - return get_token(images, auth_url, credentials); - } // If something goes really wrong, this can get stuck in a loop - ErrorKind::ConnectionFailed => { - warn!("Connection probably timed out, retrying."); - return get_token(images, auth_url, credentials); - } // Same here - _ => error!("Token request failed\n{}!", error), - } - } Err(e) => { - error!("Token request failed!\n{}", e) + if e.is_connect() { + error!("Connection to registry failed."); + } else { + error!("Token request failed!\n{}", e.to_string()) + } } }; let parsed_token_response: JsonValue = match json::parse(&raw_response) { diff --git a/src/utils.rs b/src/utils.rs index 2fdb01a..b5ade7a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,8 @@ use std::path::PathBuf; use json::{object, JsonValue}; use once_cell::sync::Lazy; use regex::Regex; +use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; /// This macro is an alternative to panic. It prints the message you give it and exits the process with code 1, without printing a stack trace. Useful for when the program has to exit due to a user error or something unexpected which is unrelated to the program (e.g. a failed web request) #[macro_export] @@ -21,14 +23,15 @@ macro_rules! warn { }) } +static RE: Lazy = Lazy::new(|| { + Regex::new( + r#"^(?P(?:(?P(?:(?:localhost|[\w-]+(?:\.[\w-]+)+)(?::\d+)?)|[\w]+:\d+)/)?(?P[a-z0-9_.-]+(?:/[a-z0-9_.-]+)*))(?::(?P[\w][\w.-]{0,127}))?$"#, // From https://regex101.com/r/nmSDPA/1 + ) + .unwrap() +}); + /// Takes an image and splits it into registry, repository and tag. For example ghcr.io/sergi0g/cup:latest becomes ['ghcr.io', 'sergi0g/cup', 'latest']. pub fn split_image(image: &str) -> (String, String, String) { - static RE: Lazy = Lazy::new(|| { - Regex::new( - r#"^(?P(?:(?P(?:(?:localhost|[\w-]+(?:\.[\w-]+)+)(?::\d+)?)|[\w]+:\d+)/)?(?P[a-z0-9_.-]+(?:/[a-z0-9_.-]+)*))(?::(?P[\w][\w.-]{0,127}))?$"#, // From https://regex101.com/r/nmSDPA/1 - ) - .unwrap() - }); match RE.captures(image) { Some(c) => { let registry = match c.name("registry") { @@ -124,21 +127,24 @@ pub fn to_json(updates: &[(String, Option)]) -> JsonValue { let up_to_date = updates .iter() .filter(|&(_, value)| *value == Some(false)) - .collect::)>>() - .len(); + .count(); let update_available = updates .iter() .filter(|&(_, value)| *value == Some(true)) - .collect::)>>() - .len(); + .count(); let unknown = updates .iter() .filter(|&(_, value)| value.is_none()) - .collect::)>>() - .len(); + .count(); let _ = json_data["metrics"].insert("monitored_images", updates.len()); let _ = json_data["metrics"].insert("up_to_date", up_to_date); let _ = json_data["metrics"].insert("update_available", update_available); let _ = json_data["metrics"].insert("unknown", unknown); json_data } + +pub fn new_reqwest_client() -> ClientWithMiddleware { + ClientBuilder::new(reqwest::Client::new()) + .with(RetryTransientMiddleware::new_with_policy(ExponentialBackoff::builder().build_with_max_retries(3))) + .build() +} \ No newline at end of file