diff --git a/Cargo.lock b/Cargo.lock index 286a308..0ddbede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -95,7 +143,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -106,7 +154,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -522,9 +570,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64-simd" @@ -722,6 +770,27 @@ dependencies = [ "usdt", ] +[[package]] +name = "buildomat-factory-oxide" +version = "0.0.0" +dependencies = [ + "anyhow", + "base64 0.13.1", + "buildomat-client", + "buildomat-common", + "buildomat-types", + "chrono", + "getopts", + "oxide", + "rusty_ulid", + "serde", + "slog", + "slog-term", + "tokio", + "toml 0.5.11", + "usdt", +] + [[package]] name = "buildomat-factory-propolis" version = "0.0.0" @@ -939,9 +1008,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", @@ -949,9 +1018,55 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.0", +] + +[[package]] +name = "clap" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.0", ] +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1079,7 +1194,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 1.0.109", ] @@ -1093,7 +1208,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 1.0.109", ] @@ -1154,6 +1269,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -1164,6 +1288,17 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1198,7 +1333,7 @@ source = "git+https://github.com/oxidecomputer/dropshot?branch=x-forwarded-for#3 dependencies = [ "async-stream", "async-trait", - "base64 0.21.5", + "base64 0.21.7", "bytes", "camino", "chrono", @@ -1230,8 +1365,8 @@ dependencies = [ "slog-term", "tokio", "tokio-rustls 0.24.1", - "toml 0.8.8", - "uuid 1.6.1", + "toml 0.8.10", + "uuid 1.7.0", "version_check", "waitgroup", ] @@ -1245,7 +1380,7 @@ dependencies = [ "quote", "serde", "serde_tokenstream 0.2.0", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1355,9 +1490,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1370,9 +1505,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1380,15 +1515,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1397,38 +1532,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1781,7 +1916,7 @@ checksum = "ce243b1bfa62ffc028f1cc3b6034ec63d649f3031bc8a4fbbb004e1ac17d1f68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2174,7 +2309,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2211,6 +2346,29 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" +[[package]] +name = "oxide" +version = "0.3.0+0.0.6" +source = "git+https://github.com/oxidecomputer/oxide.rs.git#1eb2e313ebaf68689a7abdc3382c59c85355c304" +dependencies = [ + "base64 0.21.7", + "chrono", + "clap", + "dirs", + "futures", + "progenitor-client 0.5.0", + "rand", + "regress 0.8.0", + "reqwest", + "schemars", + "serde", + "serde_json", + "thiserror", + "toml 0.8.10", + "toml_edit", + "uuid 1.7.0", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -2268,7 +2426,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "serde", ] @@ -2309,7 +2467,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2340,7 +2498,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2399,9 +2557,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -2411,7 +2569,7 @@ name = "progenitor" version = "0.4.0" source = "git+https://github.com/oxidecomputer/progenitor#8d1b8f0f8e0cf33f7f696cd1b8747e9ff2986d1c" dependencies = [ - "progenitor-client", + "progenitor-client 0.4.0", "progenitor-impl", "progenitor-macro", "serde_json", @@ -2431,6 +2589,21 @@ dependencies = [ "serde_urlencoded", ] +[[package]] +name = "progenitor-client" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24dd3cac4cd81ac77f90e5a8dcb8c9311c7500c6ce8c65c98ce61a5894672e9" +dependencies = [ + "bytes", + "futures-core", + "percent-encoding", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", +] + [[package]] name = "progenitor-impl" version = "0.4.0" @@ -2447,7 +2620,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "syn 2.0.39", + "syn 2.0.52", "thiserror", "typify", "unicode-ident", @@ -2467,14 +2640,14 @@ dependencies = [ "serde_json", "serde_tokenstream 0.2.0", "serde_yaml", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2568,13 +2741,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "regress" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5f39ba4513916c1b2657b72af6ec671f091cd637992f58d0ede5cae4e5dea0" +dependencies = [ + "hashbrown 0.14.3", + "memchr", +] + [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -2593,9 +2776,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -2644,7 +2829,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bitflags 2.4.1", "serde", "serde_derive", @@ -2817,7 +3002,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] [[package]] @@ -2877,7 +3062,7 @@ dependencies = [ "serde_json", "url", "uuid 0.8.2", - "uuid 1.6.1", + "uuid 1.7.0", ] [[package]] @@ -2940,7 +3125,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "thiserror", ] @@ -2985,22 +3170,22 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3016,9 +3201,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -3043,14 +3228,14 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -3075,7 +3260,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3314,6 +3499,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "strum" version = "0.25.0" @@ -3333,7 +3524,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3355,15 +3546,21 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -3444,7 +3641,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3561,7 +3758,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3645,9 +3842,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", @@ -3666,9 +3863,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" dependencies = [ "indexmap", "serde", @@ -3725,7 +3922,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3767,10 +3964,10 @@ dependencies = [ "log", "proc-macro2", "quote", - "regress", + "regress 0.7.1", "schemars", "serde_json", - "syn 2.0.39", + "syn 2.0.52", "thiserror", "unicode-ident", ] @@ -3786,7 +3983,7 @@ dependencies = [ "serde", "serde_json", "serde_tokenstream 0.2.0", - "syn 2.0.39", + "syn 2.0.52", "typify-impl", ] @@ -3941,6 +4138,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "0.8.2" @@ -3949,9 +4152,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "getrandom", "serde", @@ -4020,7 +4223,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "wasm-bindgen-shared", ] @@ -4054,7 +4257,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4067,9 +4270,9 @@ checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-streams" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" dependencies = [ "futures-util", "js-sys", @@ -4263,9 +4466,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.25" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e87b8dfbe3baffbe687eef2e164e32286eff31a5ee16463ce03d991643ec94" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" dependencies = [ "memchr", ] @@ -4348,7 +4551,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -4359,7 +4562,7 @@ checksum = "86cd5ca076997b97ef09d3ad65efe811fa68c9e874cb636ccb211223a813b0c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 142fcaf..eddfc80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "factory/aws", "factory/lab", "factory/propolis", + "factory/oxide", "github/common", "github/database", "github/dbtool", @@ -47,6 +48,7 @@ jmclib = { git = "https://github.com/jclulow/rust-jmclib", features = ["sqlite"] libc = "0.2.113" new_mime_guess = "4" octorust = { git = "https://github.com/oxidecomputer/third-party-api-clients", branch = "jclulow" } +oxide = { git = "https://github.com/oxidecomputer/oxide.rs.git", version = "0.3.0" } pem = "2" percent-encoding = "2.1" progenitor = { git = "https://github.com/oxidecomputer/progenitor" } diff --git a/factory/oxide/Cargo.toml b/factory/oxide/Cargo.toml new file mode 100644 index 0000000..a5e6ddf --- /dev/null +++ b/factory/oxide/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "buildomat-factory-oxide" +version = "0.0.0" +edition = "2021" +license = "MPL-2.0" + +[dependencies] +buildomat-client = { path = "../../client" } +buildomat-common = { path = "../../common" } +buildomat-types = { path = "../../types" } + +anyhow = { workspace = true } +base64 = { workspace = true } +chrono = { workspace = true } +getopts = { workspace = true } +serde = { workspace = true } +rusty_ulid = { workspace = true } +slog = { workspace = true } +slog-term = { workspace = true } +tokio = { workspace = true } +toml = { workspace = true } +usdt = { workspace = true } +oxide = { workspace = true } diff --git a/factory/oxide/scripts/user_data.sh b/factory/oxide/scripts/user_data.sh new file mode 100644 index 0000000..2b191b9 --- /dev/null +++ b/factory/oxide/scripts/user_data.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -o errexit +set -o pipefail + +function os_release { + if [[ ! -f /etc/os-release ]]; then + printf '\n' + else + local r=$( ( . /etc/os-release ; eval "echo \$$1" ) ) + printf '%s\n' "${r// /+}" + fi +} + +# +# Give the server some hints as to what OS we're running so that it can give us +# the most appropriate agent binary: +# +q="?kernel=$(uname -s)" +q+="&proc=$(uname -p)" +q+="&mach=$(uname -m)" +q+="&plat=$(uname -i)" +q+="&id=$(os_release ID)" +q+="&id_like=$(os_release ID_LIKE)" +q+="&version_id=$(os_release VERSION_ID)" + +while :; do + rm -f /var/tmp/agent + if ! curl -sSf -o /var/tmp/agent '%URL%/file/agent'"$q"; then + sleep 1 + continue + fi + chmod +rx /var/tmp/agent + if ! /var/tmp/agent install '%URL%' '%STRAP%'; then + sleep 1 + continue + fi + break +done + +exit 0 diff --git a/factory/oxide/src/config.rs b/factory/oxide/src/config.rs new file mode 100644 index 0000000..8ba483e --- /dev/null +++ b/factory/oxide/src/config.rs @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Oxide Computer Company + */ + +use std::collections::HashMap; + +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub(crate) struct ConfigFile { + pub oxide: ConfigFileOxide, + pub general: ConfigFileGeneral, + pub factory: ConfigFileFactory, + pub target: HashMap, +} + +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub(crate) struct ConfigFileGeneral { + pub baseurl: String, +} + +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub(crate) struct ConfigFileFactory { + pub token: String, +} + +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub(crate) struct ConfigFileOxideTarget { + /// Image name to use as a base + pub image: String, + /// Size of the disk to create + pub disk_size: String, + /// Number of cpus to use + pub cpu_cnt: u16, + /// Memory for instance + pub memory: String, +} + +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub(crate) struct ConfigFileOxide { + /// Rack running Oxide API + pub rack_host: String, + /// access token + pub rack_token: String, + /// Project to create instances in + pub project: String, + /// total of number of workers to create + pub limit_total: usize +} diff --git a/factory/oxide/src/main.rs b/factory/oxide/src/main.rs new file mode 100644 index 0000000..835dd83 --- /dev/null +++ b/factory/oxide/src/main.rs @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Oxide Computer Company + */ + +use std::sync::Arc; + +use anyhow::{bail, Context, Result}; +use buildomat_common::*; +use getopts::Options; +use slog::Logger; + +mod config; +mod oxide; +use config::ConfigFile; + +struct Central { + log: Logger, + config: config::ConfigFile, + client: buildomat_client::Client, + targets: Vec, +} + +#[tokio::main] +async fn main() -> Result<()> { + usdt::register_probes().unwrap(); + + let mut opts = Options::new(); + + opts.optopt("f", "", "configuration file", "CONFIG"); + + let p = match opts.parse(std::env::args().skip(1)) { + Ok(p) => p, + Err(e) => { + eprintln!("ERROR: usage: {}", e); + eprintln!(" {}", opts.usage("usage")); + std::process::exit(1); + } + }; + + let log = make_log("factory-oxide"); + let config: ConfigFile = if let Some(f) = p.opt_str("f").as_deref() { + read_toml(f)? + } else { + bail!("must specify configuration file (-f)"); + }; + let targets = config.target.keys().map(String::to_string).collect(); + let client = buildomat_client::ClientBuilder::new(&config.general.baseurl) + .bearer_token(&config.factory.token) + .build()?; + + let c = Arc::new(Central { log, config, client, targets }); + + let t_aws = tokio::task::spawn(async move { + oxide::oxide_worker(c).await.context("Oxide worker task failure") + }); + + tokio::select! { + _ = t_aws => bail!("Oxide worker task stopped early"), + } +} diff --git a/factory/oxide/src/oxide.rs b/factory/oxide/src/oxide.rs new file mode 100644 index 0000000..948f9f7 --- /dev/null +++ b/factory/oxide/src/oxide.rs @@ -0,0 +1,543 @@ +/* + * Copyright 2024 Oxide Computer Company + */ + +use std::collections::HashMap; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::{Result}; +use buildomat_client::types::*; +use buildomat_types::metadata; +use chrono::prelude::*; +use rusty_ulid::Ulid; +use slog::{debug, error, info, o, warn, Logger}; + +use oxide::context::Context; + +use oxide::ClientImagesExt; +use oxide::ClientInstancesExt; +use oxide::ClientDisksExt; +use oxide::types::InstanceDiskAttachment; +use oxide::types::DiskSource; +use oxide::types::ByteCount; +use oxide::types::ExternalIpCreate; +use oxide::types::ExternalIp; + +use super::{config::ConfigFileOxideTarget, Central, ConfigFile}; + +#[derive(Debug)] +#[allow(dead_code)] +struct Instance { + id: String, + state: String, + ip: Option, + worker_id: Option, + lease_id: Option, + launch_time: Option>, +} + +impl Instance { + fn age_secs(&self) -> u64 { + self.launch_time + .map(|dt| { + Utc::now() + .signed_duration_since(dt) + .to_std() + .unwrap_or_else(|_| Duration::from_secs(0)) + .as_secs() + }) + .unwrap_or(0) + } +} + +async fn destroy_instance( + log: &Logger, + context: &Context, + id: &str, +) -> Result<()> { + info!(log, "destroying instance {}...", id); + + let disks = context.client()?.instance_disk_list().instance(id).send().await?; + let disk_id = disks.items[0].id; + + context.client()?.instance_stop().instance(id).send().await?; + loop { + match context.client()?.instance_delete().instance(id).send().await { + Ok(_) => break, + Err(_) => continue, + } + } + + context.client()?.disk_delete().disk(disk_id).send().await?; + + Ok(()) +} + +async fn create_instance( + log: &Logger, + context: &Context, + config: &ConfigFile, + target: &ConfigFileOxideTarget, + worker: &FactoryWorker, + lease_id: &str, +) -> Result { + let id = worker.id.to_string(); + + let script = include_str!("../scripts/user_data.sh") + .replace("%URL%", &config.general.baseurl) + .replace("%STRAP%", &worker.bootstrap); + + let script = base64::encode(&script); + + let img = match context + .client()? + .image_view() + .image(target.image.clone()) + .send() + .await + { + Ok(i) => i, + Err(_) => { + context + .client()? + .image_view() + .image(target.image.clone()) + .project(config.oxide.project.clone()) + .send() + .await? + } + }; + + info!(log, "creating an instance (worker {})...", id); + + // Makeshift Tags + let desc = format!("buildomat:worker_id={},lease_id={}", id, lease_id); + + let res = context + .client()? + .instance_create() + .project(&config.oxide.project) + .body_map(|body| { + body.name(format!("oxide-worker-{}", id.to_lowercase())) + .description(desc) + .disks(vec![InstanceDiskAttachment::Create { + description: format!("Buildomat Worker {}", id), + disk_source: DiskSource::Image { image_id: img.id }, + name: format!("oxide-worker-{}-disk", id.to_lowercase()).try_into().unwrap(), + size: ByteCount(target.disk_size.parse().unwrap()), + }]) + .external_ips(vec![ExternalIpCreate::Ephemeral { pool: None }]) + .hostname(format!("oxide-worker-{}", id)) + .memory(ByteCount(target.memory.parse().unwrap())) + .ncpus(target.cpu_cnt) + .start(true) + .user_data(script) + }) + .send() + .await?; + + Ok(res.id.into()) +} + +async fn instances( + _log: &Logger, + context: &Context, + config: &ConfigFile, +) -> Result> { + let res = context + .client()? + .instance_list() + .project(config.oxide.project.clone()) + .send() + .await?; + + let mut out = HashMap::new(); + for i in res.items.iter() { + let desc = match i.description.strip_prefix("buildomat:") { + Some(d) => d, + None => continue, + }; + + let id = i.id.to_string(); + let state = i.run_state.to_string(); + + let launch_time = Some(i + .time_created); + + let desc : Vec<_> = desc.split(",").collect(); + + let worker_id = desc[0] + .strip_prefix("worker_id=") + .map(|s| Ulid::from_str(&s).ok()) + .unwrap_or_default(); + let lease_id = desc[1] + .strip_prefix("lease_id=") + .map(|s| Ulid::from_str(&s).ok()) + .unwrap_or_default(); + + let ip = context.client()? + .instance_external_ip_list() + .instance(id.clone()) + .send() + .await?; + + let ip = Some(match ip.items[0] { + ExternalIp::Ephemeral { ip } => ip.to_string(), + ExternalIp::Floating { ip, .. } => ip.to_string(), + }); + + out.insert( + id.to_string(), + Instance { id, state, worker_id, lease_id, ip, launch_time }, + ); + } + + Ok(out) +} + +async fn oxide_worker_one( + log: &Logger, + c: &Central, + context: &Context, + config: &ConfigFile, +) -> Result<()> { + /* + * Get a complete list of instances that have the buildomat tag. + */ + debug!(log, "scanning for instances..."); + let insts = instances(log, context, config).await?; + + info!(log, "found instances: {:?}", insts); + + /* + * For each instance, check to see if its worker record still exists. If it + * does not, delete the instance now. + */ + for i in insts.values() { + let destroy = if let Some(id) = &i.worker_id { + /* + * Request information about this worker from the core server. + */ + let w = c + .client + .factory_worker_get() + .worker(id.to_string()) + .send() + .await? + .into_inner(); + match w.worker { + Some(w) => { + debug!(log, "instance {} is for worker {}", i.id, w.id); + + if let Some(expected) = w.private.as_deref() { + if expected != i.id { + error!( + log, + "instance {} for worker {} does not \ + match expected instance ID {} from DB", + i.id, + w.id, + expected + ); + continue; + } + } else { + /* + * This can occur if we crash after creating the + * instance but before associating it. + */ + info!( + log, + "associating instance {} with worker {}", + i.id, + w.id + ); + c.client + .factory_worker_associate() + .worker(&w.id) + .body_map(|body| { + body.private(&i.id).metadata(Some( + metadata::FactoryMetadata::V1( + metadata::FactoryMetadataV1 { + addresses: Default::default(), + root_password_hash: None + }, + ), + )) + }) + .send() + .await?; + } + + if w.recycle { + /* + * If the worker has been deleted through the + * administrative API then we need to tear it down + * straight away. + */ + warn!(log, "worker {} recycled, destroying it", w.id); + true + } else if i.state == "stopped" { + /* + * Terminate any instances which stop themselves. + */ + warn!(log, "instance {} stopped, destroying!", i.id); + true + } else if !w.online && i.age_secs() > 5 * 60 { + /* + * We have seen a recent spate of AWS instances that + * hang in early boot. They get destroyed eventually, + * but we should terminate them more promptly than that + * to avoid being billed for AWS bullshit. + */ + error!( + log, + "instance {} hung; destroying after {} seconds", + i.id, + i.age_secs(), + ); + true + } else { + /* + * Otherwise, this is a regular active worker that does + * not need to be destroyed. + */ + if !w.online { + if let Some(lid) = &i.lease_id { + /* + * If the worker has not yet bootstrapped, and + * we have a lease on file for this instance, + * try to renew it with the core server. This + * should prevent duplicate instance creation + * when creation or bootstrap is taking longer + * than expected. + */ + info!( + log, + "renew lease {} for worker {}", lid, w.id + ); + c.client + .factory_lease_renew() + .job(lid.to_string()) + .send() + .await?; + } + } + false + } + } + None => { + warn!( + log, + "instance {} is worker {} which no longer \ + exists", + i.id, + id, + ); + true + } + } + } else { + /* + * This should not happen, and likely represents a serious problem: + * either this software is not correctly tracking instances, or the + * operator is creating instances in the AWS account that was set + * aside for unprivileged build VMs. + */ + warn!(log, "instance {} has no worker id; ignoring...", i.id); + continue; + }; + + if destroy + && (&i.state == "running" + || &i.state == "stopped" + || &i.state == "pending") + { + destroy_instance(log, context, &i.id).await?; + } + } + + /* + * At this point we have examined all of the instances which exist. If + * there are any worker records left that do not have an associated + * instance, they must be scrubbed from the database as detritus from prior + * failed runs. + */ + for w in c.client.factory_workers().send().await?.into_inner() { + let rm = if let Some(instance_id) = w.private.as_deref() { + /* + * There is a record of a particular instance ID for this worker. + * Check to see if that instance exists. + */ + if let Some(i) = insts.get(&instance_id.to_string()) { + if i.state == "terminated" { + /* + * The instance exists, but is terminated. Delete the + * worker. + */ + info!( + log, + "deleting worker {} for terminated instance {}", + w.id, + instance_id + ); + true + } else { + /* + * The instance exists but is not yet terminated. + */ + false + } + } else { + /* + * The instance does not exist. Make this a warning unless we + * were already instructed by the core server to recycle the + * worker. + */ + if w.recycle { + info!( + log, + "deleting recycled worker {} with \ + missing instance {}", + w.id, + instance_id + ); + } else { + warn!( + log, + "clearing worker {} with missing instance {}", + w.id, + instance_id + ); + } + true + } + } else { + /* + * The worker record was never associated with an instance. This + * generally should not happen -- we would have associated the + * worker with an instance that was created for it if we found one + * earlier. + */ + warn!(log, "clearing old worker {} with no instance", w.id); + true + }; + + if rm { + c.client.factory_worker_destroy().worker(&w.id).send().await?; + } + } + + /* + * Count the number of instances we can see in AWS that are not yet + * completely destroyed. + */ + let c_insts = insts.values().filter(|i| &i.state != "terminated").count(); + + /* + * Calculate the total number of workers we are willing to create if + * required. The hard limit on total instances includes all instances + * regardless of where they are in the worker lifecycle. + */ + let freeslots = config.oxide.limit_total.saturating_sub(c_insts); + + info!(log, "worker stats"; + "instances" => c_insts, + "freeslots" => freeslots, + ); + + let mut created = 0; + while created < freeslots { + /* + * Check to see if the server requires any new workers. + */ + let res = c + .client + .factory_lease() + .body_map(|body| body.supported_targets(c.targets.clone())) + .send() + .await? + .into_inner(); + + let lease = if let Some(lease) = res.lease { + lease + } else { + break; + }; + + /* + * Locate target-specific configuration. + */ + let t = if let Some(t) = c.config.target.get(&lease.target) { + t + } else { + error!(log, "server wants target we do not support: {:?}", lease); + break; + }; + + let w = c + .client + .factory_worker_create() + .body_map(|body| body.target(&lease.target)) + .send() + .await?; + + let instance_id = + create_instance(log, context, config, t, &w, &lease.job).await?; + created += 1; + info!(log, "created instance: {}", instance_id); + + /* + * Record the instance ID against the worker for which it was created: + */ + c.client + .factory_worker_associate() + .worker(&w.id) + .body_map(|body| { + body.private(&instance_id).metadata(Some( + metadata::FactoryMetadata::V1( + metadata::FactoryMetadataV1 { + addresses: Default::default(), + root_password_hash: None + }, + ), + )) + }) + .send() + .await?; + } + + if freeslots == 0 { + /* + * If we are not going to check for workers, we should explicitly ping + * the server so it knows we are online: + */ + c.client.factory_ping().send().await?; + } + + info!(log, "worker pass complete"); + Ok(()) +} + +pub(crate) async fn oxide_worker(c: Arc) -> Result<()> { + let log = c.log.new(o!("component" => "worker")); + + std::env::set_var("OXIDE_HOST", c.config.oxide.rack_host.clone()); + std::env::set_var("OXIDE_TOKEN", c.config.oxide.rack_token.clone()); + let oxide_config = oxide::config::Config::default(); + let context = oxide::context::Context::new(oxide_config)?; + + let delay = Duration::from_secs(7); + + info!(log, "start Oxide worker task"); + + loop { + if let Err(e) = oxide_worker_one(&log, &c, &context, &c.config).await { + error!(log, "worker error: {:?}", e); + } + + tokio::time::sleep(delay).await; + } +}