From e069346e15bf4ce68817470171d1adc29ace2db7 Mon Sep 17 00:00:00 2001 From: Hikari Date: Wed, 31 Jan 2024 08:51:13 -0600 Subject: [PATCH] feat: Bugreport command (#28) Add a bug report + completions command(not complete yet) so that new users can easily submit bugs to us, and I wanted completions for bb (super easy with clap) --------- Co-authored-by: Gerald Pinder --- .gitignore | 5 +- .helix/languages.toml | 4 + .rusty-hook.toml | 2 +- Cargo.lock | 752 ++++++++++++++++++++++++++++----- Cargo.toml | 44 +- Earthfile | 1 + build.rs | 50 +++ rust-toolchain.toml | 7 +- src/bin/bb.rs | 72 +--- src/commands.rs | 25 -- src/commands/bug_report.rs | 471 +++++++++++++++++++++ src/commands/build.rs | 13 +- src/commands/completions.rs | 27 ++ src/commands/local.rs | 6 +- src/commands/mod.rs | 94 +++++ src/commands/template.rs | 48 +-- src/commands/utils.rs | 136 ++++++ src/lib.rs | 11 +- src/module_recipe.rs | 64 ++- src/ops.rs | 2 +- templates/Containerfile.module | 2 +- templates/github_issue.j2 | 35 ++ 22 files changed, 1593 insertions(+), 278 deletions(-) create mode 100644 build.rs delete mode 100644 src/commands.rs create mode 100644 src/commands/bug_report.rs create mode 100644 src/commands/completions.rs create mode 100644 src/commands/mod.rs create mode 100644 src/commands/utils.rs create mode 100644 templates/github_issue.j2 diff --git a/.gitignore b/.gitignore index 8839e912..b4f691cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target .sccache/ -.vscode/ \ No newline at end of file +.vscode/ + +# Local testing for bb recipe files +/config/ diff --git a/.helix/languages.toml b/.helix/languages.toml index 8307a6d7..6cb0c757 100644 --- a/.helix/languages.toml +++ b/.helix/languages.toml @@ -1,2 +1,6 @@ [language-server.rust-analyzer.config] cargo.features = ["nightly"] + +[language-server.rust-analyzer.config.check] +command = "clippy" +args = ["--no-deps"] diff --git a/.rusty-hook.toml b/.rusty-hook.toml index a2d0ec70..ac4c348b 100644 --- a/.rusty-hook.toml +++ b/.rusty-hook.toml @@ -1,5 +1,5 @@ [hooks] -pre-commit = "cargo fmt && cargo test && cargo clippy -- -D warnings" +pre-commit = "cargo fmt --check && cargo test && cargo clippy -- -D warnings" [logging] verbose = true diff --git a/Cargo.lock b/Cargo.lock index ac6a272d..4b0fa97e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.7" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -147,13 +147,14 @@ dependencies = [ "percent-encoding", "serde", "serde_json", + "serde_yaml 0.9.30", ] [[package]] name = "askama_derive" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccf09143e56923c12e027b83a9553210a3c58322ed8419a53461b14a4dccd85" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" dependencies = [ "askama_parser", "basic-toml", @@ -173,9 +174,9 @@ checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" [[package]] name = "askama_parser" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262eb9cf7be51269c5f2951eeda9ccd14d6934e437457f47b4f066bf55a6770d" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" dependencies = [ "nom", ] @@ -253,9 +254,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block-buffer" @@ -281,24 +282,37 @@ version = "0.5.5" dependencies = [ "anyhow", "askama", - "cfg-if", "chrono", "clap", "clap-verbosity-flag", + "clap_complete", + "clap_complete_nushell", + "colorized", "derive_builder", + "directories", + "dunce", "env_logger", + "format_serde_error", "futures-util", - "indexmap 2.1.0", + "fuzzy-matcher", + "indexmap 2.2.1", "log", + "open", + "os_info", "podman-api", + "process_control", + "requestty", "rusty-hook", "serde", "serde_json", - "serde_yaml", + "serde_yaml 0.9.30", + "shadow-rs", "sigstore", "tokio", "typed-builder", + "urlencoding", "users", + "which", ] [[package]] @@ -367,9 +381,9 @@ dependencies = [ [[package]] name = "cached_proc_macro_types" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cbc" @@ -386,6 +400,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -403,9 +418,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ "android-tzdata", "iana-time-zone", @@ -413,7 +428,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] @@ -438,9 +453,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.16" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -448,9 +463,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c90e95e5bd4e8ac34fa6f37c774b0c6f8ed06ea90c79931fd448fcf941a9767" +checksum = "b57f73ca21b17a0352944b9bb61803b6007bd911b6cccfef7153f7f0600ac495" dependencies = [ "clap", "log", @@ -458,14 +473,35 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.16" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", + "unicase", + "unicode-width", +] + +[[package]] +name = "clap_complete" +version = "4.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df631ae429f6613fcd3a7c1adbdb65f637271e561b03680adaa6573015dfb106" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_complete_nushell" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "948bf70d7e1f179635d3ef819ce8baa2d3074d0d57816ac37387cd6f9eed0c31" +dependencies = [ + "clap", + "clap_complete", ] [[package]] @@ -492,6 +528,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "colorized" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739281952453521b532f476b5f8b77c4b16d5ab2a248466c8dc153b3f85d6564" + [[package]] name = "combine" version = "4.6.6" @@ -508,6 +560,32 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_fn" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" + +[[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 = "containers-api" version = "0.8.0" @@ -525,7 +603,7 @@ dependencies = [ "mime", "openssl", "paste", - "pin-project 1.1.3", + "pin-project 1.1.4", "serde", "serde_json", "tar", @@ -568,6 +646,46 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -740,18 +858,18 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +checksum = "660047478bc508c0fde22c868991eec0c40a63e48d610befef466d48e2bee574" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +checksum = "9b217e6dd1011a54d12f3b920a411b5abd44b1716ecfe94f5f2f2f7b52e08ab7" dependencies = [ "darling 0.14.4", "proc-macro2", @@ -761,9 +879,9 @@ dependencies = [ [[package]] name = "derive_builder_macro" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +checksum = "7a5f77d7e20ac9153428f7ca14a88aba652adfc7a0ef0a06d654386310ef663b" dependencies = [ "derive_builder_core", "syn 1.0.109", @@ -781,12 +899,39 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "dyn-clone" version = "1.0.16" @@ -868,17 +1013,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "05e7cf40684ae96ade6232ed84582f40ce0a66efcd43a5117aef610534f8e0b8" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -937,7 +1092,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "windows-sys 0.52.0", ] @@ -987,6 +1142,19 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "format_serde_error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5837b8e6a4001f99fe4746767fb7379e8510c508a843caa136cc12ed9c0bad0" +dependencies = [ + "colored", + "serde", + "serde_json", + "serde_yaml 0.8.26", + "unicode-segmentation", +] + [[package]] name = "fsio" version = "0.1.3" @@ -1094,6 +1262,15 @@ dependencies = [ "pin-project 0.4.30", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1133,6 +1310,19 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "git2" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" +dependencies = [ + "bitflags 2.4.2", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "globset" version = "0.4.14" @@ -1159,9 +1349,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes 1.5.0", "fnv", @@ -1169,7 +1359,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.1.0", + "indexmap 2.2.1", "slab", "tokio", "tokio-util", @@ -1200,9 +1390,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "hex" @@ -1359,7 +1549,7 @@ dependencies = [ "futures-util", "hex", "hyper", - "pin-project 1.1.3", + "pin-project 1.1.4", "tokio", ] @@ -1415,9 +1605,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1450,16 +1640,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] -name = "is-terminal" -version = "0.4.10" +name = "is-docker" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.52.0", + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", ] +[[package]] +name = "is_debug" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" + [[package]] name = "itertools" version = "0.10.5" @@ -1497,6 +1701,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.67" @@ -1536,12 +1749,47 @@ version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +[[package]] +name = "libgit2-sys" +version = "0.16.1+1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "libz-sys" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1559,9 +1807,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -1632,6 +1880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -1734,6 +1983,21 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "oauth2" version = "4.4.2" @@ -1820,6 +2084,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "open" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "openidconnect" version = "3.4.0" @@ -1854,11 +2129,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.62" +version = "0.10.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -1886,9 +2161,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.98" +version = "0.9.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" dependencies = [ "cc", "libc", @@ -1896,6 +2171,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.1" @@ -1905,6 +2186,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_info" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +dependencies = [ + "log", + "serde", + "winapi", +] + [[package]] name = "p256" version = "0.13.2" @@ -1947,7 +2239,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", ] @@ -1987,6 +2279,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -2042,11 +2340,11 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ - "pin-project-internal 1.1.3", + "pin-project-internal 1.1.4", ] [[package]] @@ -2062,9 +2360,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", @@ -2123,9 +2421,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "platforms" @@ -2203,13 +2501,25 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] +[[package]] +name = "process_control" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e056a69288d0a211f4c74c48391c6eb86e714fdcb9dc58a9f34302da9c20bf" +dependencies = [ + "crossbeam-channel", + "libc", + "signal-hook", + "windows-sys 0.48.0", +] + [[package]] name = "quote" version = "1.0.35" @@ -2255,6 +2565,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2264,11 +2583,28 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -2278,9 +2614,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -2293,6 +2629,47 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "requestty" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa883a1f3e288e65187f653e6ba2e84fdf810fe02f4c8074f9c723d1aa26e2ae" +dependencies = [ + "requestty-macro", + "requestty-ui", + "shell-words", + "smallvec", + "tempfile", + "winsplit", +] + +[[package]] +name = "requestty-macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4786268b0f92c2caade70e559e1788a60fa70fdcd671538be2c4bed9860a34ef" +dependencies = [ + "bitflags 1.3.2", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "requestty-ui" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7549bab39cf982b629b68e7ec191a5574e85086e95c0ebe514c02d3b42ffe225" +dependencies = [ + "bitflags 1.3.2", + "crossterm", + "once_cell", + "termion", + "textwrap", + "unicode-segmentation", +] + [[package]] name = "reqwest" version = "0.11.23" @@ -2414,7 +2791,7 @@ version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", @@ -2438,6 +2815,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "rusty-hook" version = "0.11.2" @@ -2546,9 +2929,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] @@ -2565,9 +2948,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", @@ -2576,9 +2959,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed" dependencies = [ "itoa", "ryu", @@ -2629,15 +3012,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.4.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +checksum = "f5c9fdb6b00a489875b22efd4b78fe2b363b72265cc5f6eb2e2b9ee270e6140c" dependencies = [ "base64 0.21.7", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.1.0", + "indexmap 2.2.1", "serde", "serde_json", "serde_with_macros", @@ -2646,9 +3029,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.4.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +checksum = "dbff351eb4b33600a2e138dfa0b10b65a238ea8ff8fb2387c422c5022a3e8298" dependencies = [ "darling 0.20.3", "proc-macro2", @@ -2656,13 +3039,25 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap 1.9.3", + "ryu", + "serde", + "yaml-rust", +] + [[package]] name = "serde_yaml" version = "0.9.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.1", "itoa", "ryu", "serde", @@ -2680,6 +3075,46 @@ dependencies = [ "digest", ] +[[package]] +name = "shadow-rs" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5c5c8276991763b44ede03efaf966eaa0412fafbf299e6380704678ca3b997" +dependencies = [ + "const_format", + "git2", + "is_debug", + "time", + "tzdb", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2761,9 +3196,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.12.0" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "smawk" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "snafu" @@ -2893,18 +3334,32 @@ checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.4.1", "rustix", "windows-sys 0.52.0", ] [[package]] -name = "termcolor" -version = "1.4.1" +name = "termion" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ - "winapi-util", + "libc", + "numtoa", + "redox_syscall 0.2.16", + "redox_termios", +] + +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", ] [[package]] @@ -2927,6 +3382,16 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.31" @@ -2935,6 +3400,8 @@ checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", + "libc", + "num_threads", "powerfmt", "serde", "time-core", @@ -2973,9 +3440,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tls_codec" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38a1d5fcfa859f0ec2b5e111dc903890bd7dac7f34713232bf9aa4fd7cad7b2" +checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a" dependencies = [ "tls_codec_derive", "zeroize", @@ -2983,9 +3450,9 @@ dependencies = [ [[package]] name = "tls_codec_derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e00e3e7a54e0f1c8834ce72ed49c8487fbd3f801d8cfe1a0ad0640382f8e15" +checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" dependencies = [ "proc-macro2", "quote", @@ -3146,18 +3613,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typed-builder" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47c0496149861b7c95198088cbf36645016b1a0734cf350c50e2a38e070f38a" +checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982ee4197351b5c9782847ef5ec1fdcaf50503fb19d68f9771adae314e72b492" +checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" dependencies = [ "proc-macro2", "quote", @@ -3170,6 +3637,35 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "tz-rs" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" +dependencies = [ + "const_fn", +] + +[[package]] +name = "tzdb" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b580f6b365fa89f5767cdb619a55d534d04a4e14c2d7e5b9a31e94598687fb1" +dependencies = [ + "iana-time-zone", + "tz-rs", + "tzdb_data", +] + +[[package]] +name = "tzdb_data" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629555d2921f3f0dc0de98699415a8b2b61dfcd3a0b082a327f7ed748bbb2b76" +dependencies = [ + "tz-rs", +] + [[package]] name = "unicase" version = "2.7.0" @@ -3181,9 +3677,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -3191,6 +3687,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.22" @@ -3200,12 +3702,24 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "universal-hash" version = "0.5.1" @@ -3246,6 +3760,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "users" version = "0.11.0" @@ -3405,6 +3925,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "which" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3653,6 +4186,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsplit" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956" + [[package]] name = "x509-cert" version = "0.2.5" @@ -3667,15 +4206,24 @@ dependencies = [ [[package]] name = "xattr" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys", "rustix", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/Cargo.toml b/Cargo.toml index 3ea3c4b7..dd91c587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,27 +6,41 @@ description = "A CLI tool built for creating Containerfile templates based on th repository = "https://github.com/blue-build/cli" license = "Apache-2.0" categories = ["command-line-utilities"] +build = "build.rs" [dependencies] anyhow = "1" -askama = { version = "0.12.1", features = ["serde-json"] } -cfg-if = "1.0.0" +askama = { version = "0.12", features = ["serde-json", "serde-yaml"] } chrono = "0.4" -clap = { version = "4", features = ["derive"] } -clap-verbosity-flag = "2.1.1" -derive_builder = "0.12.0" -env_logger = "0.10.1" -futures-util = { version = "0.3.30", optional = true } -indexmap = { version = "2.1.0", features = ["serde"] } +clap = { version = "4", features = ["derive", "cargo", "unicode"] } +clap-verbosity-flag = "2" +clap_complete = "4" +clap_complete_nushell = "4" +colorized = "1" +derive_builder = "0.13" +directories = "5" +env_logger = "0.11" +futures-util = { version = "0.3", optional = true } +fuzzy-matcher = "0.3" +indexmap = { version = "2", features = ["serde"] } log = "0.4" +open = "5" +# update os module config and tests when upgrading os_info +shadow-rs = { version = "0.26" } +os_info = "3.7" podman-api = { version = "0.10.0", optional = true } +process_control = { version = "4.0.3", features = ["crossbeam-channel"] } +requestty = { version = "0.5", features = ["macros", "termion"] } serde = { version = "1", features = ["derive"] } serde_json = "1" -serde_yaml = "0.9.25" +serde_yaml = "0.9.30" sigstore = { version = "0.8.0", optional = true } tokio = { version = "1", features = ["full"], optional = true } -typed-builder = "0.18.0" +typed-builder = "0.18.1" +urlencoding = "2.1.3" users = "0.11.0" +which = "6" +format_serde_error = "0.3.0" [features] default = [] @@ -37,3 +51,13 @@ init = [] [dev-dependencies] rusty-hook = "0.11.2" + +[build-dependencies] +shadow-rs = { version = "0.26.1", default-features = false } +dunce = "1.0.4" + +[profile.release] +lto = true +codegen-units = 1 +strip = true +debug = false diff --git a/Earthfile b/Earthfile index 2251bd83..7593c136 100644 --- a/Earthfile +++ b/Earthfile @@ -65,6 +65,7 @@ common: COPY --keep-ts Cargo.* /app COPY --keep-ts *.md /app COPY --keep-ts LICENSE /app + COPY --keep-ts build.rs /app DO cargo+INIT diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..a42bd657 --- /dev/null +++ b/build.rs @@ -0,0 +1,50 @@ +use shadow_rs::SdResult; +use std::fs::File; +use std::io::Write; +use std::process::Command; + +fn main() -> SdResult<()> { + shadow_rs::new_hook(hook) +} + +fn hook(file: &File) -> SdResult<()> { + append_write_const(file)?; + Ok(()) +} + +fn append_write_const(mut file: &File) -> SdResult<()> { + let hash = Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .map(|x| { + String::from_utf8(x.stdout) + .ok() + .map(|x| x.trim().to_string()) + }) + .unwrap_or(None); + + let short_hash = Command::new("git") + .args(["rev-parse", "--short", "HEAD"]) + .output() + .map(|x| { + String::from_utf8(x.stdout) + .ok() + .map(|x| x.trim().to_string()) + }) + .unwrap_or(None); + + let hook_const: &str = &format!( + "{}\n{}", + &format!( + "pub const BB_COMMIT_HASH: &str = \"{}\";", + hash.unwrap_or_default() + ), + &format!( + "pub const BB_COMMIT_HASH_SHORT: &str = \"{}\";", + short_hash.unwrap_or_default() + ) + ); + + writeln!(file, "{hook_const}")?; + Ok(()) +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5fc9fb5f..ac3fd53e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,8 @@ [toolchain] channel = "stable" -targets = ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl"] +targets = [ + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", +] diff --git a/src/bin/bb.rs b/src/bin/bb.rs index 2f8b170f..7d0eaa32 100644 --- a/src/bin/bb.rs +++ b/src/bin/bb.rs @@ -1,66 +1,6 @@ -#![warn(clippy::pedantic, clippy::nursery)] - -use clap::{Parser, Subcommand}; -use clap_verbosity_flag::{InfoLevel, Verbosity}; +use blue_build::commands::*; +use clap::Parser; use env_logger::WriteStyle; -use log::trace; - -use blue_build::{ - self, - commands::{build, local, template, BlueBuildCommand}, -}; - -#[cfg(feature = "init")] -use blue_build::commands::init; - -#[derive(Parser, Debug)] -#[command(name = "BlueBuild", author, version, about, long_about = None)] -struct BlueBuildArgs { - #[command(subcommand)] - command: CommandArgs, - - #[clap(flatten)] - verbosity: Verbosity, -} - -#[derive(Debug, Subcommand)] -enum CommandArgs { - /// Build an image from a recipe - Build(build::BuildCommand), - - /// Generate a Containerfile from a recipe - Template(template::TemplateCommand), - - /// Upgrade your current OS with the - /// local image saved at `/etc/blue-build/`. - /// - /// This requires having rebased already onto - /// a local archive already by using the `rebase` - /// subcommand. - /// - /// NOTE: This can only be used if you have `rpm-ostree` - /// installed and if the `--push` and `--rebase` option isn't - /// used. This image will not be signed. - Upgrade(local::UpgradeCommand), - - /// Rebase your current OS onto the image - /// being built. - /// - /// This will create a tarball of your image at - /// `/etc/blue-build/` and invoke `rpm-ostree` to - /// rebase onto the image using `oci-archive`. - /// - /// NOTE: This can only be used if you have `rpm-ostree` - /// installed. - Rebase(local::RebaseCommand), - - /// Initialize a new Ublue Starting Point repo - #[cfg(feature = "init")] - Init(init::InitCommand), - - #[cfg(feature = "init")] - New(init::NewCommand), -} fn main() { let args = BlueBuildArgs::parse(); @@ -71,18 +11,18 @@ fn main() { .write_style(WriteStyle::Always) .init(); - trace!("{args:#?}"); + log::trace!("Parsed arguments: {args:#?}"); match args.command { #[cfg(feature = "init")] CommandArgs::Init(mut command) => command.run(), - #[cfg(feature = "init")] CommandArgs::New(mut command) => command.run(), - - CommandArgs::Template(mut command) => command.run(), CommandArgs::Build(mut command) => command.run(), CommandArgs::Rebase(mut command) => command.run(), CommandArgs::Upgrade(mut command) => command.run(), + CommandArgs::Template(mut command) => command.run(), + CommandArgs::BugReport(mut command) => command.run(), + CommandArgs::Completions(mut command) => command.run(), } } diff --git a/src/commands.rs b/src/commands.rs deleted file mode 100644 index 19bcb82b..00000000 --- a/src/commands.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[cfg(feature = "init")] -pub mod init; - -pub mod build; -pub mod local; -pub mod template; - -use log::error; - -pub trait BlueBuildCommand { - /// Runs the command and returns a result - /// of the execution - /// - /// # Errors - /// Can return an `anyhow` Error - fn try_run(&mut self) -> anyhow::Result<()>; - - /// Runs the command and exits if there is an error. - fn run(&mut self) { - if let Err(e) = self.try_run() { - error!("{e}"); - std::process::exit(1); - } - } -} diff --git a/src/commands/bug_report.rs b/src/commands/bug_report.rs new file mode 100644 index 00000000..a23b46eb --- /dev/null +++ b/src/commands/bug_report.rs @@ -0,0 +1,471 @@ +use crate::module_recipe::{Module, ModuleExt, Recipe}; +use crate::shadow; + +use anyhow::Result; +use askama::Template; +use clap::Args; +use clap_complete::Shell; +use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; +use log::{debug, error, trace}; +use requestty::question::{completions, Completions}; +use std::borrow::Cow; +use std::fs; +use std::path::PathBuf; +use std::time::Duration; +use typed_builder::TypedBuilder; + +use super::utils::exec_cmd; +use super::BlueBuildCommand; + +const UNKNOWN_SHELL: &str = ""; +const UNKNOWN_VERSION: &str = ""; +const UNKNOWN_TERMINAL: &str = ""; +const GITHUB_CHAR_LIMIT: usize = 8100; // Magic number accepted by Github + +#[derive(Default, Debug, Clone, TypedBuilder, Args)] +pub struct BugReportRecipe { + recipe_dir: Option, + recipe_path: Option, +} + +#[derive(Debug, Clone, Args, TypedBuilder)] +pub struct BugReportCommand { + /// Path to the recipe file + #[arg(short, long)] + #[builder(default)] + recipe_path: Option, +} + +impl BlueBuildCommand for BugReportCommand { + fn try_run(&mut self) -> anyhow::Result<()> { + debug!( + "Generating bug report for hash: {}\n", + shadow::BB_COMMIT_HASH + ); + debug!("Shadow Versioning:\n{}", shadow::VERSION.trim()); + + self.create_bugreport() + } +} + +impl BugReportCommand { + /// Create a pre-populated GitHub issue with information about your configuration + /// + /// # Errors + /// + /// This function will return an error if it fails to open the issue in your browser. + /// If this happens, you can copy the generated report and open an issue manually. + /// + /// # Panics + /// + /// This function will panic if it fails to get the current shell or terminal version. + pub fn create_bugreport(&self) -> anyhow::Result<()> { + use colorized::{Color, Colors}; + + let os_info = os_info::get(); + let recipe = self.get_recipe(); + + let environment = Environment { + os_type: os_info.os_type(), + shell_info: get_shell_info(), + terminal_info: get_terminal_info(), + os_version: os_info.version().clone(), + }; + + let issue_body = match generate_github_issue(&environment, &recipe) { + Ok(body) => body, + Err(e) => { + println!( + "{}: {e}", + "Failed to generate bug report".color(Colors::BrightRedFg) + ); + return Err(e); + } + }; + + println!( + "\n{}\n{}\n", + "Generated bug report:".color(Colors::BrightGreenFg), + issue_body + .color(Colors::BrightBlackBg) + .color(Colors::BrightWhiteFg) + ); + + const WARNING_MESSAGE: &str = "Please copy the above report and open an issue manually."; + let question = requestty::Question::confirm("anonymous") + .message( + "Forward the pre-filled report above to GitHub in your browser?" + .color(Colors::BrightYellowFg), + ) + .default(true) + .build(); + + println!("{} To avoid any sensitive data from being exposed, please review the included information before proceeding.", "Warning:".color(Colors::BrightRedBg).color(Colors::BrightWhiteFg)); + println!("Data forwarded to GitHub is subject to GitHub's privacy policy. For more information, see https://docs.github.com/en/github/site-policy/github-privacy-statement.\n"); + match requestty::prompt_one(question) { + Ok(answer) => { + if answer.as_bool().unwrap() { + let link = make_github_issue_link(&issue_body); + if let Err(e) = open::that(&link) { + println!("Failed to open issue report in your browser: {e}"); + println!("Please copy the above report and open an issue manually, or try opening the following link:\n{link}"); + return Err(e.into()); + } + } else { + println!("{WARNING_MESSAGE}"); + } + } + Err(_) => { + println!("Will not open an issue in your browser! {WARNING_MESSAGE}"); + } + } + + println!( + "\n{}", + "Thanks for using the BlueBuild bug report tool!".color(Colors::BrightCyanFg) + ); + + Ok(()) + } + + fn get_recipe(&self) -> Option { + let recipe_path = if let Some(recipe_path) = self.recipe_path.clone() { + recipe_path + } else if let Ok(recipe) = get_config_file("recipe", "Enter path to recipe file") { + recipe + } else { + trace!("Failed to get recipe"); + String::new() + }; + + Recipe::parse(&recipe_path).ok() + } +} + +fn get_config_file(title: &str, message: &str) -> anyhow::Result { + use std::path::Path; + + let question = requestty::Question::input(title) + .message(message) + .auto_complete(|p, _| auto_complete(p)) + .validate(|p, _| { + if (p.as_ref() as &Path).exists() { + Ok(()) + } else if p.is_empty() { + Err("No file specified. Please enter a file path".to_string()) + } else { + Err(format!("file `{p}` doesn't exist")) + } + }) + .build(); + + match requestty::prompt_one(question) { + Ok(requestty::Answer::String(path)) => Ok(path), + Ok(_) => unreachable!(), + Err(e) => { + trace!("Failed to get file: {}", e); + Err(e.into()) + } + } +} + +fn auto_complete(p: String) -> Completions { + use std::path::Path; + + let current: &Path = p.as_ref(); + let (mut dir, last) = if p.ends_with('/') { + (current, "") + } else { + let dir = current.parent().unwrap_or_else(|| "/".as_ref()); + let last = current + .file_name() + .and_then(std::ffi::OsStr::to_str) + .unwrap_or(""); + (dir, last) + }; + + if dir.to_str().unwrap().is_empty() { + dir = ".".as_ref(); + } + + let mut files: Completions<_> = match dir.read_dir() { + Ok(files) => files + .flatten() + .filter_map(|file| { + let path = file.path(); + let is_dir = path.is_dir(); + match path.into_os_string().into_string() { + Ok(s) if is_dir => Some(s + "/"), + Ok(s) => Some(s), + Err(_) => None, + } + }) + .collect(), + Err(_) => { + return completions![p]; + } + }; + + if files.is_empty() { + return completions![p]; + } + + let fuzzer = SkimMatcherV2::default(); + files.sort_by_cached_key(|file| fuzzer.fuzzy_match(file, last).unwrap_or(i64::MAX)); + files +} + +// ============================================================================= // + +struct Environment { + shell_info: ShellInfo, + os_type: os_info::Type, + terminal_info: TerminalInfo, + os_version: os_info::Version, +} + +#[derive(Debug)] +struct TerminalInfo { + name: String, + version: String, +} + +fn get_terminal_info() -> TerminalInfo { + let terminal = std::env::var("TERM_PROGRAM") + .or_else(|_| std::env::var("LC_TERMINAL")) + .unwrap_or_else(|_| UNKNOWN_TERMINAL.to_string()); + + let version = std::env::var("TERM_PROGRAM_VERSION") + .or_else(|_| std::env::var("LC_TERMINAL_VERSION")) + .unwrap_or_else(|_| UNKNOWN_VERSION.to_string()); + + TerminalInfo { + name: terminal, + version, + } +} + +#[derive(Debug)] +struct ShellInfo { + name: String, + version: String, +} + +fn get_shell_info() -> ShellInfo { + let failure_shell_info = ShellInfo { + name: UNKNOWN_SHELL.to_string(), + version: UNKNOWN_VERSION.to_string(), + }; + + let current_shell = match Shell::from_env() { + Some(shell) => shell.to_string(), + None => return failure_shell_info, + }; + + let version = get_shell_version(¤t_shell); + + ShellInfo { + version, + name: current_shell.to_string(), + } +} + +fn get_shell_version(shell: &str) -> String { + let time_limit = Duration::from_millis(500); + match shell { + "powershecll" => { + error!("Powershell is not supported."); + None + } + _ => exec_cmd(shell, &["--version"], time_limit), + } + .map_or_else( + || UNKNOWN_VERSION.to_string(), + |output| output.stdout.trim().to_string(), + ) +} + +// ============================================================================= // +// Git +// ============================================================================= // + +#[derive(Debug, Clone, Template, TypedBuilder)] +#[template(path = "github_issue.j2", escape = "md")] +struct GithubIssueTemplate<'a> { + #[builder(setter(into))] + bb_version: Cow<'a, str>, + + #[builder(setter(into))] + build_rust_channel: Cow<'a, str>, + + #[builder(setter(into))] + build_time: Cow<'a, str>, + + #[builder(setter(into))] + git_commit_hash: Cow<'a, str>, + + #[builder(setter(into))] + os_name: Cow<'a, str>, + + #[builder(setter(into))] + os_version: Cow<'a, str>, + + #[builder(setter(into))] + pkg_branch_tag: Cow<'a, str>, + + #[builder(setter(into))] + recipe: Cow<'a, str>, + + #[builder(setter(into))] + rust_channel: Cow<'a, str>, + + #[builder(setter(into))] + rust_version: Cow<'a, str>, + + #[builder(setter(into))] + shell_name: Cow<'a, str>, + + #[builder(setter(into))] + shell_version: Cow<'a, str>, + + #[builder(setter(into))] + terminal_name: Cow<'a, str>, + + #[builder(setter(into))] + terminal_version: Cow<'a, str>, +} + +fn get_pkg_branch_tag() -> String { + format!("{} ({})", shadow::BRANCH, shadow::LAST_TAG) +} + +fn generate_github_issue( + environment: &Environment, + recipe: &Option, +) -> anyhow::Result { + let recipe = recipe + .as_ref() + .map_or_else(Default::default, |r| print_full_recipe(r)); + + let github_template = GithubIssueTemplate::builder() + .bb_version(shadow::PKG_VERSION) + .build_rust_channel(shadow::BUILD_RUST_CHANNEL) + .build_time(shadow::BUILD_TIME) + .git_commit_hash(shadow::BB_COMMIT_HASH) + .os_name(format!("{}", environment.os_type)) + .os_version(format!("{}", environment.os_version)) + .pkg_branch_tag(get_pkg_branch_tag()) + .recipe(recipe) + .rust_channel(shadow::RUST_CHANNEL) + .rust_version(shadow::RUST_VERSION) + .shell_name(environment.shell_info.name.clone()) + .shell_version(environment.shell_info.version.clone()) + .terminal_name(environment.terminal_info.name.clone()) + .terminal_version(environment.terminal_info.version.clone()) + .build(); + + Ok(github_template.render()?) +} + +fn make_github_issue_link(body: &str) -> String { + let escaped = urlencoding::encode(body).replace("%20", "+"); + + format!( + "https://github.com/blue-build/cli/issues/new?template={}&body={}", + urlencoding::encode("Bug_report.md"), + escaped + ) + .chars() + .take(GITHUB_CHAR_LIMIT) + .collect() +} + +fn get_module_from_file(file_name: &str) -> Result { + let file_path = PathBuf::from("config").join(file_name); + let file_path = if file_path.is_absolute() { + file_path + } else { + std::env::current_dir()?.join(file_path) + }; + + let file = fs::read_to_string(file_path.clone())?; + + serde_yaml::from_str::(file.as_str()).map_or_else( + |_| -> Result { + let module = serde_yaml::from_str::(file.as_str())?; + + Ok(ModuleExt::builder().modules(vec![module]).build()) + }, + Ok, + ) +} + +fn get_modules(modules: &[Module]) -> Vec { + modules + .iter() + .flat_map(|module| { + if let Some(file_name) = &module.from_file { + match get_module_from_file(file_name) { + Err(e) => { + error!("Failed to get module from {file_name}: {e}"); + vec![] + } + Ok(module_ext) => get_modules(&module_ext.modules), + } + } else { + vec![module.clone()] + } + }) + .collect() +} + +fn print_full_recipe(recipe: &Recipe) -> String { + let module_list: Vec = get_modules(&recipe.modules_ext.modules); + + let recipe = Recipe::builder() + .name(recipe.name.as_ref()) + .description(recipe.description.as_ref()) + .base_image(recipe.base_image.as_ref()) + .image_version(recipe.image_version.as_ref()) + .extra(recipe.extra.clone()) + .modules_ext(ModuleExt::builder().modules(module_list).build()) + .build(); + + serde_yaml::to_string(&recipe).unwrap_or_else(|e| { + error!("Failed to serialize recipe: {e}"); + format!("Error rendering recipe!!\n{e}") + }) +} +// ============================================================================= // + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + #[test] + fn test_make_github_link() { + let environment = Environment { + os_type: os_info::Type::Linux, + os_version: os_info::Version::Semantic(1, 2, 3), + shell_info: ShellInfo { + version: "2.3.4".to_string(), + name: "test_shell".to_string(), + }, + terminal_info: TerminalInfo { + name: "test_terminal".to_string(), + version: "5.6.7".to_string(), + }, + }; + + let recipe = Recipe::default(); + let body = generate_github_issue(&environment, &Some(recipe)).unwrap(); + let link = make_github_issue_link(&body); + + assert!(link.contains(clap::crate_version!())); + assert!(link.contains("Linux")); + assert!(link.contains("1.2.3")); + assert!(link.contains("test_shell")); + assert!(link.contains("2.3.4")); + } +} diff --git a/src/commands/build.rs b/src/commands/build.rs index 7017d45b..c2885466 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,21 +1,16 @@ #[cfg(feature = "podman-api")] mod build_strategy; -use std::{ - env, fs, - path::{Path, PathBuf}, - process::{self, Command}, -}; +use std::{env, fs, path::PathBuf, process::Command}; use anyhow::{anyhow, bail, Result}; use clap::Args; -use log::{debug, error, info, trace, warn}; +use log::{debug, info, trace, warn}; use typed_builder::TypedBuilder; #[cfg(feature = "podman-api")] use podman_api::{ - api::Image, - opts::{ImageBuildOpts, ImageListOpts, ImagePushOpts, RegistryAuth}, + opts::{ImageBuildOpts, ImagePushOpts, RegistryAuth}, Podman, }; @@ -183,8 +178,6 @@ impl BlueBuildCommand for BuildCommand { impl BuildCommand { #[cfg(feature = "podman-api")] async fn build_image_podman_api(&self, client: Podman) -> Result<()> { - use podman_api::opts::ImageTagOpts; - trace!("BuildCommand::build_image({client:#?})"); let credentials = self.get_login_creds(); diff --git a/src/commands/completions.rs b/src/commands/completions.rs new file mode 100644 index 00000000..24930230 --- /dev/null +++ b/src/commands/completions.rs @@ -0,0 +1,27 @@ +use clap::{Args, CommandFactory}; +use clap_complete::{generate, Shell as CompletionShell}; + +use crate::commands::BlueBuildArgs; + +use super::BlueBuildCommand; + +#[derive(Debug, Clone, Args)] +pub struct CompletionsCommand { + #[arg(value_enum)] + shell: CompletionShell, +} + +impl BlueBuildCommand for CompletionsCommand { + fn try_run(&mut self) -> anyhow::Result<()> { + log::debug!("Generating completions for {}", self.shell); + + generate( + self.shell, + &mut BlueBuildArgs::command(), + "bb", + &mut std::io::stdout().lock(), + ); + + Ok(()) + } +} diff --git a/src/commands/local.rs b/src/commands/local.rs index e4f8cebc..9d6c2d08 100644 --- a/src/commands/local.rs +++ b/src/commands/local.rs @@ -1,12 +1,12 @@ use std::{ fs, path::{Path, PathBuf}, - process::{self, Command}, + process::Command, }; use anyhow::{bail, Result}; -use clap::{Args, Subcommand}; -use log::{debug, error, info, trace}; +use clap::Args; +use log::{debug, info, trace}; use typed_builder::TypedBuilder; use users::{Users, UsersCache}; diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 00000000..9379a838 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,94 @@ +use log::error; + +use clap::{command, crate_authors, Parser, Subcommand}; +use clap_verbosity_flag::{InfoLevel, Verbosity}; + +pub mod bug_report; +pub mod build; +pub mod completions; +#[cfg(feature = "init")] +pub mod init; +pub mod local; +pub mod template; +pub mod utils; + +pub trait BlueBuildCommand { + /// Runs the command and returns a result + /// of the execution + /// + /// # Errors + /// Can return an `anyhow` Error + fn try_run(&mut self) -> anyhow::Result<()>; + + /// Runs the command and exits if there is an error. + fn run(&mut self) { + if let Err(e) = self.try_run() { + error!("{e}"); + std::process::exit(1); + } + } +} + +shadow_rs::shadow!(shadow); + +#[derive(Parser, Debug)] +#[clap( + name = "BlueBuild", + about, + long_about = None, + author=crate_authors!(), + version=shadow::PKG_VERSION, + long_version=shadow::CLAP_LONG_VERSION, +)] +pub struct BlueBuildArgs { + #[command(subcommand)] + pub command: CommandArgs, + + #[clap(flatten)] + pub verbosity: Verbosity, +} + +#[derive(Debug, Subcommand)] +pub enum CommandArgs { + /// Build an image from a recipe + Build(build::BuildCommand), + + /// Generate a Containerfile from a recipe + Template(template::TemplateCommand), + + /// Upgrade your current OS with the + /// local image saved at `/etc/blue-build/`. + /// + /// This requires having rebased already onto + /// a local archive already by using the `rebase` + /// subcommand. + /// + /// NOTE: This can only be used if you have `rpm-ostree` + /// installed and if the `--push` and `--rebase` option isn't + /// used. This image will not be signed. + Upgrade(local::UpgradeCommand), + + /// Rebase your current OS onto the image + /// being built. + /// + /// This will create a tarball of your image at + /// `/etc/blue-build/` and invoke `rpm-ostree` to + /// rebase onto the image using `oci-archive`. + /// + /// NOTE: This can only be used if you have `rpm-ostree` + /// installed. + Rebase(local::RebaseCommand), + + /// Initialize a new Ublue Starting Point repo + #[cfg(feature = "init")] + Init(init::InitCommand), + + #[cfg(feature = "init")] + New(init::NewCommand), + + /// Create a pre-populated GitHub issue with information about your configuration + BugReport(bug_report::BugReportCommand), + + /// Generate shell completions for your shell to stdout + Completions(completions::CompletionsCommand), +} diff --git a/src/commands/template.rs b/src/commands/template.rs index ed36e420..c29b0bef 100644 --- a/src/commands/template.rs +++ b/src/commands/template.rs @@ -1,18 +1,13 @@ use std::{ - collections::HashMap, env, fs, path::{Path, PathBuf}, process, }; -use anyhow::{Error, Result}; +use anyhow::Result; use askama::Template; -use chrono::Local; use clap::Args; -use indexmap::IndexMap; -use log::{debug, error, info, trace, warn}; -use serde::{Deserialize, Serialize}; -use serde_yaml::Value; +use log::{debug, error, info, trace}; use typed_builder::TypedBuilder; use crate::module_recipe::{Module, ModuleExt, Recipe}; @@ -22,7 +17,7 @@ use super::BlueBuildCommand; #[derive(Debug, Clone, Template, TypedBuilder)] #[template(path = "Containerfile")] pub struct ContainerFileTemplate<'a> { - recipe: &'a Recipe, + recipe: &'a Recipe<'a>, recipe_path: &'a Path, module_template: ModuleTemplate<'a>, @@ -67,7 +62,7 @@ impl TemplateCommand { trace!("TemplateCommand::template_file()"); debug!("Deserializing recipe"); - let recipe_de = serde_yaml::from_str::(fs::read_to_string(&self.recipe)?.as_str())?; + let recipe_de = Recipe::parse(&self.recipe)?; trace!("recipe_de: {recipe_de:#?}"); let template = ContainerFileTemplate::builder() @@ -119,11 +114,11 @@ fn print_script(script_contents: &ExportsTemplate) -> String { fn running_gitlab_actions() -> bool { trace!(" running_gitlab_actions()"); - env::var("GITHUB_ACTIONS").is_ok_and(|e| e == "true") } -fn get_containerfile_list(module: &Module) -> Option> { +#[must_use] +pub fn get_containerfile_list(module: &Module) -> Option> { if module.module_type.as_ref()? == "containerfile" { Some( module @@ -139,8 +134,9 @@ fn get_containerfile_list(module: &Module) -> Option> { } } -fn print_containerfile(containerfile: &str) -> String { - trace!("print_containerfile({containerfile})"); +#[must_use] +pub fn print_containerfile(containerfile: &str) -> String { + debug!("print_containerfile({containerfile})"); debug!("Loading containerfile contents for {containerfile}"); let path = format!("config/containerfiles/{containerfile}/Containerfile"); @@ -150,27 +146,20 @@ fn print_containerfile(containerfile: &str) -> String { process::exit(1); }); - trace!("Containerfile contents {path}:\n{file}"); + debug!("Containerfile contents {path}:\n{file}"); file } -fn get_module_from_file(file_name: &str) -> String { - trace!("get_module_from_file({file_name})"); - - let io_err_fn = |e| { - error!("Failed to read module {file_name}: {e}"); - process::exit(1); - }; +#[must_use] +pub fn template_module_from_file(file_name: &str) -> String { + debug!("get_module_from_file({file_name})"); let file_path = PathBuf::from("config").join(file_name); - - let file = fs::read_to_string(file_path).unwrap_or_else(io_err_fn); - - let serde_err_fn = |e| { - error!("Failed to deserialize module {file_name}: {e}"); + let file = fs::read_to_string(file_path).unwrap_or_else(|e| { + error!("Failed to read module {file_name}: {e}"); process::exit(1); - }; + }); let template_err_fn = |e| { error!("Failed to render module {file_name}: {e}"); @@ -179,7 +168,10 @@ fn get_module_from_file(file_name: &str) -> String { serde_yaml::from_str::(file.as_str()).map_or_else( |_| { - let module = serde_yaml::from_str::(file.as_str()).unwrap_or_else(serde_err_fn); + let module = serde_yaml::from_str::(file.as_str()).unwrap_or_else(|e| { + error!("Failed to deserialize module {file_name}: {e}"); + process::exit(1); + }); ModuleTemplate::builder() .module_ext(&ModuleExt::builder().modules(vec![module]).build()) diff --git a/src/commands/utils.rs b/src/commands/utils.rs new file mode 100644 index 00000000..3e87bb46 --- /dev/null +++ b/src/commands/utils.rs @@ -0,0 +1,136 @@ +use process_control::{ChildExt, Control}; +use std::ffi::OsStr; +use std::fmt::Debug; +use std::io::{Error, ErrorKind, Result}; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::time::{Duration, Instant}; + +#[must_use] +pub fn home_dir() -> Option { + directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_path_buf()) +} + +// ================================================================================================= // +// CommandOutput +// ================================================================================================= // + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CommandOutput { + pub stdout: String, + pub stderr: String, +} + +/// # Attempt to resolve `binary_name` from and creates a new `Command` pointing at it +/// # This allows executing cmd files on Windows and prevents running executable from cwd on Windows +/// # This function also initializes std{err,out,in} to protect against processes changing the console mode +/// # +/// # Errors +/// +pub fn create_command>(binary_name: T) -> Result { + let binary_name = binary_name.as_ref(); + log::trace!("Creating Command for binary {:?}", binary_name); + + let full_path = match which::which(binary_name) { + Ok(full_path) => { + log::trace!("Using {:?} as {:?}", full_path, binary_name); + full_path + } + Err(error) => { + log::trace!("Unable to find {:?} in PATH, {:?}", binary_name, error); + return Err(Error::new(ErrorKind::NotFound, error)); + } + }; + + #[allow(clippy::disallowed_methods)] + let mut cmd = Command::new(full_path); + cmd.stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .stdin(Stdio::null()); + + Ok(cmd) +} + +/// Execute a command and return the output on stdout and stderr if successful +pub fn exec_cmd + Debug, U: AsRef + Debug>( + cmd: T, + args: &[U], + time_limit: Duration, +) -> Option { + log::trace!("Executing command {:?} with args {:?}", cmd, args); + internal_exec_cmd(cmd, args, time_limit) +} + +fn internal_exec_cmd + Debug, U: AsRef + Debug>( + cmd: T, + args: &[U], + time_limit: Duration, +) -> Option { + let mut cmd = create_command(cmd).ok()?; + cmd.args(args); + exec_timeout(&mut cmd, time_limit) +} + +pub fn exec_timeout(cmd: &mut Command, time_limit: Duration) -> Option { + let start = Instant::now(); + let process = match cmd.spawn() { + Ok(process) => process, + Err(error) => { + log::trace!("Unable to run {:?}, {:?}", cmd.get_program(), error); + return None; + } + }; + match process + .controlled_with_output() + .time_limit(time_limit) + .terminate_for_timeout() + .wait() + { + Ok(Some(output)) => { + let stdout_string = match String::from_utf8(output.stdout) { + Ok(stdout) => stdout, + Err(error) => { + log::warn!("Unable to decode stdout: {:?}", error); + return None; + } + }; + let stderr_string = match String::from_utf8(output.stderr) { + Ok(stderr) => stderr, + Err(error) => { + log::warn!("Unable to decode stderr: {:?}", error); + return None; + } + }; + + log::trace!( + "stdout: {:?}, stderr: {:?}, exit code: \"{:?}\", took {:?}", + stdout_string, + stderr_string, + output.status.code(), + start.elapsed() + ); + + if !output.status.success() { + return None; + } + + Some(CommandOutput { + stdout: stdout_string, + stderr: stderr_string, + }) + } + Ok(None) => { + log::warn!("Executing command {:?} timed out.", cmd.get_program()); + log::warn!("You can set command_timeout in your config to a higher value to allow longer-running commands to keep executing."); + None + } + Err(error) => { + log::trace!( + "Executing command {:?} failed by: {:?}", + cmd.get_program(), + error + ); + None + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 85292f88..d1f5ee3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,14 @@ //! The root library for blue-build. -#![warn( - clippy::correctness, - clippy::suspicious, - clippy::perf, - clippy::style, - clippy::pedantic -)] +#![warn(clippy::correctness, clippy::suspicious, clippy::perf, clippy::style)] #![doc( html_logo_url = "https://gitlab.com/wunker-bunker/blue-build/-/raw/main/logos/BlueBuild-logo.png" )] #![doc = include_str!("../README.md")] #![forbid(unsafe_code)] -#![allow(unused_imports)] #![allow(clippy::module_name_repetitions)] +shadow_rs::shadow!(shadow); + pub mod commands; pub mod module_recipe; mod ops; diff --git a/src/module_recipe.rs b/src/module_recipe.rs index 9206f16b..23089254 100644 --- a/src/module_recipe.rs +++ b/src/module_recipe.rs @@ -1,32 +1,31 @@ -use std::{env, fs, path::PathBuf, process}; +use std::{borrow::Cow, env, fs, path::Path, process}; -use askama::Template; use chrono::Local; use indexmap::IndexMap; -use log::{debug, error, info, trace, warn}; +use log::{debug, error, trace, warn}; use serde::{Deserialize, Serialize}; use serde_yaml::Value; use typed_builder::TypedBuilder; -#[derive(Serialize, Clone, Deserialize, Debug, TypedBuilder)] -pub struct Recipe { +#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)] +pub struct Recipe<'a> { #[builder(setter(into))] - pub name: String, + pub name: Cow<'a, str>, #[builder(setter(into))] - pub description: String, + pub description: Cow<'a, str>, #[serde(alias = "base-image")] #[builder(setter(into))] - pub base_image: String, + pub base_image: Cow<'a, str>, #[serde(alias = "image-version")] #[builder(setter(into))] - pub image_version: String, + pub image_version: Cow<'a, str>, #[serde(alias = "blue-build-tag")] #[builder(default, setter(into, strip_option))] - pub blue_build_tag: Option, + pub blue_build_tag: Option>, #[serde(flatten)] pub modules_ext: ModuleExt, @@ -36,11 +35,11 @@ pub struct Recipe { pub extra: IndexMap, } -impl Recipe { +impl<'a> Recipe<'a> { #[must_use] pub fn generate_tags(&self) -> Vec { trace!("Recipe::generate_tags()"); - debug!("Generating image tags for {}", &self.name); + trace!("Generating image tags for {}", &self.name); let mut tags: Vec = Vec::new(); let image_version = &self.image_version; @@ -95,7 +94,7 @@ impl Recipe { debug!("Running in a PR"); tags.push(format!("pr-{github_event_number}-{image_version}")); } else if github_ref_name == "live" { - tags.push(image_version.to_owned()); + tags.push(image_version.to_string()); tags.push(format!("{image_version}-{timestamp}")); tags.push("latest".to_string()); } else { @@ -106,13 +105,40 @@ impl Recipe { warn!("Running locally"); tags.push(format!("{image_version}-local")); } - info!("Finished generating tags!"); + debug!("Finished generating tags!"); debug!("Tags: {tags:#?}"); tags } + + /// # Parse a recipe file + /// # + /// # Errors + pub fn parse>(path: &P) -> anyhow::Result { + let file_path = if Path::new(path.as_ref()).is_absolute() { + path.as_ref().to_path_buf() + } else { + std::env::current_dir()?.join(path.as_ref()) + }; + + let recipe_path = fs::canonicalize(file_path)?; + let recipe_path_string = recipe_path.display().to_string(); + debug!("Recipe::parse_recipe({recipe_path_string})"); + + let file = fs::read_to_string(recipe_path).unwrap_or_else(|e| { + error!("Failed to read file {recipe_path_string}: {e}"); + process::exit(1); + }); + + debug!("Recipe contents: {file}"); + + serde_yaml::from_str::(file.as_str()).map_err(|e| { + error!("Failed to parse recipe {recipe_path_string}: {e}"); + process::exit(1); + }) + } } -#[derive(Serialize, Clone, Deserialize, Debug, TypedBuilder)] +#[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)] pub struct ModuleExt { #[builder(default, setter(into))] pub modules: Vec, @@ -120,19 +146,15 @@ pub struct ModuleExt { #[derive(Serialize, Deserialize, Debug, Clone, TypedBuilder)] pub struct Module { - #[serde(rename = "type")] #[builder(default, setter(into, strip_option))] + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub module_type: Option, - #[serde(rename = "from-file")] #[builder(default, setter(into, strip_option))] + #[serde(rename = "from-file", skip_serializing_if = "Option::is_none")] pub from_file: Option, #[serde(flatten)] #[builder(default, setter(into))] pub config: IndexMap, } - -// ======================================================== // -// ========================= Helpers ====================== // -// ======================================================== // diff --git a/src/ops.rs b/src/ops.rs index dc296b09..6a3973c1 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; use log::{debug, trace}; -use std::{path::Path, process::Command}; +use std::process::Command; pub const LOCAL_BUILD: &str = "/etc/blue-build"; pub const ARCHIVE_SUFFIX: &str = "tar.gz"; diff --git a/templates/Containerfile.module b/templates/Containerfile.module index c95af17b..8f58442b 100644 --- a/templates/Containerfile.module +++ b/templates/Containerfile.module @@ -10,7 +10,7 @@ RUN chmod +x /tmp/modules/{{ type }}/{{ type }}.sh && source /tmp/exports.sh && /tmp/modules/{{ type }}/{{ type }}.sh '{{ self::print_module_context(module) }}' {%- endif %} {%- else if let Some(from_file) = module.from_file %} -{{ self::get_module_from_file(from_file) }} +{{ self::template_module_from_file(from_file) }} {%- endif %} {%- endfor %} diff --git a/templates/github_issue.j2 b/templates/github_issue.j2 new file mode 100644 index 00000000..671821b3 --- /dev/null +++ b/templates/github_issue.j2 @@ -0,0 +1,35 @@ +#### Current Behavior + + +#### Expected Behavior + + +#### Additional context/Screenshots + + +#### Possible Solution + + +#### Environment +- Blue Build Version: {{ bb_version }} +- Operating system: {{ os_name }} {{ os_version }} +- Branch/Tag: {{ pkg_branch_tag }} +- Git Commit Hash: {{ git_commit_hash }} + +#### Shell +- Name: {{ shell_name }} +- Version: {{ shell_version }} +- Terminal emulator: {{ terminal_name }} {{ terminal_version }} + +#### Rust +- Rust Version: {{ rust_version }} +- Rust channel: {{ rust_channel }} {{ build_rust_channel }} +- Build Time: {{ build_time }} + +{%- if !recipe.is_empty() %} + +#### Recipe: +```yml +{{ recipe }} +``` +{%- endif %}