diff --git a/Cargo.lock b/Cargo.lock index 0ec112c23..aad637544 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,9 +147,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" dependencies = [ "backtrace", ] @@ -257,7 +257,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef1bb8d1b645fe38d51dfc331d720fb5fc2c94b440c76cc79c80ff265ca33e3" dependencies = [ - "rustix", + "rustix 0.38.34", "tempfile", "windows-sys 0.52.0", ] @@ -567,7 +567,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "thiserror 1.0.64", @@ -581,7 +581,7 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "thiserror 2.0.16", @@ -661,7 +661,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim 0.11.1", - "terminal_size", + "terminal_size 0.3.0", ] [[package]] @@ -734,7 +734,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.14", "windows-sys 0.52.0", ] @@ -907,7 +907,7 @@ dependencies = [ "reqwest", "ringbuffer", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "slog", @@ -1345,6 +1345,20 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "drift" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43eb40edecda6106744f5e4f3d4dc78b3adf19d3cfb2d81cc4faa007da91e527" +dependencies = [ + "anyhow", + "indexmap 2.11.4", + "openapiv3", + "regex", + "serde", + "serde_json", +] + [[package]] name = "dropshot" version = "0.16.4" @@ -1375,7 +1389,7 @@ dependencies = [ "rustls-pemfile 2.1.3", "schemars", "scopeguard", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "serde_path_to_error", @@ -1396,6 +1410,49 @@ dependencies = [ "waitgroup", ] +[[package]] +name = "dropshot-api-manager" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0c9a9b3587eb5c7da419466203773307767124fa20e84068d9dd06ee34caa9" +dependencies = [ + "anyhow", + "atomicwrites", + "camino", + "clap", + "debug-ignore", + "drift", + "dropshot", + "dropshot-api-manager-types", + "fs-err", + "hex", + "indent_write", + "newtype_derive", + "openapiv3", + "owo-colors", + "paste", + "semver 1.0.27", + "serde_json", + "sha2 0.10.9", + "similar", + "supports-color 3.0.2", + "textwrap", + "thiserror 2.0.16", +] + +[[package]] +name = "dropshot-api-manager-types" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00be3e4459aae391b88805e9f735c7cf9cf4ed6aad02bc0e92b224b590af39ab" +dependencies = [ + "anyhow", + "camino", + "paste", + "semver 1.0.27", + "serde_json", +] + [[package]] name = "dropshot_endpoint" version = "0.16.4" @@ -1405,7 +1462,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_tokenstream 0.2.2", "syn 2.0.106", @@ -1513,12 +1570,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -1719,6 +1776,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fs-err" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f150ffc8782f35521cec2b23727707cb4045706ba3c854e86bef66b3a8cdbd" +dependencies = [ + "autocfg", +] + [[package]] name = "funty" version = "2.0.0" @@ -1926,7 +1992,7 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", + "regex-automata 0.4.11", "regex-syntax 0.8.5", ] @@ -2253,7 +2319,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -2908,6 +2974,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.7.4" @@ -3225,7 +3297,7 @@ dependencies = [ "oxql-types", "parse-display", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "serde_with", @@ -3518,7 +3590,7 @@ dependencies = [ "regress", "reqwest", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_human_bytes", "serde_json", @@ -3581,11 +3653,11 @@ dependencies = [ "futures-util", "hex", "reqwest", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_derive", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "slog", "tar", "thiserror 1.0.64", @@ -3734,12 +3806,12 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "4.2.0" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" dependencies = [ "supports-color 2.1.0", - "supports-color 3.0.1", + "supports-color 3.0.2", ] [[package]] @@ -4076,7 +4148,7 @@ checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -4758,6 +4830,19 @@ dependencies = [ "uuid", ] +[[package]] +name = "propolis-dropshot-apis" +version = "0.1.0" +dependencies = [ + "anyhow", + "camino", + "clap", + "dropshot-api-manager", + "dropshot-api-manager-types", + "propolis-server-api", + "semver 1.0.27", +] + [[package]] name = "propolis-mock-server" version = "0.0.0" @@ -4775,7 +4860,7 @@ dependencies = [ "rand 0.9.2", "reqwest", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "slog", @@ -4846,7 +4931,7 @@ dependencies = [ "ring", "ron", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_derive", "serde_json", @@ -5180,13 +5265,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.11", "regex-syntax 0.8.5", ] @@ -5201,9 +5286,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -5382,7 +5467,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", + "semver 1.0.27", ] [[package]] @@ -5392,12 +5477,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.6.0", - "errno 0.3.9", + "errno 0.3.14", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.6.0", + "errno 0.3.14", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.60.2", +] + [[package]] name = "rustls" version = "0.21.12" @@ -5554,7 +5652,7 @@ dependencies = [ "chrono", "dyn-clone", "schemars_derive", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "uuid", @@ -5648,11 +5746,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -5897,9 +5996,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -5957,9 +6056,12 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "similar" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +dependencies = [ + "bstr", +] [[package]] name = "siphasher" @@ -6120,6 +6222,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "smf" version = "0.2.3" @@ -6324,9 +6432,9 @@ dependencies = [ [[package]] name = "supports-color" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" dependencies = [ "is_ci", ] @@ -6418,7 +6526,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a327282c4f64f6dc37e3bba4c2b6842cc3a992f204fa58d917696a89f691e5f6" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -6452,7 +6560,7 @@ checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -6462,7 +6570,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6471,10 +6579,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix", + "rustix 0.38.34", "windows-sys 0.48.0", ] +[[package]] +name = "terminal_size" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +dependencies = [ + "rustix 1.1.2", + "windows-sys 0.60.2", +] + [[package]] name = "terminfo" version = "0.7.5" @@ -6556,6 +6674,18 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "terminal_size 0.4.3", + "unicode-linebreak", + "unicode-width 0.2.1", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -7030,7 +7160,7 @@ dependencies = [ "parse-display", "proptest", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_human_bytes", "strum 0.26.3", @@ -7120,7 +7250,7 @@ dependencies = [ "quote", "regress", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "syn 2.0.106", @@ -7140,7 +7270,7 @@ dependencies = [ "quote", "regress", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "syn 2.0.106", @@ -7160,7 +7290,7 @@ dependencies = [ "quote", "regress", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "syn 2.0.106", @@ -7177,7 +7307,7 @@ dependencies = [ "proc-macro2", "quote", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "serde_tokenstream 0.2.2", @@ -7194,7 +7324,7 @@ dependencies = [ "proc-macro2", "quote", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "serde_tokenstream 0.2.2", @@ -7211,7 +7341,7 @@ dependencies = [ "proc-macro2", "quote", "schemars", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "serde_tokenstream 0.2.2", @@ -7237,6 +7367,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -7249,6 +7385,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -7303,7 +7445,7 @@ dependencies = [ "slog", "swrite", "tokio", - "unicode-width", + "unicode-width 0.1.14", "uuid", ] @@ -7942,6 +8084,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -7966,13 +8117,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -7985,6 +8152,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -7997,6 +8170,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -8009,12 +8188,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -8027,6 +8218,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -8039,6 +8236,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -8051,6 +8254,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -8063,6 +8272,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.5.40" @@ -8128,8 +8343,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", - "linux-raw-sys", - "rustix", + "linux-raw-sys 0.4.14", + "rustix 0.38.34", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7427e1716..ecf6db399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ default-members = [ "crates/*", "crates/*/sys", "lib/*", + "bin/dropshot-apis", "bin/propolis-cli", "bin/propolis-server", "bin/propolis-standalone", @@ -113,6 +114,8 @@ const_format = "0.2" crossbeam-channel = "0.5" ctrlc = "3.2" dropshot = "0.16.4" +dropshot-api-manager = "0.2.2" +dropshot-api-manager-types = "0.2.2" erased-serde = "0.4" errno = "0.2.8" escargot = "0.5.8" diff --git a/bin/dropshot-apis/Cargo.toml b/bin/dropshot-apis/Cargo.toml new file mode 100644 index 000000000..b5b827b91 --- /dev/null +++ b/bin/dropshot-apis/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "propolis-dropshot-apis" +version = "0.1.0" +edition = "2024" +license = "MPL-2.0" + +[dependencies] +anyhow.workspace = true +camino.workspace = true +clap.workspace = true +dropshot-api-manager-types.workspace = true +dropshot-api-manager.workspace = true +propolis-server-api.workspace = true +semver.workspace = true diff --git a/bin/dropshot-apis/src/main.rs b/bin/dropshot-apis/src/main.rs new file mode 100644 index 000000000..3e8de339a --- /dev/null +++ b/bin/dropshot-apis/src/main.rs @@ -0,0 +1,81 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::process::ExitCode; + +use anyhow::Context; +use camino::Utf8PathBuf; +use clap::Parser; +use dropshot_api_manager::{Environment, ManagedApiConfig, ManagedApis}; +use dropshot_api_manager_types::{ManagedApiMetadata, Versions}; +use propolis_server_api::*; + +pub fn environment() -> anyhow::Result { + // The workspace root is two levels up from this crate's directory. + let workspace_root = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .to_path_buf(); + let env = Environment::new( + // This is the command used to run the OpenAPI manager. + "cargo xtask openapi", + workspace_root, + // This is the location within the workspace root where the OpenAPI + // documents are stored. + "openapi", + )? + .with_default_git_branch("origin/master".to_owned()); + Ok(env) +} + +/// The list of APIs managed by the OpenAPI manager. +pub fn all_apis() -> anyhow::Result { + let apis = vec![ManagedApiConfig { + ident: "propolis-server", + versions: Versions::Lockstep { version: semver::Version::new(0, 0, 1) }, + title: "Oxide Propolis Server API", + metadata: ManagedApiMetadata { + description: Some( + "API for interacting with the Propolis hypervisor frontend.", + ), + contact_url: Some("https://oxide.computer"), + contact_email: Some("api@oxide.computer"), + ..Default::default() + }, + api_description: propolis_server_api_mod::stub_api_description, + extra_validation: None, + }]; + + let apis = ManagedApis::new(apis).context("error creating ManagedApis")?; + Ok(apis) +} + +fn main() -> anyhow::Result { + let app = dropshot_api_manager::App::parse(); + let env = environment()?; + let apis = all_apis()?; + + Ok(app.exec(&env, &apis)) +} + +#[cfg(test)] +mod test { + use dropshot_api_manager::test_util::check_apis_up_to_date; + + use super::*; + + // Also recommended: a test which ensures documents are up-to-date. The + // OpenAPI manager comes with a helper function for this, called + // `check_apis_up_to_date`. + #[test] + fn test_apis_up_to_date() -> anyhow::Result { + let env = environment()?; + let apis = all_apis()?; + + let result = check_apis_up_to_date(&env, &apis)?; + Ok(result.to_exit_code()) + } +} diff --git a/bin/mock-server/src/main.rs b/bin/mock-server/src/main.rs index d229a0082..32dc8bfa8 100644 --- a/bin/mock-server/src/main.rs +++ b/bin/mock-server/src/main.rs @@ -91,11 +91,11 @@ async fn run_server( Arc::new(context), &log, ) - .map_err(|error| anyhow!("Failed to start server: {}", error))? + .map_err(|error| anyhow!("Failed to start server: {error}"))? .start(); let server_res = server.await; - server_res.map_err(|e| anyhow!("Server exited with an error: {}", e)) + server_res.map_err(|e| anyhow!("Server exited with an error: {e}")) } #[tokio::main] @@ -105,7 +105,7 @@ async fn main() -> anyhow::Result<()> { match args { Args::OpenApi => run_openapi() - .map_err(|e| anyhow!("Cannot generate OpenAPI spec: {}", e)), + .map_err(|e| anyhow!("Cannot generate OpenAPI spec: {e}")), Args::Run { cfg: _cfg, propolis_addr, metric_addr } => { // Dropshot configuration. let config_dropshot = ConfigDropshot { diff --git a/bin/propolis-cli/src/main.rs b/bin/propolis-cli/src/main.rs index e380d469c..828d35acd 100644 --- a/bin/propolis-cli/src/main.rs +++ b/bin/propolis-cli/src/main.rs @@ -443,9 +443,7 @@ fn resolve_host(server: &str) -> anyhow::Result { .to_socket_addrs()? .map(|sock_addr| sock_addr.ip()) .next() - .ok_or_else(|| { - anyhow!("failed to resolve server argument '{}'", server) - }) + .ok_or_else(|| anyhow!("failed to resolve server argument '{server}'")) } /// Create a top-level logger that outputs to stderr @@ -689,7 +687,7 @@ async fn serial( | CloseCode::Protocol | CloseCode::Size | CloseCode::Unsupported => { - anyhow::bail!("{}", reason); + anyhow::bail!("{reason}"); } _ => break, } diff --git a/bin/propolis-server/src/lib/migrate/protocol.rs b/bin/propolis-server/src/lib/migrate/protocol.rs index 6a278091f..56d813e85 100644 --- a/bin/propolis-server/src/lib/migrate/protocol.rs +++ b/bin/propolis-server/src/lib/migrate/protocol.rs @@ -43,8 +43,7 @@ impl TryFrom for Protocol { Self::RonV0 } _ => anyhow::bail!(format!( - "no protocol matching definition: {:?}", - value + "no protocol matching definition: {value:?}" )), }; diff --git a/bin/propolis-server/src/lib/server.rs b/bin/propolis-server/src/lib/server.rs index b88bebee6..d25bb8070 100644 --- a/bin/propolis-server/src/lib/server.rs +++ b/bin/propolis-server/src/lib/server.rs @@ -646,26 +646,3 @@ fn not_created_error() -> HttpError { "Server not initialized (no instance)".to_string(), ) } - -#[cfg(test)] -mod test { - #[test] - fn test_propolis_server_openapi() { - let mut buf: Vec = vec![]; - propolis_server_api::propolis_server_api_mod::stub_api_description() - .unwrap() - .openapi("Oxide Propolis Server API", semver::Version::new(0, 0, 1)) - .description( - "API for interacting with the Propolis hypervisor frontend.", - ) - .contact_url("https://oxide.computer") - .contact_email("api@oxide.computer") - .write(&mut buf) - .unwrap(); - let output = String::from_utf8(buf).unwrap(); - expectorate::assert_contents( - "../../openapi/propolis-server.json", - &output, - ); - } -} diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index 628d9e466..87106912d 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -70,8 +70,6 @@ fn parse_log_level(s: &str) -> anyhow::Result { #[clap(about, version)] /// An HTTP server providing access to Propolis enum Args { - /// Generates the OpenAPI specification. - OpenApi, /// Runs the Propolis server. Run { #[clap(action)] @@ -107,18 +105,6 @@ enum Args { }, } -pub fn run_openapi() -> Result<(), String> { - server::api() - .openapi("Oxide Propolis Server API", semver::Version::new(0, 0, 1)) - .description( - "API for interacting with the Propolis hypervisor frontend.", - ) - .contact_url("https://oxide.computer") - .contact_email("api@oxide.computer") - .write(&mut std::io::stdout()) - .map_err(|e| e.to_string()) -} - fn run_server( bootrom_path: PathBuf, bootrom_version: Option, @@ -187,7 +173,7 @@ fn run_server( Arc::new(context), &log, ) - .map_err(|error| anyhow!("Failed to start server: {}", error))? + .map_err(|error| anyhow!("Failed to start server: {error}"))? .start(); let result = api_runtime.block_on(server); @@ -197,7 +183,7 @@ fn run_server( api_runtime.block_on(async { vnc.halt().await }); } - result.map_err(|e| anyhow!("Server exited with an error: {}", e)) + result.map_err(|e| anyhow!("Server exited with an error: {e}")) } fn build_logger(level: slog::Level) -> slog::Logger { @@ -298,8 +284,6 @@ fn main() -> anyhow::Result<()> { let args = Args::parse(); match args { - Args::OpenApi => run_openapi() - .map_err(|e| anyhow!("Cannot generate OpenAPI spec: {}", e)), Args::Run { bootrom_path, bootrom_version, diff --git a/bin/propolis-standalone/src/snapshot.rs b/bin/propolis-standalone/src/snapshot.rs index b66135e93..a390dc32c 100644 --- a/bin/propolis-standalone/src/snapshot.rs +++ b/bin/propolis-standalone/src/snapshot.rs @@ -114,8 +114,7 @@ pub(crate) fn save( let device_data = match dev.migrate() { Migrator::NonMigratable => { anyhow::bail!( - "Can't snapshot instance with non-migratable device ({})", - name + "Can't snapshot instance with non-migratable device ({name})" ); } Migrator::Empty => continue, @@ -343,13 +342,12 @@ pub(crate) fn restore( for snap_dev in device_data { let name = &snap_dev.instance_name; let dev = guard.inventory.devs.get(name).ok_or_else(|| { - anyhow::anyhow!("unknown device in snapshot {}", name) + anyhow::anyhow!("unknown device in snapshot {name}") })?; match dev.migrate() { Migrator::NonMigratable => anyhow::bail!( - "can't restore snapshot with non-migratable device ({})", - name + "can't restore snapshot with non-migratable device ({name})" ), Migrator::Empty => { // There really shouldn't be a payload for this @@ -413,9 +411,7 @@ pub(crate) fn restore( let remain = offer.remaining().count(); if remain > 0 { return Err(anyhow::anyhow!( - "Device {} had {} remaining payload(s)", - name, - remain + "Device {name} had {remain} remaining payload(s)" )); } } diff --git a/phd-tests/framework/src/artifacts/store.rs b/phd-tests/framework/src/artifacts/store.rs index 65def357d..a8c17e745 100644 --- a/phd-tests/framework/src/artifacts/store.rs +++ b/phd-tests/framework/src/artifacts/store.rs @@ -61,7 +61,7 @@ impl StoredArtifact { if let Some(digest) = sha256 { file_hash_equals(&path, digest)?; } else if !path.is_file() { - anyhow::bail!("artifact path {} is not a file", path); + anyhow::bail!("artifact path {path} is not a file"); } // The file is in the right place and has the right hash (if that @@ -98,8 +98,7 @@ impl StoredArtifact { } } else if maybe_path.exists() { anyhow::bail!( - "artifact path {} already exists but isn't a file", - maybe_path + "artifact path {maybe_path} already exists but isn't a file" ); } @@ -152,8 +151,7 @@ impl StoredArtifact { // This artifact is a tarball, and a file must be extracted from it. let filename = untar_path.file_name().ok_or_else(|| { anyhow::anyhow!( - "untar path '{}' has no file name component", - untar_path + "untar path '{untar_path}' has no file name component" ) })?; let extracted_path = path.with_file_name(filename); @@ -358,8 +356,7 @@ impl Store { Ok((path, kind)) } _ => Err(anyhow::anyhow!( - "artifact {} is not a guest OS image", - artifact_name + "artifact {artifact_name} is not a guest OS image" )), } } @@ -375,8 +372,7 @@ impl Store { guard.ensure(&self.local_dir, &self.downloader).await } _ => Err(anyhow::anyhow!( - "artifact {} is not a bootrom", - artifact_name + "artifact {artifact_name} is not a bootrom" )), } } @@ -392,8 +388,7 @@ impl Store { guard.ensure(&self.local_dir, &self.downloader).await } _ => Err(anyhow::anyhow!( - "artifact {} is not a Propolis server", - artifact_name + "artifact {artifact_name} is not a Propolis server" )), } } @@ -416,7 +411,7 @@ impl Store { name: &str, ) -> anyhow::Result<&Mutex> { self.artifacts.get(name).ok_or_else(|| { - anyhow::anyhow!("artifact {} not found in store", name) + anyhow::anyhow!("artifact {name} not found in store") }) } @@ -435,14 +430,12 @@ impl Store { let full_path = cmd.canonicalize_utf8()?; let filename = full_path.file_name().ok_or_else(|| { anyhow::anyhow!( - "local artifact command '{}' contains no file component", - cmd + "local artifact command '{cmd}' contains no file component" ) })?; let dir = full_path.parent().ok_or_else(|| { anyhow::anyhow!( - "canonicalized local artifact path '{}' has no directory component", - full_path + "canonicalized local artifact path '{full_path}' has no directory component" ) })?; diff --git a/phd-tests/framework/src/disk/fat.rs b/phd-tests/framework/src/disk/fat.rs index e845e86e8..5d37b8e1c 100644 --- a/phd-tests/framework/src/disk/fat.rs +++ b/phd-tests/framework/src/disk/fat.rs @@ -213,8 +213,7 @@ impl FatFilesystem { // this by ensuring there's one track containing all the sectors. let sectors_per_track: u16 = sectors.try_into().map_err(|_| { anyhow::anyhow!( - "disk has {} sectors, which is too many for one FAT track", - sectors + "disk has {sectors} sectors, which is too many for one FAT track" ) })?; diff --git a/phd-tests/framework/src/test_vm/mod.rs b/phd-tests/framework/src/test_vm/mod.rs index 5e3a2b4ad..df7eb28c8 100644 --- a/phd-tests/framework/src/test_vm/mod.rs +++ b/phd-tests/framework/src/test_vm/mod.rs @@ -698,9 +698,7 @@ impl TestVm { Ok(()) } else { Err(backoff::Error::transient(anyhow!( - "not in desired state yet: current {:?}, target {:?}", - current, - target + "not in desired state yet: current {current:?}, target {target:?}" ))) } }; @@ -1102,8 +1100,7 @@ async fn try_ensure_vm_destroyed(client: &Client) { match client.instance_get().send().await.map(|r| r.instance.state) { Ok(InstanceState::Destroyed) => Ok(()), Ok(state) => Err(backoff::Error::transient(anyhow::anyhow!( - "instance not destroyed yet (state: {:?})", - state + "instance not destroyed yet (state: {state:?})" ))), Err(error) => { error!( diff --git a/phd-tests/tests/src/boot_order.rs b/phd-tests/tests/src/boot_order.rs index 455ffb0cf..6cb3ef1bb 100644 --- a/phd-tests/tests/src/boot_order.rs +++ b/phd-tests/tests/src/boot_order.rs @@ -196,9 +196,7 @@ async fn unbootable_disk_skipped(ctx: &Framework) { continue; } else { bail!( - "Did not expect to find {:?} yet (test state = {:?})", - load_option, - state + "Did not expect to find {load_option:?} yet (test state = {state:?})" ); } } @@ -208,9 +206,7 @@ async fn unbootable_disk_skipped(ctx: &Framework) { continue; } else { bail!( - "Did not expect to find {:?} (test state = {:?})", - load_option, - state + "Did not expect to find {load_option:?} (test state = {state:?})" ); } } @@ -224,9 +220,7 @@ async fn unbootable_disk_skipped(ctx: &Framework) { ); if !is_ui_app && !is_efi_shell { bail!( - "Did not expect to find {:?} (test state = {:?})", - load_option, - state + "Did not expect to find {load_option:?} (test state = {state:?})" ); } } diff --git a/phd-tests/tests/src/boot_order/efi_utils.rs b/phd-tests/tests/src/boot_order/efi_utils.rs index 0aa9a7f8a..9c88a02fe 100644 --- a/phd-tests/tests/src/boot_order/efi_utils.rs +++ b/phd-tests/tests/src/boot_order/efi_utils.rs @@ -198,9 +198,7 @@ impl DevicePath { } (ty, subtype) => { bail!( - "Device path type/subtype unsupported: ({:#02x}/{:#02x})", - ty, - subtype + "Device path type/subtype unsupported: ({ty:#02x}/{subtype:#02x})" ); } } @@ -238,7 +236,7 @@ impl EfiLoadOption { let path_entry = DevicePath::parse_from(&mut device_path_cursor) .map_err(|e| { - anyhow::anyhow!("unable to parse device path element: {:?}", e) + anyhow::anyhow!("unable to parse device path element: {e:?}") })?; let load_path = match path_entry { acpi_root @ DevicePath::Acpi { .. } => { @@ -248,8 +246,7 @@ impl EfiLoadOption { if !matches!(pci_device, DevicePath::Pci { .. }) { bail!( "expected ACPI Device Path entry to be followed by \ - a PCI Device Path, but was {:?}", - pci_device + a PCI Device Path, but was {pci_device:?}" ); } @@ -261,15 +258,14 @@ impl EfiLoadOption { if !matches!(file, DevicePath::FirmwareFile { .. }) { bail!( "expected Firmware Volume entry to be followed by \ - a Firmware File, but was {:?}", - file + a Firmware File, but was {file:?}" ); } EfiLoadPath::FirmwareFile { volume, file } } other => { - bail!("unexpected root EFI Load Option path item: {:?}", other); + bail!("unexpected root EFI Load Option path item: {other:?}"); } }; @@ -392,7 +388,7 @@ pub(crate) async fn write_efivar( // If something went sideways and the write failed with something like // `invalid argument`... if !res.is_empty() { - bail!("writing efi produced unexpected output: {}", res); + bail!("writing efi produced unexpected output: {res}"); } Ok(()) diff --git a/xtask/src/external.rs b/xtask/src/external.rs new file mode 100644 index 000000000..43c34b8a9 --- /dev/null +++ b/xtask/src/external.rs @@ -0,0 +1,131 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! External xtasks. (extasks?) + +use std::ffi::OsString; +use std::os::unix::process::CommandExt; +use std::process::Command; + +use anyhow::{Context, Result}; +use clap::Parser; + +/// Argument parser for external xtasks. +/// +/// In general we want all developer tasks to be discoverable simply by running +/// `cargo xtask`, but some development tools end up with a particularly +/// large dependency tree. It's not ideal to have to pay the cost of building +/// our release engineering tooling if all the user wants to do is check for +/// workspace dependency issues. +/// +/// `External` provides a pattern for creating xtasks that live in other crates. +/// An external xtask is defined on `crate::Cmds` as a tuple variant containing +/// `External`, which captures all arguments and options (even `--help`) as +/// a `Vec`. The main function then calls `External::exec` with the +/// appropriate bin target name and any additional Cargo arguments. +#[derive(Parser)] +#[clap( + disable_help_flag(true), + disable_help_subcommand(true), + disable_version_flag(true) +)] +pub struct External { + #[clap(trailing_var_arg(true), allow_hyphen_values(true))] + args: Vec, + + // This stores an in-progress Command builder. `cargo_args` appends args + // to it, and `exec` consumes it. Clap does not treat this as a command + // (`skip`), but fills in this field by calling `new_command`. + #[clap(skip = new_command())] + command: Command, +} + +impl External { + pub fn exec_bin( + self, + package: impl AsRef, + bin_target: impl AsRef, + ) -> Result<()> { + self.exec_common(&[ + "--package", + package.as_ref(), + "--bin", + bin_target.as_ref(), + ]) + } + + fn exec_common(mut self, args: &[&str]) -> Result<()> { + let error = self.command.args(args).arg("--").args(self.args).exec(); + Err(error).context("failed to exec `cargo run`") + } +} + +fn new_command() -> Command { + let mut command = cargo_command(CargoLocation::FromEnv); + command.arg("run"); + command +} + +/// Creates and prepares a `std::process::Command` for the `cargo` executable. +pub fn cargo_command(location: CargoLocation) -> Command { + let mut command = location.resolve(); + + for (key, _) in std::env::vars_os() { + let Some(key) = key.to_str() else { continue }; + if SANITIZED_ENV_VARS.matches(key) { + command.env_remove(key); + } + } + + command +} + +/// How to determine the location of the `cargo` executable. +#[derive(Clone, Copy, Debug)] +pub enum CargoLocation { + /// Use the `CARGO` environment variable, and fall back to `"cargo"` if it + /// is not set. + FromEnv, +} + +impl CargoLocation { + fn resolve(self) -> Command { + match self { + CargoLocation::FromEnv => { + let cargo = std::env::var_os("CARGO") + .unwrap_or_else(|| OsString::from("cargo")); + Command::new(&cargo) + } + } + } +} + +#[derive(Debug)] +struct SanitizedEnvVars { + // At the moment we only ban some prefixes, but we may also want to ban env + // vars by exact name in the future. + prefixes: &'static [&'static str], +} + +impl SanitizedEnvVars { + const fn new() -> Self { + // Remove many of the environment variables set in + // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts. + // This is done to avoid recompilation with crates like ring between + // `cargo clippy` and `cargo xtask clippy`. (This is really a bug in + // both ring's build script and in Cargo.) + // + // The current list is informed by looking at ring's build script, so + // it's not guaranteed to be exhaustive and it may need to grow over + // time. + let prefixes = &["CARGO_PKG_", "CARGO_MANIFEST_", "CARGO_CFG_"]; + Self { prefixes } + } + + fn matches(&self, key: &str) -> bool { + self.prefixes.iter().any(|prefix| key.starts_with(prefix)) + } +} + +static SANITIZED_ENV_VARS: SanitizedEnvVars = SanitizedEnvVars::new(); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index ed652a868..d2a388a96 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -5,6 +5,7 @@ use anyhow::Result; use clap::{Parser, Subcommand}; +mod external; mod task_clippy; mod task_fmt; mod task_license; @@ -37,6 +38,8 @@ enum Cmds { Fmt, /// (Crudely) Check for appropriate license headers License, + /// Manage OpenAPI documents + Openapi(external::External), /// Preform pre-push checks (clippy, license, fmt, etc) Prepush { /// Suppress non-essential output @@ -64,6 +67,8 @@ fn main() -> Result<()> { println!("License checks pass"); Ok(()) } + Cmds::Openapi(external) => external + .exec_bin("propolis-dropshot-apis", "propolis-dropshot-apis"), Cmds::Phd { cmd } => cmd.run(), Cmds::Prepush { quiet } => { task_prepush::cmd_prepush(quiet)?;