diff --git a/Cargo.lock b/Cargo.lock index 850260587eb..c1e49de097e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1105,7 +1105,7 @@ dependencies = [ "semver 1.0.25", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -2757,7 +2757,7 @@ dependencies = [ "slog-bunyan", "slog-json", "slog-term", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tokio-rustls 0.25.0", "toml 0.8.20", @@ -6184,7 +6184,6 @@ dependencies = [ "oxnet", "proptest", "rand 0.8.5", - "semver 1.0.25", "sled-agent-client", "slog", "slog-error-chain", @@ -6192,6 +6191,7 @@ dependencies = [ "strum", "test-strategy", "thiserror 1.0.69", + "tufaceous-artifact", "typed-rng", "uuid", ] @@ -6428,6 +6428,7 @@ dependencies = [ "strum", "test-strategy", "thiserror 1.0.69", + "tufaceous-artifact", "update-engine", "uuid", ] @@ -7553,6 +7554,7 @@ dependencies = [ "crossterm", "crypto-common", "curve25519-dalek", + "daft", "digest", "dof", "dropshot 0.12.0", @@ -7879,7 +7881,7 @@ dependencies = [ "oxide-vpc", "postcard", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -9223,7 +9225,7 @@ dependencies = [ "serde", "serde_json", "syn 2.0.98", - "thiserror 2.0.11", + "thiserror 2.0.12", "typify 0.3.0", "unicode-ident", ] @@ -9737,6 +9739,7 @@ dependencies = [ "swrite", "tabled 0.15.0", "tokio", + "tufaceous-artifact", "uuid", ] @@ -10464,7 +10467,7 @@ dependencies = [ "quick-xml", "rand 0.8.5", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "url", "uuid", ] @@ -11977,11 +11980,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -11997,9 +12000,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -12519,7 +12522,7 @@ dependencies = [ "slog-async", "slog-term", "tabled 0.18.0", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "transceiver-decode", "transceiver-messages", @@ -12535,7 +12538,7 @@ dependencies = [ "schemars", "serde", "static_assertions", - "thiserror 2.0.11", + "thiserror 2.0.12", "transceiver-messages", ] @@ -12549,7 +12552,7 @@ dependencies = [ "hubpack", "schemars", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -12576,7 +12579,7 @@ dependencies = [ [[package]] name = "tufaceous" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/tufaceous?branch=main#d2387032714f66e31b7e255d89f9bf6eb9b3a010" +source = "git+https://github.com/oxidecomputer/tufaceous?branch=main#69e2896b5905aba61445e519aaa40f02d59638b2" dependencies = [ "anyhow", "camino", @@ -12597,8 +12600,9 @@ dependencies = [ [[package]] name = "tufaceous-artifact" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/tufaceous?branch=main#d2387032714f66e31b7e255d89f9bf6eb9b3a010" +source = "git+https://github.com/oxidecomputer/tufaceous?branch=main#69e2896b5905aba61445e519aaa40f02d59638b2" dependencies = [ + "daft", "parse-display", "proptest", "schemars", @@ -12607,12 +12611,13 @@ dependencies = [ "serde_human_bytes", "strum", "test-strategy", + "thiserror 2.0.12", ] [[package]] name = "tufaceous-brand-metadata" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/tufaceous?branch=main#d2387032714f66e31b7e255d89f9bf6eb9b3a010" +source = "git+https://github.com/oxidecomputer/tufaceous?branch=main#69e2896b5905aba61445e519aaa40f02d59638b2" dependencies = [ "semver 1.0.25", "serde", @@ -12623,7 +12628,7 @@ dependencies = [ [[package]] name = "tufaceous-lib" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/tufaceous?branch=main#d2387032714f66e31b7e255d89f9bf6eb9b3a010" +source = "git+https://github.com/oxidecomputer/tufaceous?branch=main#69e2896b5905aba61445e519aaa40f02d59638b2" dependencies = [ "anyhow", "async-trait", @@ -12795,7 +12800,7 @@ dependencies = [ "serde", "serde_json", "syn 2.0.98", - "thiserror 2.0.11", + "thiserror 2.0.12", "unicode-ident", ] diff --git a/clients/wicketd-client/src/lib.rs b/clients/wicketd-client/src/lib.rs index bef57a982ee..20bfaa5d4ea 100644 --- a/clients/wicketd-client/src/lib.rs +++ b/clients/wicketd-client/src/lib.rs @@ -33,6 +33,7 @@ progenitor::generate_api!( replace = { AbortUpdateOptions = wicket_common::rack_update::AbortUpdateOptions, AllowedSourceIps = omicron_common::api::internal::shared::AllowedSourceIps, + ArtifactId = omicron_common::update::ArtifactId, Baseboard = sled_hardware_types::Baseboard, BgpAuthKey = wicket_common::rack_setup::BgpAuthKey, BgpAuthKeyId = wicket_common::rack_setup::BgpAuthKeyId, diff --git a/common/src/update.rs b/common/src/update.rs index c4d24537614..14f0c1adba9 100644 --- a/common/src/update.rs +++ b/common/src/update.rs @@ -10,9 +10,8 @@ use schemars::{ gen::SchemaGenerator, schema::{Schema, SchemaObject}, }; -use semver::Version; use serde::{Deserialize, Serialize}; -use tufaceous_artifact::{Artifact, ArtifactKind}; +use tufaceous_artifact::{Artifact, ArtifactKind, ArtifactVersion}; /// An identifier for an artifact. /// @@ -35,7 +34,7 @@ pub struct ArtifactId { pub name: String, /// The artifact's version. - pub version: Version, + pub version: ArtifactVersion, /// The kind of artifact this is. pub kind: ArtifactKind, diff --git a/dev-tools/reconfigurator-cli/Cargo.toml b/dev-tools/reconfigurator-cli/Cargo.toml index f6694ea16fb..16365bf1a27 100644 --- a/dev-tools/reconfigurator-cli/Cargo.toml +++ b/dev-tools/reconfigurator-cli/Cargo.toml @@ -39,6 +39,7 @@ slog-term.workspace = true slog.workspace = true swrite.workspace = true tabled.workspace = true +tufaceous-artifact.workspace = true uuid.workspace = true omicron-workspace-hack.workspace = true diff --git a/dev-tools/reconfigurator-cli/src/main.rs b/dev-tools/reconfigurator-cli/src/main.rs index dacef1ceb94..8ff7dd58015 100644 --- a/dev-tools/reconfigurator-cli/src/main.rs +++ b/dev-tools/reconfigurator-cli/src/main.rs @@ -52,6 +52,7 @@ use std::io::BufRead; use std::io::IsTerminal; use swrite::{SWrite, swriteln}; use tabled::Tabled; +use tufaceous_artifact::ArtifactVersion; mod log_capture; @@ -463,7 +464,7 @@ enum ImageSourceArgs { /// the zone image comes from the `install` dataset InstallDataset, /// the zone image comes from a specific TUF repo artifact - Artifact { version: semver::Version, hash: ArtifactHash }, + Artifact { version: ArtifactVersion, hash: ArtifactHash }, } impl From for BlueprintZoneImageSource { diff --git a/dev-tools/reconfigurator-cli/tests/input/cmds-set-zone-images.txt b/dev-tools/reconfigurator-cli/tests/input/cmds-set-zone-images.txt index 35a1e561a18..1e3c6025c2a 100644 --- a/dev-tools/reconfigurator-cli/tests/input/cmds-set-zone-images.txt +++ b/dev-tools/reconfigurator-cli/tests/input/cmds-set-zone-images.txt @@ -2,23 +2,26 @@ load-example --nsleds 1 --ndisks-per-sled 3 blueprint-show 971eeb12-1830-4fa0-a699-98ea0164505c -# Set a zone's image source to a specific artifact. +# Set a couple zones' image sources to specific artifacts. blueprint-edit 971eeb12-1830-4fa0-a699-98ea0164505c set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact 1.2.3 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -blueprint-show 9766ca20-38d4-4380-b005-e7c43c797e7c -blueprint-diff 971eeb12-1830-4fa0-a699-98ea0164505c 9766ca20-38d4-4380-b005-e7c43c797e7c +blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 72d12821-646d-4296-a081-ef5fb3a0ca24 artifact valid-non-semver 7b4281a6bd5946bb96b332c62afe289bef275e58b9ba031085f8827655f434b3 -# Set the zone's image source back to the install dataset. -blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 install-dataset blueprint-show f714e6ea-e85a-4d7d-93c2-a018744fe176 -blueprint-diff 9766ca20-38d4-4380-b005-e7c43c797e7c f714e6ea-e85a-4d7d-93c2-a018744fe176 +blueprint-diff 971eeb12-1830-4fa0-a699-98ea0164505c f714e6ea-e85a-4d7d-93c2-a018744fe176 + +# Set these zones' image sources back to the install dataset. +blueprint-edit f714e6ea-e85a-4d7d-93c2-a018744fe176 set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 install-dataset +blueprint-edit bb128f06-a2e1-44c1-8874-4f789d0ff896 set-zone-image 72d12821-646d-4296-a081-ef5fb3a0ca24 install-dataset +blueprint-show d9c572a1-a68c-4945-b1ec-5389bd588fe9 +blueprint-diff f714e6ea-e85a-4d7d-93c2-a018744fe176 d9c572a1-a68c-4945-b1ec-5389bd588fe9 # test help output -blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c help -blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image help +blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 help +blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 set-zone-image help # test error case: no such zone id -blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 126f8db9-b966-45ec-bf89-fb7b41a04cc9 install-dataset -# test error case: bad semver -blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact one e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 set-zone-image 126f8db9-b966-45ec-bf89-fb7b41a04cc9 install-dataset +# test error case: invalid version identifier +blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact 🫡 5d835e8666bd5cf65be66efbca6847b24e41fd85993448a211ad7be5fea559ab # test error case: bad hash -blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact 1.2.3 aaaa +blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact 1.2.3 aaaa diff --git a/dev-tools/reconfigurator-cli/tests/output/cmd-set-zone-images-stderr b/dev-tools/reconfigurator-cli/tests/output/cmd-set-zone-images-stderr index 6c98cf4b203..8aa21773b68 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmd-set-zone-images-stderr +++ b/dev-tools/reconfigurator-cli/tests/output/cmd-set-zone-images-stderr @@ -1,4 +1,4 @@ -error: invalid value 'one' for '': unexpected character 'o' while parsing major version number +error: invalid value '🫡' for '': version contains invalid byte `\xf0` (allowed: ^[a-zA-Z0-9._+-]{1,63}$) For more information, try '--help'. error: invalid value 'aaaa' for '': Invalid string length diff --git a/dev-tools/reconfigurator-cli/tests/output/cmd-set-zone-images-stdout b/dev-tools/reconfigurator-cli/tests/output/cmd-set-zone-images-stdout index bade5023dbf..92cdc66465f 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmd-set-zone-images-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmd-set-zone-images-stdout @@ -98,17 +98,23 @@ parent: 1b013011-2062-4b48-b544-a32b23bce83a > -> # Set a zone's image source to a specific artifact. +> # Set a couple zones' image sources to specific artifacts. > blueprint-edit 971eeb12-1830-4fa0-a699-98ea0164505c set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact 1.2.3 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 blueprint 9766ca20-38d4-4380-b005-e7c43c797e7c created from blueprint 971eeb12-1830-4fa0-a699-98ea0164505c: set sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 zone 026f8db9-b966-45ec-bf89-fb7b41a04cc9 image source to artifact: version 1.2.3 warn: no validation is done on the requested image source -> blueprint-show 9766ca20-38d4-4380-b005-e7c43c797e7c -blueprint 9766ca20-38d4-4380-b005-e7c43c797e7c -parent: 971eeb12-1830-4fa0-a699-98ea0164505c +> blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 72d12821-646d-4296-a081-ef5fb3a0ca24 artifact valid-non-semver 7b4281a6bd5946bb96b332c62afe289bef275e58b9ba031085f8827655f434b3 +blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 created from blueprint 9766ca20-38d4-4380-b005-e7c43c797e7c: set sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 zone 72d12821-646d-4296-a081-ef5fb3a0ca24 image source to artifact: version valid-non-semver +warn: no validation is done on the requested image source + +> + +> blueprint-show f714e6ea-e85a-4d7d-93c2-a018744fe176 +blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 +parent: 9766ca20-38d4-4380-b005-e7c43c797e7c - sled: 868d5b02-7792-4fc0-b6a9-654afcae9ea0 (active, config generation 3) + sled: 868d5b02-7792-4fc0-b6a9-654afcae9ea0 (active, config generation 4) physical disks: ------------------------------------------------------------------------------------ @@ -159,26 +165,26 @@ parent: 971eeb12-1830-4fa0-a699-98ea0164505c omicron zones: - ----------------------------------------------------------------------------------------------------------------------- - zone type zone id image source disposition underlay IP - ----------------------------------------------------------------------------------------------------------------------- - clickhouse 4fc6cb68-8ce9-4171-91b3-0106b8fed386 install dataset in service fd00:1122:3344:101::25 - crucible 6cf2a0d4-d9da-4a0c-9103-f1cddb6663a8 install dataset in service fd00:1122:3344:101::2d - crucible b0d184c8-0b73-4b29-8add-3a51b4fd60a6 install dataset in service fd00:1122:3344:101::2c - crucible f401784a-83cb-4e93-a3a3-6914b920d884 install dataset in service fd00:1122:3344:101::2e - crucible_pantry 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact: version 1.2.3 in service fd00:1122:3344:101::2a - crucible_pantry 72d12821-646d-4296-a081-ef5fb3a0ca24 install dataset in service fd00:1122:3344:101::29 - crucible_pantry ae74917b-2c6a-477d-9b90-fe2c2a4247f7 install dataset in service fd00:1122:3344:101::2b - external_dns 77de9dbd-c3d4-4445-ae59-e2f3d4c388e0 install dataset in service fd00:1122:3344:101::26 - external_dns ac0f0ca6-02dd-43c7-b029-f34b247f7392 install dataset in service fd00:1122:3344:101::28 - external_dns f9b07b90-25a8-42ad-a9e9-e497947f4f30 install dataset in service fd00:1122:3344:101::27 - internal_dns 0bb572bc-f5b5-48da-9004-ba336e97bf60 install dataset in service fd00:1122:3344:1::1 - internal_dns bc221177-0711-4bde-9f3d-32d6a6873c51 install dataset in service fd00:1122:3344:2::1 - internal_dns fe8bca28-44ac-4968-ba32-292dede5953f install dataset in service fd00:1122:3344:3::1 - internal_ntp 4211daae-e09f-4742-a62a-32080f429cb3 install dataset in service fd00:1122:3344:101::21 - nexus 296ab8b3-3fd4-4c2b-9e0a-1f909e4993e5 install dataset in service fd00:1122:3344:101::24 - nexus 50dcee18-a857-4596-8cb7-fc9cf90b698d install dataset in service fd00:1122:3344:101::22 - nexus 805cba8f-a039-44e1-8cb0-1ff2c6b89997 install dataset in service fd00:1122:3344:101::23 + ---------------------------------------------------------------------------------------------------------------------------------- + zone type zone id image source disposition underlay IP + ---------------------------------------------------------------------------------------------------------------------------------- + clickhouse 4fc6cb68-8ce9-4171-91b3-0106b8fed386 install dataset in service fd00:1122:3344:101::25 + crucible 6cf2a0d4-d9da-4a0c-9103-f1cddb6663a8 install dataset in service fd00:1122:3344:101::2d + crucible b0d184c8-0b73-4b29-8add-3a51b4fd60a6 install dataset in service fd00:1122:3344:101::2c + crucible f401784a-83cb-4e93-a3a3-6914b920d884 install dataset in service fd00:1122:3344:101::2e + crucible_pantry 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact: version 1.2.3 in service fd00:1122:3344:101::2a + crucible_pantry 72d12821-646d-4296-a081-ef5fb3a0ca24 artifact: version valid-non-semver in service fd00:1122:3344:101::29 + crucible_pantry ae74917b-2c6a-477d-9b90-fe2c2a4247f7 install dataset in service fd00:1122:3344:101::2b + external_dns 77de9dbd-c3d4-4445-ae59-e2f3d4c388e0 install dataset in service fd00:1122:3344:101::26 + external_dns ac0f0ca6-02dd-43c7-b029-f34b247f7392 install dataset in service fd00:1122:3344:101::28 + external_dns f9b07b90-25a8-42ad-a9e9-e497947f4f30 install dataset in service fd00:1122:3344:101::27 + internal_dns 0bb572bc-f5b5-48da-9004-ba336e97bf60 install dataset in service fd00:1122:3344:1::1 + internal_dns bc221177-0711-4bde-9f3d-32d6a6873c51 install dataset in service fd00:1122:3344:2::1 + internal_dns fe8bca28-44ac-4968-ba32-292dede5953f install dataset in service fd00:1122:3344:3::1 + internal_ntp 4211daae-e09f-4742-a62a-32080f429cb3 install dataset in service fd00:1122:3344:101::21 + nexus 296ab8b3-3fd4-4c2b-9e0a-1f909e4993e5 install dataset in service fd00:1122:3344:101::24 + nexus 50dcee18-a857-4596-8cb7-fc9cf90b698d install dataset in service fd00:1122:3344:101::22 + nexus 805cba8f-a039-44e1-8cb0-1ff2c6b89997 install dataset in service fd00:1122:3344:101::23 COCKROACHDB SETTINGS: @@ -194,13 +200,13 @@ parent: 971eeb12-1830-4fa0-a699-98ea0164505c -> blueprint-diff 971eeb12-1830-4fa0-a699-98ea0164505c 9766ca20-38d4-4380-b005-e7c43c797e7c +> blueprint-diff 971eeb12-1830-4fa0-a699-98ea0164505c f714e6ea-e85a-4d7d-93c2-a018744fe176 from: blueprint 971eeb12-1830-4fa0-a699-98ea0164505c -to: blueprint 9766ca20-38d4-4380-b005-e7c43c797e7c +to: blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 MODIFIED SLEDS: - sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 (active, config generation 2 -> 3): + sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 (active, config generation 2 -> 4): physical disks: ------------------------------------------------------------------------------------ @@ -251,27 +257,28 @@ to: blueprint 9766ca20-38d4-4380-b005-e7c43c797e7c omicron zones: - ------------------------------------------------------------------------------------------------------------------------- - zone type zone id image source disposition underlay IP - ------------------------------------------------------------------------------------------------------------------------- - clickhouse 4fc6cb68-8ce9-4171-91b3-0106b8fed386 install dataset in service fd00:1122:3344:101::25 - crucible 6cf2a0d4-d9da-4a0c-9103-f1cddb6663a8 install dataset in service fd00:1122:3344:101::2d - crucible b0d184c8-0b73-4b29-8add-3a51b4fd60a6 install dataset in service fd00:1122:3344:101::2c - crucible f401784a-83cb-4e93-a3a3-6914b920d884 install dataset in service fd00:1122:3344:101::2e - crucible_pantry 72d12821-646d-4296-a081-ef5fb3a0ca24 install dataset in service fd00:1122:3344:101::29 - crucible_pantry ae74917b-2c6a-477d-9b90-fe2c2a4247f7 install dataset in service fd00:1122:3344:101::2b - external_dns 77de9dbd-c3d4-4445-ae59-e2f3d4c388e0 install dataset in service fd00:1122:3344:101::26 - external_dns ac0f0ca6-02dd-43c7-b029-f34b247f7392 install dataset in service fd00:1122:3344:101::28 - external_dns f9b07b90-25a8-42ad-a9e9-e497947f4f30 install dataset in service fd00:1122:3344:101::27 - internal_dns 0bb572bc-f5b5-48da-9004-ba336e97bf60 install dataset in service fd00:1122:3344:1::1 - internal_dns bc221177-0711-4bde-9f3d-32d6a6873c51 install dataset in service fd00:1122:3344:2::1 - internal_dns fe8bca28-44ac-4968-ba32-292dede5953f install dataset in service fd00:1122:3344:3::1 - internal_ntp 4211daae-e09f-4742-a62a-32080f429cb3 install dataset in service fd00:1122:3344:101::21 - nexus 296ab8b3-3fd4-4c2b-9e0a-1f909e4993e5 install dataset in service fd00:1122:3344:101::24 - nexus 50dcee18-a857-4596-8cb7-fc9cf90b698d install dataset in service fd00:1122:3344:101::22 - nexus 805cba8f-a039-44e1-8cb0-1ff2c6b89997 install dataset in service fd00:1122:3344:101::23 -* crucible_pantry 026f8db9-b966-45ec-bf89-fb7b41a04cc9 - install dataset in service fd00:1122:3344:101::2a - └─ + artifact: version 1.2.3 + ------------------------------------------------------------------------------------------------------------------------------------ + zone type zone id image source disposition underlay IP + ------------------------------------------------------------------------------------------------------------------------------------ + clickhouse 4fc6cb68-8ce9-4171-91b3-0106b8fed386 install dataset in service fd00:1122:3344:101::25 + crucible 6cf2a0d4-d9da-4a0c-9103-f1cddb6663a8 install dataset in service fd00:1122:3344:101::2d + crucible b0d184c8-0b73-4b29-8add-3a51b4fd60a6 install dataset in service fd00:1122:3344:101::2c + crucible f401784a-83cb-4e93-a3a3-6914b920d884 install dataset in service fd00:1122:3344:101::2e + crucible_pantry ae74917b-2c6a-477d-9b90-fe2c2a4247f7 install dataset in service fd00:1122:3344:101::2b + external_dns 77de9dbd-c3d4-4445-ae59-e2f3d4c388e0 install dataset in service fd00:1122:3344:101::26 + external_dns ac0f0ca6-02dd-43c7-b029-f34b247f7392 install dataset in service fd00:1122:3344:101::28 + external_dns f9b07b90-25a8-42ad-a9e9-e497947f4f30 install dataset in service fd00:1122:3344:101::27 + internal_dns 0bb572bc-f5b5-48da-9004-ba336e97bf60 install dataset in service fd00:1122:3344:1::1 + internal_dns bc221177-0711-4bde-9f3d-32d6a6873c51 install dataset in service fd00:1122:3344:2::1 + internal_dns fe8bca28-44ac-4968-ba32-292dede5953f install dataset in service fd00:1122:3344:3::1 + internal_ntp 4211daae-e09f-4742-a62a-32080f429cb3 install dataset in service fd00:1122:3344:101::21 + nexus 296ab8b3-3fd4-4c2b-9e0a-1f909e4993e5 install dataset in service fd00:1122:3344:101::24 + nexus 50dcee18-a857-4596-8cb7-fc9cf90b698d install dataset in service fd00:1122:3344:101::22 + nexus 805cba8f-a039-44e1-8cb0-1ff2c6b89997 install dataset in service fd00:1122:3344:101::23 +* crucible_pantry 026f8db9-b966-45ec-bf89-fb7b41a04cc9 - install dataset in service fd00:1122:3344:101::2a + └─ + artifact: version 1.2.3 +* crucible_pantry 72d12821-646d-4296-a081-ef5fb3a0ca24 - install dataset in service fd00:1122:3344:101::29 + └─ + artifact: version valid-non-semver COCKROACHDB SETTINGS: @@ -361,17 +368,21 @@ external DNS: > -> # Set the zone's image source back to the install dataset. +> # Set these zones' image sources back to the install dataset. -> blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 install-dataset -blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 created from blueprint 9766ca20-38d4-4380-b005-e7c43c797e7c: set sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 zone 026f8db9-b966-45ec-bf89-fb7b41a04cc9 image source to install dataset +> blueprint-edit f714e6ea-e85a-4d7d-93c2-a018744fe176 set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 install-dataset +blueprint bb128f06-a2e1-44c1-8874-4f789d0ff896 created from blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176: set sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 zone 026f8db9-b966-45ec-bf89-fb7b41a04cc9 image source to install dataset warn: no validation is done on the requested image source -> blueprint-show f714e6ea-e85a-4d7d-93c2-a018744fe176 -blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 -parent: 9766ca20-38d4-4380-b005-e7c43c797e7c +> blueprint-edit bb128f06-a2e1-44c1-8874-4f789d0ff896 set-zone-image 72d12821-646d-4296-a081-ef5fb3a0ca24 install-dataset +blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9 created from blueprint bb128f06-a2e1-44c1-8874-4f789d0ff896: set sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 zone 72d12821-646d-4296-a081-ef5fb3a0ca24 image source to install dataset +warn: no validation is done on the requested image source - sled: 868d5b02-7792-4fc0-b6a9-654afcae9ea0 (active, config generation 4) +> blueprint-show d9c572a1-a68c-4945-b1ec-5389bd588fe9 +blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9 +parent: bb128f06-a2e1-44c1-8874-4f789d0ff896 + + sled: 868d5b02-7792-4fc0-b6a9-654afcae9ea0 (active, config generation 6) physical disks: ------------------------------------------------------------------------------------ @@ -457,13 +468,13 @@ parent: 9766ca20-38d4-4380-b005-e7c43c797e7c -> blueprint-diff 9766ca20-38d4-4380-b005-e7c43c797e7c f714e6ea-e85a-4d7d-93c2-a018744fe176 -from: blueprint 9766ca20-38d4-4380-b005-e7c43c797e7c -to: blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 +> blueprint-diff f714e6ea-e85a-4d7d-93c2-a018744fe176 d9c572a1-a68c-4945-b1ec-5389bd588fe9 +from: blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 +to: blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9 MODIFIED SLEDS: - sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 (active, config generation 3 -> 4): + sled 868d5b02-7792-4fc0-b6a9-654afcae9ea0 (active, config generation 4 -> 6): physical disks: ------------------------------------------------------------------------------------ @@ -514,27 +525,28 @@ to: blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 omicron zones: - ------------------------------------------------------------------------------------------------------------------------- - zone type zone id image source disposition underlay IP - ------------------------------------------------------------------------------------------------------------------------- - clickhouse 4fc6cb68-8ce9-4171-91b3-0106b8fed386 install dataset in service fd00:1122:3344:101::25 - crucible 6cf2a0d4-d9da-4a0c-9103-f1cddb6663a8 install dataset in service fd00:1122:3344:101::2d - crucible b0d184c8-0b73-4b29-8add-3a51b4fd60a6 install dataset in service fd00:1122:3344:101::2c - crucible f401784a-83cb-4e93-a3a3-6914b920d884 install dataset in service fd00:1122:3344:101::2e - crucible_pantry 72d12821-646d-4296-a081-ef5fb3a0ca24 install dataset in service fd00:1122:3344:101::29 - crucible_pantry ae74917b-2c6a-477d-9b90-fe2c2a4247f7 install dataset in service fd00:1122:3344:101::2b - external_dns 77de9dbd-c3d4-4445-ae59-e2f3d4c388e0 install dataset in service fd00:1122:3344:101::26 - external_dns ac0f0ca6-02dd-43c7-b029-f34b247f7392 install dataset in service fd00:1122:3344:101::28 - external_dns f9b07b90-25a8-42ad-a9e9-e497947f4f30 install dataset in service fd00:1122:3344:101::27 - internal_dns 0bb572bc-f5b5-48da-9004-ba336e97bf60 install dataset in service fd00:1122:3344:1::1 - internal_dns bc221177-0711-4bde-9f3d-32d6a6873c51 install dataset in service fd00:1122:3344:2::1 - internal_dns fe8bca28-44ac-4968-ba32-292dede5953f install dataset in service fd00:1122:3344:3::1 - internal_ntp 4211daae-e09f-4742-a62a-32080f429cb3 install dataset in service fd00:1122:3344:101::21 - nexus 296ab8b3-3fd4-4c2b-9e0a-1f909e4993e5 install dataset in service fd00:1122:3344:101::24 - nexus 50dcee18-a857-4596-8cb7-fc9cf90b698d install dataset in service fd00:1122:3344:101::22 - nexus 805cba8f-a039-44e1-8cb0-1ff2c6b89997 install dataset in service fd00:1122:3344:101::23 -* crucible_pantry 026f8db9-b966-45ec-bf89-fb7b41a04cc9 - artifact: version 1.2.3 in service fd00:1122:3344:101::2a - └─ + install dataset + ------------------------------------------------------------------------------------------------------------------------------------ + zone type zone id image source disposition underlay IP + ------------------------------------------------------------------------------------------------------------------------------------ + clickhouse 4fc6cb68-8ce9-4171-91b3-0106b8fed386 install dataset in service fd00:1122:3344:101::25 + crucible 6cf2a0d4-d9da-4a0c-9103-f1cddb6663a8 install dataset in service fd00:1122:3344:101::2d + crucible b0d184c8-0b73-4b29-8add-3a51b4fd60a6 install dataset in service fd00:1122:3344:101::2c + crucible f401784a-83cb-4e93-a3a3-6914b920d884 install dataset in service fd00:1122:3344:101::2e + crucible_pantry ae74917b-2c6a-477d-9b90-fe2c2a4247f7 install dataset in service fd00:1122:3344:101::2b + external_dns 77de9dbd-c3d4-4445-ae59-e2f3d4c388e0 install dataset in service fd00:1122:3344:101::26 + external_dns ac0f0ca6-02dd-43c7-b029-f34b247f7392 install dataset in service fd00:1122:3344:101::28 + external_dns f9b07b90-25a8-42ad-a9e9-e497947f4f30 install dataset in service fd00:1122:3344:101::27 + internal_dns 0bb572bc-f5b5-48da-9004-ba336e97bf60 install dataset in service fd00:1122:3344:1::1 + internal_dns bc221177-0711-4bde-9f3d-32d6a6873c51 install dataset in service fd00:1122:3344:2::1 + internal_dns fe8bca28-44ac-4968-ba32-292dede5953f install dataset in service fd00:1122:3344:3::1 + internal_ntp 4211daae-e09f-4742-a62a-32080f429cb3 install dataset in service fd00:1122:3344:101::21 + nexus 296ab8b3-3fd4-4c2b-9e0a-1f909e4993e5 install dataset in service fd00:1122:3344:101::24 + nexus 50dcee18-a857-4596-8cb7-fc9cf90b698d install dataset in service fd00:1122:3344:101::22 + nexus 805cba8f-a039-44e1-8cb0-1ff2c6b89997 install dataset in service fd00:1122:3344:101::23 +* crucible_pantry 026f8db9-b966-45ec-bf89-fb7b41a04cc9 - artifact: version 1.2.3 in service fd00:1122:3344:101::2a + └─ + install dataset +* crucible_pantry 72d12821-646d-4296-a081-ef5fb3a0ca24 - artifact: version valid-non-semver in service fd00:1122:3344:101::29 + └─ + install dataset COCKROACHDB SETTINGS: @@ -626,7 +638,7 @@ external DNS: > # test help output -> blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c help +> blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 help edit contents of a blueprint directly Usage: blueprint-edit [OPTIONS] @@ -646,7 +658,7 @@ Options: --comment "comment" field for the new blueprint -h, --help Print help -> blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image help +> blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 set-zone-image help set the image source for a zone Usage: blueprint-edit set-zone-image @@ -666,14 +678,14 @@ Options: > # test error case: no such zone id -> blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 126f8db9-b966-45ec-bf89-fb7b41a04cc9 install-dataset +> blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 set-zone-image 126f8db9-b966-45ec-bf89-fb7b41a04cc9 install-dataset error: could not find parent sled for zone 126f8db9-b966-45ec-bf89-fb7b41a04cc9 -> # test error case: bad semver +> # test error case: invalid version identifier -> blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact one e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +> blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact 🫡 5d835e8666bd5cf65be66efbca6847b24e41fd85993448a211ad7be5fea559ab > # test error case: bad hash -> blueprint-edit 9766ca20-38d4-4380-b005-e7c43c797e7c set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact 1.2.3 aaaa +> blueprint-edit d9c572a1-a68c-4945-b1ec-5389bd588fe9 set-zone-image 026f8db9-b966-45ec-bf89-fb7b41a04cc9 artifact 1.2.3 aaaa diff --git a/dev-tools/releng/src/hubris.rs b/dev-tools/releng/src/hubris.rs index 23ac60f1f48..c44a01dec3b 100644 --- a/dev-tools/releng/src/hubris.rs +++ b/dev-tools/releng/src/hubris.rs @@ -14,6 +14,7 @@ use semver::Version; use serde::Deserialize; use slog::Logger; use slog::warn; +use tufaceous_artifact::ArtifactVersion; use tufaceous_artifact::KnownArtifactKind; use tufaceous_lib::assemble::DeserializedArtifactData; use tufaceous_lib::assemble::DeserializedArtifactSource; @@ -83,8 +84,17 @@ pub(crate) async fn fetch_hubris_artifacts( }; manifest.artifacts.entry(kind).or_default().push( DeserializedArtifactData { - name: artifact.name, - version: artifact.version, + name: artifact.name.clone(), + version: artifact + .version + .to_string() + .parse::() + .with_context(|| { + format!( + "artifact {} has invalid version: {}", + artifact.name, artifact.version + ) + })?, source, }, ); diff --git a/dev-tools/releng/src/tuf.rs b/dev-tools/releng/src/tuf.rs index 136d75a5ec2..499f91e8af0 100644 --- a/dev-tools/releng/src/tuf.rs +++ b/dev-tools/releng/src/tuf.rs @@ -18,6 +18,7 @@ use sha2::Digest; use sha2::Sha256; use slog::Logger; use tokio::io::AsyncReadExt; +use tufaceous_artifact::ArtifactVersion; use tufaceous_artifact::KnownArtifactKind; use tufaceous_lib::Key; use tufaceous_lib::assemble::ArtifactManifest; @@ -41,6 +42,11 @@ pub(crate) async fn build_tuf_repo( // write it to disk, and then turn it into an `ArtifactManifest` to actually // build the repo. + let artifact_version = + version.to_string().parse::().with_context(|| { + format!("failed to parse artifact version from {}", version) + })?; + // Start a new manifest by loading the Hubris staging manifest. let mut manifest = DeserializedManifest::from_path( &output_dir.join("hubris-staging/manifest.toml"), @@ -71,7 +77,7 @@ pub(crate) async fn build_tuf_repo( KnownArtifactKind::Host, vec![DeserializedArtifactData { name: "host".to_string(), - version: manifest.system_version.clone(), + version: artifact_version.clone(), source: DeserializedArtifactSource::File { path: output_dir.join("os-host/os.tar.gz"), }, @@ -81,7 +87,7 @@ pub(crate) async fn build_tuf_repo( KnownArtifactKind::Trampoline, vec![DeserializedArtifactData { name: "trampoline".to_string(), - version: manifest.system_version.clone(), + version: artifact_version.clone(), source: DeserializedArtifactSource::File { path: output_dir.join("os-recovery/os.tar.gz"), }, @@ -109,7 +115,7 @@ pub(crate) async fn build_tuf_repo( KnownArtifactKind::ControlPlane, vec![DeserializedArtifactData { name: "control-plane".to_string(), - version: manifest.system_version.clone(), + version: artifact_version.clone(), source: DeserializedArtifactSource::CompositeControlPlane { zones }, }], ); @@ -123,6 +129,7 @@ pub(crate) async fn build_tuf_repo( // Convert the manifest. let manifest = ArtifactManifest::from_deserialized(&output_dir, manifest)?; + manifest.verify_all_semver()?; manifest.verify_all_present()?; // Assemble the repo. let keys = vec![Key::generate_ed25519()?]; diff --git a/nexus/db-model/src/tuf_repo.rs b/nexus/db-model/src/tuf_repo.rs index 7df713614b4..0302966e8a9 100644 --- a/nexus/db-model/src/tuf_repo.rs +++ b/nexus/db-model/src/tuf_repo.rs @@ -18,9 +18,10 @@ use omicron_common::{ use omicron_uuid_kinds::TufArtifactKind; use omicron_uuid_kinds::TufRepoKind; use omicron_uuid_kinds::TypedUuid; +use parse_display::Display; use serde::{Deserialize, Serialize}; use std::fmt; -use tufaceous_artifact::ArtifactKind; +use tufaceous_artifact::{ArtifactKind, ArtifactVersion}; use uuid::Uuid; /// A description of a TUF update: a repo, along with the artifacts it @@ -154,7 +155,7 @@ impl TufRepo { pub struct TufArtifact { pub id: DbTypedUuid, pub name: String, - pub version: SemverVersion, + pub version: DbArtifactVersion, pub kind: String, pub time_created: DateTime, pub sha256: ArtifactHash, @@ -220,7 +221,7 @@ impl TufArtifact { /// Returns the artifact's name, version, and kind, which is unique across /// all artifacts. - pub fn nvk(&self) -> (&str, &SemverVersion, &str) { + pub fn nvk(&self) -> (&str, &ArtifactVersion, &str) { (&self.name, &self.version, &self.kind) } @@ -230,6 +231,50 @@ impl TufArtifact { } } +/// Artifact version in the database: a freeform identifier. +#[derive( + Clone, + Debug, + AsExpression, + FromSqlRow, + Serialize, + Deserialize, + PartialEq, + Eq, + Hash, + Display, +)] +#[diesel(sql_type = Text)] +#[serde(transparent)] +#[display("{0}")] +pub struct DbArtifactVersion(pub ArtifactVersion); + +NewtypeFrom! { () pub struct DbArtifactVersion(ArtifactVersion); } +NewtypeDeref! { () pub struct DbArtifactVersion(ArtifactVersion); } + +impl ToSql for DbArtifactVersion { + fn to_sql<'a>( + &'a self, + out: &mut diesel::serialize::Output<'a, '_, diesel::pg::Pg>, + ) -> diesel::serialize::Result { + >::to_sql( + &self.0.to_string(), + &mut out.reborrow(), + ) + } +} + +impl FromSql for DbArtifactVersion { + fn from_sql( + bytes: diesel::pg::PgValue<'_>, + ) -> diesel::deserialize::Result { + let s = >::from_sql(bytes)?; + s.parse::() + .map(DbArtifactVersion) + .map_err(|e| e.into()) + } +} + /// A many-to-many relationship between [`TufRepo`] and [`TufArtifact`]. #[derive(Queryable, Insertable, Clone, Debug, Selectable)] #[diesel(table_name = tuf_repo_artifact)] diff --git a/nexus/db-queries/src/db/datastore/target_release.rs b/nexus/db-queries/src/db/datastore/target_release.rs index e3876746d73..a111d526cb0 100644 --- a/nexus/db-queries/src/db/datastore/target_release.rs +++ b/nexus/db-queries/src/db/datastore/target_release.rs @@ -120,7 +120,7 @@ mod test { use omicron_common::update::ArtifactId; use omicron_test_utils::dev; use semver::Version; - use tufaceous_artifact::ArtifactKind; + use tufaceous_artifact::{ArtifactKind, ArtifactVersion}; #[tokio::test] async fn target_release_datastore() { @@ -182,6 +182,8 @@ mod test { // Now add a new TUF repo and use it as the source. let version = Version::new(0, 0, 1); + const ARTIFACT_VERSION: ArtifactVersion = + ArtifactVersion::new_const("0.0.1"); let hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" .parse() @@ -200,7 +202,7 @@ mod test { artifacts: vec![TufArtifactMeta { id: ArtifactId { name: String::new(), - version: version.clone(), + version: ARTIFACT_VERSION, kind: ArtifactKind::from_static("empty"), }, hash, diff --git a/nexus/reconfigurator/planning/Cargo.toml b/nexus/reconfigurator/planning/Cargo.toml index 3f414de0a7a..695e65eb584 100644 --- a/nexus/reconfigurator/planning/Cargo.toml +++ b/nexus/reconfigurator/planning/Cargo.toml @@ -29,13 +29,13 @@ omicron-uuid-kinds.workspace = true once_cell.workspace = true oxnet.workspace = true rand.workspace = true -semver.workspace = true sled-agent-client.workspace = true slog.workspace = true slog-error-chain.workspace = true static_assertions.workspace = true strum.workspace = true thiserror.workspace = true +tufaceous-artifact.workspace = true typed-rng.workspace = true uuid.workspace = true diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 2c12069d02d..508d8f6cb39 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -2062,9 +2062,9 @@ pub mod test { use omicron_common::disk::DatasetName; use omicron_common::update::ArtifactHash; use omicron_test_utils::dev::test_setup_log; - use semver::Version; use std::collections::BTreeSet; use std::mem; + use tufaceous_artifact::ArtifactVersion; pub const DEFAULT_N_SLEDS: usize = 3; @@ -3141,11 +3141,13 @@ pub mod test { .iter(); let zone_id = zones.next().expect("zone exists").id; // Set the zone's image source to an artifact. + const ARTIFACT_VERSION: ArtifactVersion = + ArtifactVersion::new_const("1.2.3"); editor .set_zone_image_source( &zone_id, BlueprintZoneImageSource::Artifact { - version: Version::new(1, 2, 3), + version: ARTIFACT_VERSION, // The hash is not displayed in the diff -- only the // version is. hash: ArtifactHash([0x12; 32]), diff --git a/nexus/tests/integration_tests/target_release.rs b/nexus/tests/integration_tests/target_release.rs index abcd63fb9b0..298db8720e1 100644 --- a/nexus/tests/integration_tests/target_release.rs +++ b/nexus/tests/integration_tests/target_release.rs @@ -4,10 +4,11 @@ //! Get/set the target release via the external API. +use camino::Utf8Path; use camino_tempfile::Utf8TempDir; use chrono::Utc; use clap::Parser as _; -use dropshot::test_util::LogContext; +use dropshot::test_util::{ClientTestContext, LogContext}; use http::StatusCode; use http::method::Method; use nexus_config::UpdatesConfig; @@ -38,6 +39,7 @@ async fn get_set_target_release() -> anyhow::Result<()> { ) .await; let client = &ctx.external_client; + let logctx = LogContext::new("get_set_target_release", &config.pkg.log); // There should always be a target release. let target_release: TargetRelease = @@ -64,55 +66,113 @@ async fn get_set_target_release() -> anyhow::Result<()> { .await .expect_err("invalid TUF repo"); + let temp = Utf8TempDir::new().unwrap(); + // Adding a fake (tufaceous) repo and then setting it as the // target release should succeed. - let before = Utc::now(); - let system_version = Version::new(1, 0, 0); - let logctx = LogContext::new("get_set_target_release", &config.pkg.log); - let temp = Utf8TempDir::new().unwrap(); - let path = temp.path().join("repo.zip"); - tufaceous::Args::try_parse_from([ - "tufaceous", - "assemble", - "../update-common/manifests/fake.toml", - path.as_str(), - ]) - .expect("can't parse tufaceous args") - .exec(&logctx.log) - .await - .expect("can't assemble TUF repo"); + { + let before = Utc::now(); + let system_version = Version::new(1, 0, 0); + let path = temp.path().join("repo-1.0.0.zip"); + tufaceous::Args::try_parse_from([ + "tufaceous", + "assemble", + "../update-common/manifests/fake.toml", + path.as_str(), + ]) + .expect("can't parse tufaceous args") + .exec(&logctx.log) + .await + .expect("can't assemble TUF repo"); - assert_eq!( - system_version, - NexusRequest::new( - RequestBuilder::new( - client, - http::Method::PUT, - "/v1/system/update/repository?file_name=/tmp/foo.zip", - ) - .body_file(Some(&path)) - .expect_status(Some(http::StatusCode::OK)), - ) - .authn_as(AuthnMode::PrivilegedUser) - .execute() + assert_eq!( + system_version, + upload_tuf_repo(client, &path).await.recorded.repo.system_version + ); + + let target_release = + set_target_release(client, system_version.clone()).await; + let after = Utc::now(); + assert_eq!(target_release.generation, 2); + assert!(target_release.time_requested >= before); + assert!(target_release.time_requested <= after); + assert_eq!( + target_release.release_source, + TargetReleaseSource::SystemVersion { version: system_version }, + ); + } + + // Add a repo with non-semver versions. + { + let before = Utc::now(); + let system_version = Version::new(2, 0, 0); + let path = temp.path().join("repo-2.0.0.zip"); + tufaceous::Args::try_parse_from([ + "tufaceous", + "assemble", + "../update-common/manifests/fake-non-semver.toml", + "--allow-non-semver", + path.as_str(), + ]) + .expect("can't parse tufaceous args") + .exec(&logctx.log) .await - .unwrap() - .parsed_body::() - .unwrap() - .recorded - .repo - .system_version - ); + .expect("can't assemble TUF repo"); + + assert_eq!( + system_version, + upload_tuf_repo(client, &path).await.recorded.repo.system_version + ); + + let target_release = + set_target_release(client, system_version.clone()).await; + let after = Utc::now(); + assert_eq!(target_release.generation, 3); + assert!(target_release.time_requested >= before); + assert!(target_release.time_requested <= after); + assert_eq!( + target_release.release_source, + TargetReleaseSource::SystemVersion { version: system_version }, + ); + } + + ctx.teardown().await; + logctx.cleanup_successful(); + Ok(()) +} - let target_release: TargetRelease = NexusRequest::new( +async fn upload_tuf_repo( + client: &ClientTestContext, + path: &Utf8Path, +) -> TufRepoInsertResponse { + NexusRequest::new( + RequestBuilder::new( + client, + http::Method::PUT, + "/v1/system/update/repository?file_name=/tmp/foo.zip", + ) + .body_file(Some(path)) + .expect_status(Some(StatusCode::OK)), + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap() + .parsed_body::() + .unwrap() +} + +async fn set_target_release( + client: &ClientTestContext, + system_version: Version, +) -> TargetRelease { + NexusRequest::new( RequestBuilder::new( client, Method::PUT, "/v1/system/update/target-release", ) - .body(Some(&SetTargetReleaseParams { - system_version: system_version.clone(), - })) + .body(Some(&SetTargetReleaseParams { system_version })) .expect_status(Some(StatusCode::CREATED)), ) .authn_as(AuthnMode::PrivilegedUser) @@ -120,17 +180,5 @@ async fn get_set_target_release() -> anyhow::Result<()> { .await .unwrap() .parsed_body() - .unwrap(); - let after = Utc::now(); - assert_eq!(target_release.generation, 2); - assert!(target_release.time_requested >= before); - assert!(target_release.time_requested <= after); - assert_eq!( - target_release.release_source, - TargetReleaseSource::SystemVersion { version: system_version }, - ); - - ctx.teardown().await; - logctx.cleanup_successful(); - Ok(()) + .unwrap() } diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml index dd01d8a242d..6b9ca89e831 100644 --- a/nexus/types/Cargo.toml +++ b/nexus/types/Cargo.toml @@ -40,6 +40,7 @@ slog-error-chain.workspace = true steno.workspace = true strum.workspace = true thiserror.workspace = true +tufaceous-artifact.workspace = true newtype-uuid.workspace = true update-engine.workspace = true uuid.workspace = true diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index f15e74cd688..f1c79b6f473 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -44,7 +44,6 @@ use omicron_uuid_kinds::PhysicalDiskUuid; use omicron_uuid_kinds::SledUuid; use omicron_uuid_kinds::ZpoolUuid; use schemars::JsonSchema; -use semver::Version; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; @@ -53,6 +52,7 @@ use std::fmt; use std::net::Ipv6Addr; use std::net::SocketAddrV6; use strum::EnumIter; +use tufaceous_artifact::ArtifactVersion; mod blueprint_diff; mod blueprint_display; @@ -934,7 +934,7 @@ pub enum BlueprintZoneImageSource { /// This originates from TUF repos uploaded to Nexus which are then /// replicated out to all sleds. #[serde(rename_all = "snake_case")] - Artifact { version: Version, hash: ArtifactHash }, + Artifact { version: ArtifactVersion, hash: ArtifactHash }, } impl From for OmicronZoneImageSource { diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 23b5598a13a..ac4c4167951 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -1546,6 +1546,11 @@ } ] }, + "ArtifactVersion": { + "description": "An artifact version.\n\nThis is a freeform identifier with some basic validation. It may be the serialized form of a semver version, or a custom identifier that uses the same character set as a semver, plus `_`.\n\nThe exact pattern accepted is `^[a-zA-Z0-9._+-]{1,63}$`.\n\n# Ord implementation\n\n`ArtifactVersion`s are not intended to be sorted, just compared for equality. `ArtifactVersion` implements `Ord` only for storage within sorted collections.", + "type": "string", + "pattern": "^[a-zA-Z0-9._+-]{1,63}$" + }, "BackgroundTask": { "description": "Background tasks\n\nThese are currently only intended for observability by developers. We will eventually want to flesh this out into something more observable for end users.", "type": "object", @@ -2374,8 +2379,7 @@ ] }, "version": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + "$ref": "#/components/schemas/ArtifactVersion" } }, "required": [ diff --git a/openapi/wicketd.json b/openapi/wicketd.json index 316ae1ef60d..ff9cd9ca216 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -903,8 +903,11 @@ }, "version": { "description": "The artifact's version.", - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + "allOf": [ + { + "$ref": "#/components/schemas/ArtifactVersion" + } + ] } }, "required": [ @@ -913,6 +916,11 @@ "version" ] }, + "ArtifactVersion": { + "description": "An artifact version.\n\nThis is a freeform identifier with some basic validation. It may be the serialized form of a semver version, or a custom identifier that uses the same character set as a semver, plus `_`.\n\nThe exact pattern accepted is `^[a-zA-Z0-9._+-]{1,63}$`.\n\n# Ord implementation\n\n`ArtifactVersion`s are not intended to be sorted, just compared for equality. `ArtifactVersion` implements `Ord` only for storage within sorted collections.", + "type": "string", + "pattern": "^[a-zA-Z0-9._+-]{1,63}$" + }, "Aux1Monitor": { "description": "The first auxlliary CMIS monitor.", "oneOf": [ diff --git a/update-common/manifests/fake-non-semver.toml b/update-common/manifests/fake-non-semver.toml new file mode 100644 index 00000000000..ba805e1ba21 --- /dev/null +++ b/update-common/manifests/fake-non-semver.toml @@ -0,0 +1,87 @@ +# This is an artifact manifest that generates fake entries for all components. +# Some of the components have non-semver artifact versions. +# +# This is completely non-functional and is only useful for testing archive +# extraction in other parts of the repository. + +system_version = "2.0.0" + +[[artifact.gimlet_sp]] +name = "fake-gimlet-sp" +version = "2.0.0" +source = { kind = "fake", size = "1MiB" } + +[[artifact.gimlet_rot]] +name = "fake-gimlet-rot" +version = "2.0.0" +[artifact.gimlet_rot.source] +kind = "composite-rot" +archive_a = { kind = "fake", size = "512KiB" } +archive_b = { kind = "fake", size = "512KiB" } + +[[artifact.host]] +name = "fake-host" +version = "2.0.0" +[artifact.host.source] +kind = "composite-host" +phase_1 = { kind = "fake", size = "512KiB" } +phase_2 = { kind = "fake", size = "1MiB" } + +[[artifact.trampoline]] +name = "fake-trampoline" +version = "non-semver" +[artifact.trampoline.source] +kind = "composite-host" +phase_1 = { kind = "fake", size = "512KiB" } +phase_2 = { kind = "fake", size = "1MiB" } + +[[artifact.control_plane]] +name = "fake-control-plane" +version = "2.0.0" +[artifact.control_plane.source] +kind = "composite-control-plane" +zones = [ + { kind = "fake", name = "zone1", size = "1MiB" }, + { kind = "fake", name = "zone2", size = "1MiB" }, +] + +[[artifact.psc_sp]] +name = "fake-psc-sp" +version = "2.0.0" +source = { kind = "fake", size = "1MiB" } + +[[artifact.psc_rot]] +name = "fake-psc-rot" +version = "2.0.0" +[artifact.psc_rot.source] +kind = "composite-rot" +archive_a = { kind = "fake", size = "512KiB" } +archive_b = { kind = "fake", size = "512KiB" } + +[[artifact.switch_sp]] +name = "fake-switch-sp" +version = "2.0.0" +source = { kind = "fake", size = "1MiB" } + +[[artifact.switch_rot]] +name = "fake-switch-rot" +version = "2.0.0" +[artifact.switch_rot.source] +kind = "composite-rot" +archive_a = { kind = "fake", size = "512KiB" } +archive_b = { kind = "fake", size = "512KiB" } + +[[artifact.gimlet_rot_bootloader]] +name = "fake-gimlet-rot-bootloader" +version = "2.0.0" +source = { kind = "fake", size = "1MiB" } + +[[artifact.psc_rot_bootloader]] +name = "fake-psc-rot-bootloader" +version = "2.0.0" +source = { kind = "fake", size = "1MiB" } + +[[artifact.switch_rot_bootloader]] +name = "fake-switch-rot-bootloader" +version = "non-semver-2" +source = { kind = "fake", size = "1MiB" } diff --git a/update-common/src/artifacts/update_plan.rs b/update-common/src/artifacts/update_plan.rs index b31b9d78ed7..93213e9ae9c 100644 --- a/update-common/src/artifacts/update_plan.rs +++ b/update-common/src/artifacts/update_plan.rs @@ -36,6 +36,7 @@ use std::io; use tokio::io::AsyncReadExt; use tokio::runtime::Handle; use tufaceous_artifact::ArtifactKind; +use tufaceous_artifact::ArtifactVersion; use tufaceous_artifact::KnownArtifactKind; use tufaceous_lib::ControlPlaneZoneImages; use tufaceous_lib::HostPhaseImages; @@ -932,7 +933,7 @@ impl<'a> UpdatePlanBuilder<'a> { let artifact_id = ArtifactId { name: info.pkg.clone(), - version: info.version.clone(), + version: ArtifactVersion::new(info.version.to_string())?, kind: KnownArtifactKind::Zone.into(), }; self.record_extracted_artifact( @@ -1465,13 +1466,15 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_bad_rot_versions() { - const VERSION_0: Version = Version::new(0, 0, 0); - const VERSION_1: Version = Version::new(0, 0, 1); + const ARTIFACT_VERSION_0: ArtifactVersion = + ArtifactVersion::new_const("0.0.0"); + const ARTIFACT_VERSION_1: ArtifactVersion = + ArtifactVersion::new_const("0.0.1"); let logctx = test_setup_log("test_bad_rot_version"); let mut plan_builder = UpdatePlanBuilder::new( - VERSION_0, + "0.0.0".parse().unwrap(), ControlPlaneZonesMode::Composite, &logctx.log, ) @@ -1485,7 +1488,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1512,7 +1515,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: board.to_string(), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1539,7 +1542,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1568,7 +1571,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1586,7 +1589,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{bad_kind:?}"), - version: VERSION_1, + version: ARTIFACT_VERSION_1, kind: bad_kind.into(), }; plan_builder @@ -1615,7 +1618,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&artifact).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1637,8 +1640,10 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_multi_rot_version() { - const VERSION_0: Version = Version::new(0, 0, 0); - const VERSION_1: Version = Version::new(0, 0, 1); + const ARTIFACT_VERSION_0: ArtifactVersion = + ArtifactVersion::new_const("0.0.0"); + const ARTIFACT_VERSION_1: ArtifactVersion = + ArtifactVersion::new_const("0.0.1"); let logctx = test_setup_log("test_multi_rot_version"); @@ -1657,7 +1662,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1684,7 +1689,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: board.to_string(), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1711,7 +1716,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1742,7 +1747,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1764,7 +1769,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_1, + version: ARTIFACT_VERSION_1, kind: kind.into(), }; plan_builder @@ -1798,7 +1803,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&artifact).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1826,7 +1831,8 @@ mod tests { // is required. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_update_plan_from_artifacts() { - const VERSION_0: Version = Version::new(0, 0, 0); + const ARTIFACT_VERSION_0: ArtifactVersion = + ArtifactVersion::new_const("0.0.0"); let logctx = test_setup_log("test_update_plan_from_artifacts"); @@ -1846,7 +1852,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: kind.to_string(), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.parse().unwrap(), }; expected_unknown_artifacts.insert(id.clone()); @@ -1868,7 +1874,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1895,7 +1901,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: board.to_string(), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1922,7 +1928,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1948,7 +1954,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -1982,7 +1988,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&artifact).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -2131,7 +2137,8 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_bad_hubris_cabooses() { - const VERSION_0: Version = Version::new(0, 0, 0); + const ARTIFACT_VERSION_0: ArtifactVersion = + ArtifactVersion::new_const("0.0.0"); let logctx = test_setup_log("test_bad_hubris_cabooses"); @@ -2155,7 +2162,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; match plan_builder @@ -2188,7 +2195,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&artifact).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; match plan_builder @@ -2209,8 +2216,10 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_too_many_rot_bootloaders() { - const VERSION_0: Version = Version::new(0, 0, 0); - const VERSION_1: Version = Version::new(0, 0, 1); + const ARTIFACT_VERSION_0: ArtifactVersion = + ArtifactVersion::new_const("0.0.0"); + const ARTIFACT_VERSION_1: ArtifactVersion = + ArtifactVersion::new_const("0.0.1"); // The regular RoT can have multiple versions but _not_ the // bootloader @@ -2233,7 +2242,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&gimlet_rot_bootloader).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -2248,7 +2257,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&gimlet2_rot_bootloader).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_1, + version: ARTIFACT_VERSION_1, kind: kind.into(), }; match plan_builder @@ -2278,7 +2287,8 @@ mod tests { // YYYY BBBB 2.0.0 // YYYY CCCC 2.0.0 - const VERSION_0: Version = Version::new(0, 0, 0); + const ARTIFACT_VERSION_0: ArtifactVersion = + ArtifactVersion::new_const("0.0.0"); let logctx = test_setup_log("test_update_plan_from_artifacts"); @@ -2297,7 +2307,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -2324,7 +2334,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: board.to_string(), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -2351,7 +2361,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -2380,7 +2390,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -2414,7 +2424,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&artifact).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -2442,7 +2452,8 @@ mod tests { // YYYY BBBB // YYYY CCCC - const VERSION_0: Version = Version::new(0, 0, 0); + const ARTIFACT_VERSION_0: ArtifactVersion = + ArtifactVersion::new_const("0.0.0"); let logctx = test_setup_log("test_update_plan_from_artifacts"); @@ -2462,7 +2473,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; plan_builder @@ -2474,7 +2485,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(data).into()); let id = ArtifactId { name: format!("{kind:?}"), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: kind.into(), }; match plan_builder @@ -2491,6 +2502,8 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_split_control_plane() { const VERSION_0: Version = Version::new(0, 0, 0); + const ARTIFACT_VERSION_0: ArtifactVersion = + ArtifactVersion::new_const("0.0.0"); let logctx = test_setup_log("test_split_control_plane"); @@ -2533,7 +2546,7 @@ mod tests { let hash = ArtifactHash(Sha256::digest(&data).into()); let id = ArtifactId { name: "control_plane".into(), - version: VERSION_0, + version: ARTIFACT_VERSION_0, kind: KnownArtifactKind::ControlPlane.into(), }; plan_builder @@ -2548,7 +2561,7 @@ mod tests { let content = &zones.iter().find(|(name, _)| *name == id.name).unwrap().1; let expected_hash = ArtifactHash(Sha256::digest(content).into()); - assert_eq!(id.version, VERSION_0); + assert_eq!(id.version, ARTIFACT_VERSION_0); assert_eq!(id.kind, KnownArtifactKind::Zone.into()); assert_eq!( vec, diff --git a/update-common/src/errors.rs b/update-common/src/errors.rs index 47674540904..f3b82551b0d 100644 --- a/update-common/src/errors.rs +++ b/update-common/src/errors.rs @@ -8,10 +8,9 @@ use camino::Utf8PathBuf; use display_error_chain::DisplayErrorChain; use dropshot::HttpError; use omicron_common::update::{ArtifactHashId, ArtifactId}; -use semver::Version; use slog::error; use thiserror::Error; -use tufaceous_artifact::{ArtifactKind, KnownArtifactKind}; +use tufaceous_artifact::{ArtifactKind, ArtifactVersion, KnownArtifactKind}; #[derive(Debug, Error)] pub enum RepositoryError { @@ -144,8 +143,8 @@ pub enum RepositoryError { )] MultipleVersionsPresent { kind: KnownArtifactKind, - v1: Version, - v2: Version, + v1: ArtifactVersion, + v2: ArtifactVersion, }, #[error("Caboose mismatch between A {a:?} and B {b:?}")] CabooseMismatch { a: String, b: String }, diff --git a/wicket/src/events.rs b/wicket/src/events.rs index ca68a0dcba4..e920fdc27bc 100644 --- a/wicket/src/events.rs +++ b/wicket/src/events.rs @@ -4,6 +4,7 @@ use crate::{State, keymap::Cmd, state::ComponentId}; use camino::Utf8PathBuf; use humantime::format_rfc3339; +use omicron_common::update::ArtifactId; use semver::Version; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -12,7 +13,7 @@ use std::time::SystemTime; use wicket_common::inventory::RackV1Inventory; use wicket_common::update_events::EventReport; use wicketd_client::types::{ - ArtifactId, CurrentRssUserConfig, GetLocationResponse, IgnitionCommand, + CurrentRssUserConfig, GetLocationResponse, IgnitionCommand, RackOperationStatus, }; diff --git a/wicket/src/state/update.rs b/wicket/src/state/update.rs index 591e29e46b6..4108ed0fab1 100644 --- a/wicket/src/state/update.rs +++ b/wicket/src/state/update.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; use slog::Logger; use std::collections::BTreeMap; use std::fmt::Display; -use tufaceous_artifact::KnownArtifactKind; +use tufaceous_artifact::{ArtifactVersion, KnownArtifactKind}; // Represents a version and the signature (optional) associated // with a particular artifact. This allows for multiple versions @@ -30,7 +30,7 @@ use tufaceous_artifact::KnownArtifactKind; // sign is currently only used for RoT artifacts #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ArtifactVersions { - pub version: Version, + pub version: ArtifactVersion, pub sign: Option>, } @@ -122,7 +122,7 @@ impl RackUpdateState { self.artifacts = artifacts; self.artifact_versions.clear(); for a in &mut self.artifacts { - if let Ok(known) = a.id.kind.parse() { + if let Some(known) = a.id.kind.to_known() { self.artifact_versions.entry(known).or_default().push( ArtifactVersions { version: a.id.version.clone(), diff --git a/wicketd/src/update_tracker.rs b/wicketd/src/update_tracker.rs index 5814b1c0964..42d8934624f 100644 --- a/wicketd/src/update_tracker.rs +++ b/wicketd/src/update_tracker.rs @@ -60,6 +60,7 @@ use tokio::sync::oneshot; use tokio::sync::watch; use tokio::task::JoinHandle; use tokio_util::io::StreamReader; +use tufaceous_artifact::ArtifactVersion; use update_common::artifacts::ArtifactIdData; use update_common::artifacts::ArtifactsWithPlan; use update_common::artifacts::ControlPlaneZonesMode; @@ -961,7 +962,7 @@ impl UpdateDriver { "SP board {}, version {} (git commit {})", caboose.board, caboose.version, caboose.git_commit ); - match caboose.version.parse::() { + match caboose.version.parse::() { Ok(version) => { StepSuccess::new((sp_artifact, Some(version))) .with_message(message) @@ -1683,7 +1684,7 @@ struct RotInterrogation { sp: SpIdentifier, // Version reported by the target RoT. artifact_to_apply: ArtifactIdData, - active_version: Option, + active_version: Option, } impl RotInterrogation { @@ -1918,7 +1919,7 @@ impl UpdateContext { c.version, c.git_commit ); - match c.version.parse::() { + match c.version.parse::() { Ok(version) => StepSuccess::new(make_result(Some(version))) .with_message(message) .into(), @@ -2012,7 +2013,7 @@ impl UpdateContext { active_version, }; - match caboose.version.parse::() { + match caboose.version.parse::() { Ok(version) => StepSuccess::new(make_result(Some(version))) .with_message(message) .into(), diff --git a/wicketd/tests/integration_tests/updates.rs b/wicketd/tests/integration_tests/updates.rs index 27687b288cd..194835db7eb 100644 --- a/wicketd/tests/integration_tests/updates.rs +++ b/wicketd/tests/integration_tests/updates.rs @@ -106,7 +106,7 @@ async fn test_updates() { let mut kinds = BTreeSet::new(); let mut installable_kinds = BTreeSet::new(); for artifact in response.artifacts { - kinds.insert(artifact.artifact_id.kind.parse().unwrap()); + kinds.insert(artifact.artifact_id.kind); for installable in artifact.installable { installable_kinds.insert(installable.kind.parse().unwrap()); } @@ -278,10 +278,16 @@ async fn test_installinator_fetch() { let temp_dir = Utf8TempDir::new().expect("temp dir created"); let archive_path = temp_dir.path().join("archive.zip"); + // Test ingestion of an artifact with non-semver versions. This ensures that + // wicketd for v14 and above can handle non-semver versions. + // + // --allow-non-semver can be removed once customer systems are updated to + // v14 and above. let args = tufaceous::Args::try_parse_from([ "tufaceous", "assemble", - "../update-common/manifests/fake.toml", + "../update-common/manifests/fake-non-semver.toml", + "--allow-non-semver", archive_path.as_str(), ]) .expect("args parsed correctly"); diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 75ea7e50216..a3b14ba686f 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -41,6 +41,7 @@ crossbeam-utils = { version = "0.8.20" } crossterm = { version = "0.28.1", features = ["event-stream", "serde"] } crypto-common = { version = "0.1.6", default-features = false, features = ["getrandom", "std"] } curve25519-dalek = { version = "4.1.3", features = ["digest", "legacy_compatibility", "rand_core"] } +daft = { version = "0.1.2", features = ["derive", "newtype-uuid1", "oxnet01", "uuid1"] } digest = { version = "0.10.7", features = ["mac", "oid", "std"] } dropshot = { version = "0.12.0", default-features = false, features = ["usdt-probes"] } ecdsa = { version = "0.16.9", features = ["pem", "signing", "std", "verifying"] } @@ -163,6 +164,7 @@ crossbeam-utils = { version = "0.8.20" } crossterm = { version = "0.28.1", features = ["event-stream", "serde"] } crypto-common = { version = "0.1.6", default-features = false, features = ["getrandom", "std"] } curve25519-dalek = { version = "4.1.3", features = ["digest", "legacy_compatibility", "rand_core"] } +daft = { version = "0.1.2", features = ["derive", "newtype-uuid1", "oxnet01", "uuid1"] } digest = { version = "0.10.7", features = ["mac", "oid", "std"] } dropshot = { version = "0.12.0", default-features = false, features = ["usdt-probes"] } ecdsa = { version = "0.16.9", features = ["pem", "signing", "std", "verifying"] }