diff --git a/workspaces/Cargo.lock b/workspaces/Cargo.lock index fe4a300eab6..08da782506a 100644 --- a/workspaces/Cargo.lock +++ b/workspaces/Cargo.lock @@ -674,6 +674,8 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_plain 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "snafu 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1280,6 +1282,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "libc" @@ -1357,6 +1362,14 @@ name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "matches" version = "0.1.8" @@ -1669,6 +1682,14 @@ dependencies = [ "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "owning_ref" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.9.0" @@ -2052,6 +2073,16 @@ dependencies = [ "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex-automata" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex-syntax" version = "0.6.12" @@ -2516,6 +2547,11 @@ name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "stable_deref_trait" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "storewolf" version = "0.1.0" @@ -2996,6 +3032,61 @@ dependencies = [ "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tracing" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-attributes 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-log" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-subscriber" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matchers 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-log 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "trust-dns-proto" version = "0.7.4" @@ -3139,6 +3230,21 @@ name = "untrusted" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "update_metadata" +version = "0.1.0" +dependencies = [ + "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "data_store_version 0.1.0", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_plain 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "snafu 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "updog" version = "0.1.0" @@ -3156,9 +3262,14 @@ dependencies = [ "signpost 0.1.0", "simplelog 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "snafu 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "tough 0.1.0", + "tracing 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-subscriber 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "update_metadata 0.1.0", ] [[package]] @@ -3186,6 +3297,11 @@ name = "utf8-cstr" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "utf8-ranges" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "uuid" version = "0.7.4" @@ -3548,6 +3664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lz4 1.23.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43c94a9f09a60017f373020cc93d4291db4cd92b0db64ff25927f27d09dc23d5" "checksum lz4-sys 1.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "20ab022822e9331c58d373acdd6b98085bace058ac6837b8266f213a2fccdafe" "checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +"checksum matchers 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum md5 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d" "checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" @@ -3575,6 +3692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-src 111.6.0+1.1.1d (registry+https://github.com/rust-lang/crates.io-index)" = "b9c2da1de8a7a3f860919c01540b03a6db16de042405a8a07a5e9d0b4b825d9c" "checksum openssl-sys 0.9.51 (registry+https://github.com/rust-lang/crates.io-index)" = "ba24190c8f0805d3bd2ce028f439fe5af1d55882bbe6261bed1dbc93b50dd6b1" +"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum pem 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39eb474073dfddbf7156515344266245d91ce698ddbf15e0498cef22b836f45a" @@ -3616,6 +3734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" +"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum reqwest 0.9.22 (registry+https://github.com/rust-lang/crates.io-index)" = "2c2064233e442ce85c77231ebd67d9eca395207dec2127fe0bbedde4bd29a650" @@ -3658,6 +3777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum structopt 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4f66a4c0ddf7aee4677995697366de0749b0139057342eccbb609b12d0affc" @@ -3694,6 +3814,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" +"checksum tracing 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ff4e4f59e752cb3beb5b61c6d5e11191c7946231ba84faec2902c9efdd8691c5" +"checksum tracing-attributes 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a4263b12c3d3c403274493eb805966093b53214124796552d674ca1dd5d27c2b" +"checksum tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bc913647c520c959b6d21e35ed8fa6984971deca9f0a2fcb8c51207e0c56af1d" +"checksum tracing-log 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9" +"checksum tracing-subscriber 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "192ca16595cdd0661ce319e8eede9c975f227cdaabc4faaefdc256f43d852e45" "checksum trust-dns-proto 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5559ebdf6c2368ddd11e20b11d6bbaf9e46deb803acd7815e93f5a7b4a6d2901" "checksum trust-dns-resolver 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c9992e58dba365798803c0b91018ff6c8d3fc77e06977c4539af2a6bfe0a039" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" @@ -3711,6 +3836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum utf8-cstr 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55bcbb425141152b10d5693095950b51c3745d019363fc2929ffd8f61449b628" +"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" "checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" diff --git a/workspaces/Cargo.toml b/workspaces/Cargo.toml index 3e476a51f68..f86fcd26c3e 100644 --- a/workspaces/Cargo.toml +++ b/workspaces/Cargo.toml @@ -27,6 +27,7 @@ members = [ "updater/signpost", "updater/tough", "updater/tough_schema", + "updater/update_metadata", "updater/updog", "tuftool", diff --git a/workspaces/api/data_store_version/Cargo.toml b/workspaces/api/data_store_version/Cargo.toml index fb3015e24e5..86ac42317fb 100644 --- a/workspaces/api/data_store_version/Cargo.toml +++ b/workspaces/api/data_store_version/Cargo.toml @@ -8,6 +8,9 @@ edition = "2018" lazy_static = "1.2" log = "0.4" regex = "1.1" +serde = { version = "1.0", features = ["derive"] } +serde_plain = "0.3.0" + snafu = "0.5" [build-dependencies] diff --git a/workspaces/api/data_store_version/src/lib.rs b/workspaces/api/data_store_version/src/lib.rs index 01bb5341599..3f57c4dc098 100644 --- a/workspaces/api/data_store_version/src/lib.rs +++ b/workspaces/api/data_store_version/src/lib.rs @@ -11,9 +11,12 @@ It is especially helpful during data store migrations, and is also used for data #[macro_use] extern crate log; +#[macro_use] +extern crate serde_plain; use lazy_static::lazy_static; use regex::Regex; +use serde::{Serialize, Serializer}; use snafu::{OptionExt, ResultExt}; use std::path::Path; use std::path::PathBuf; @@ -92,12 +95,23 @@ impl FromStr for Version { } } +derive_deserialize_from_str!(Version, "Valid data-store version"); + impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "v{}.{}", self.major, self.minor) } } +impl Serialize for Version { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{}.{}", self.major, self.minor)) + } +} + impl Version { #[allow(dead_code)] pub fn new(major: VersionComponent, minor: VersionComponent) -> Self { diff --git a/workspaces/deny.toml b/workspaces/deny.toml index 95e75a7fbe9..ff381f1f6b1 100644 --- a/workspaces/deny.toml +++ b/workspaces/deny.toml @@ -52,6 +52,7 @@ skip = [ { name = "tough", licenses = [] }, { name = "tough_schema", licenses = [] }, { name = "tuftool", licenses = [] }, + { name = "update_metadata", licenses = [] }, { name = "updog", licenses = [] }, { name = "webpki-roots", licenses = [] }, diff --git a/workspaces/updater/update_metadata/Cargo.toml b/workspaces/updater/update_metadata/Cargo.toml new file mode 100644 index 00000000000..92bafc72937 --- /dev/null +++ b/workspaces/updater/update_metadata/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "update_metadata" +version = "0.1.0" +authors = ["Samuel Mendoza-Jonas "] +edition = "2018" +publish = false + +[dependencies] +chrono = { version = "0.4.9", features = ["serde"] } +data_store_version = { path = "../../api/data_store_version" } +rand = "0.7.0" +regex = "1.1" +semver = { version = "0.9.0", features = ["serde"] } +serde = { version = "1.0.100", features = ["derive"] } +serde_json = "1.0.40" +serde_plain = "0.3.0" +snafu = "0.5.0" + +[lib] +name = "update_metadata" +path = "src/lib.rs" diff --git a/workspaces/updater/updog/src/de.rs b/workspaces/updater/update_metadata/src/de.rs similarity index 61% rename from workspaces/updater/updog/src/de.rs rename to workspaces/updater/update_metadata/src/de.rs index b18dc180647..b0da1da1d20 100644 --- a/workspaces/updater/updog/src/de.rs +++ b/workspaces/updater/update_metadata/src/de.rs @@ -1,13 +1,11 @@ use crate::error; use chrono::{DateTime, Utc}; -use data_store_version::Version as DVersion; +use data_store_version::Version as DataVersion; use regex::Regex; -use semver::Version; use serde::{de::Error as _, Deserializer}; use snafu::{ensure, ResultExt}; use std::collections::BTreeMap; use std::fmt; -use std::str::FromStr; /// Converts the bound key to an integer before insertion and catches duplicates pub(crate) fn deserialize_bound<'de, D>( @@ -56,15 +54,15 @@ where deserializer.deserialize_map(Visitor) } -/// Converts the tuple keys to a `DVersion` before insertion and catches duplicates +/// Converts the tuple keys to a `DataVersion` before insertion and catches duplicates pub(crate) fn deserialize_migration<'de, D>( deserializer: D, -) -> Result>, D::Error> +) -> Result>, D::Error> where D: Deserializer<'de>, { fn parse_versions(key: &str) -> Result<(&str, &str), error::Error> { - let r = Regex::new(r"\((?P[0-9.]+),[ ]+(?P[0-9.]+)\)"); + let r = Regex::new(r"\((?P[0-9.]+),[ ]*(?P[0-9.]+)\)"); if let Ok(regex) = r { if let Some(captures) = regex.captures(&key) { @@ -80,11 +78,11 @@ where fn parse_tuple_key( key: String, list: Vec, - map: &mut BTreeMap<(DVersion, DVersion), Vec>, + map: &mut BTreeMap<(DataVersion, DataVersion), Vec>, ) -> Result<(), error::Error> { let (from, to) = parse_versions(&key)?; - if let (Ok(from), Ok(to)) = (DVersion::from_str(from), DVersion::from_str(to)) { + if let (Ok(from), Ok(to)) = (serde_plain::from_str(from), serde_plain::from_str(to)) { ensure!( map.insert((from, to), list).is_none(), error::DuplicateVersionKey { key } @@ -103,7 +101,7 @@ where struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = BTreeMap<(DVersion, DVersion), Vec>; + type Value = BTreeMap<(DataVersion, DataVersion), Vec>; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a map") @@ -123,55 +121,3 @@ where deserializer.deserialize_map(Visitor) } - -/// Converts the key and value into a Version/DVersion pair before insertion and -/// catches duplicates -pub(crate) fn deserialize_datastore_version<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - fn to_versions( - key: String, - value: String, - map: &mut BTreeMap, - ) -> Result<(), error::Error> { - let key_ver = Version::parse(&key); - let value_ver = DVersion::from_str(&value); - match (key_ver, value_ver) { - (Ok(k), Ok(v)) => { - ensure!( - map.insert(k, v).is_none(), - error::DuplicateVersionKey { key } - ); - } - _ => return error::BadMapVersion { key, value }.fail(), - } - Ok(()) - } - - // The rest of this is fitting the above function into serde and doing error type conversion. - struct Visitor; - - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = BTreeMap; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a map") - } - - fn visit_map(self, mut access: M) -> Result - where - M: serde::de::MapAccess<'de>, - { - let mut map = BTreeMap::new(); - while let Some((t_ver, d_ver)) = access.next_entry()? { - to_versions(t_ver, d_ver, &mut map).map_err(M::Error::custom)?; - } - Ok(map) - } - } - - deserializer.deserialize_map(Visitor) -} diff --git a/workspaces/updater/update_metadata/src/error.rs b/workspaces/updater/update_metadata/src/error.rs new file mode 100644 index 00000000000..45fb0fd1013 --- /dev/null +++ b/workspaces/updater/update_metadata/src/error.rs @@ -0,0 +1,84 @@ +#![allow(clippy::default_trait_access)] + +use data_store_version::Version as DataVersion; +use snafu::{Backtrace, Snafu}; + +pub type Result = std::result::Result; + +#[derive(Debug, Snafu)] +#[snafu(visibility = "pub(crate)")] +pub enum Error { + #[snafu(display("Bad bound field: {}", bound_str))] + BadBound { + backtrace: Backtrace, + source: std::num::ParseIntError, + bound_str: String, + }, + + #[snafu(display("Invalid bound start: {}", key))] + BadBoundKey { + source: std::num::ParseIntError, + key: String, + backtrace: Backtrace, + }, + + #[snafu(display("Could not parse datastore version: {}", key))] + BadDataVersion { backtrace: Backtrace, key: String }, + + #[snafu(display("Could not parse image version: {} - {}", key, value))] + BadMapVersion { + backtrace: Backtrace, + key: String, + value: String, + }, + + #[snafu(display("Duplicate key ID: {}", keyid))] + DuplicateKeyId { backtrace: Backtrace, keyid: u64 }, + + #[snafu(display("Duplicate version key: {}", key))] + DuplicateVersionKey { backtrace: Backtrace, key: String }, + + #[snafu(display("Failed to parse updates manifest: {}", source))] + ManifestParse { + source: serde_json::Error, + backtrace: Backtrace, + }, + + #[snafu(display("Missing datastore version in metadata: {:?}", version))] + MissingDataVersion { + backtrace: Backtrace, + version: DataVersion, + }, + + #[snafu(display("Image version missing datastore mapping: {}", version))] + MissingMapping { + backtrace: Backtrace, + version: String, + }, + + #[snafu(display( + "Reached end of migration chain at {} but target is {}", + current, + target + ))] + MissingMigration { + backtrace: Backtrace, + current: DataVersion, + target: DataVersion, + }, + + #[snafu(display("Missing version in metadata: {}", version))] + MissingVersion { + backtrace: Backtrace, + version: String, + }, + + #[snafu(display("This host is not part of any wave"))] + NoWave { backtrace: Backtrace }, + + #[snafu(display("Failed to serialize update information: {}", source))] + UpdateSerialize { + source: serde_json::Error, + backtrace: Backtrace, + }, +} diff --git a/workspaces/updater/update_metadata/src/lib.rs b/workspaces/updater/update_metadata/src/lib.rs new file mode 100644 index 00000000000..db9fd6d1393 --- /dev/null +++ b/workspaces/updater/update_metadata/src/lib.rs @@ -0,0 +1,84 @@ +#![warn(clippy::pedantic)] + +mod de; +pub mod error; +mod se; + +use chrono::{DateTime, Duration, Utc}; +use data_store_version::Version as DataVersion; +use rand::{thread_rng, Rng}; +use semver::Version as SemVer; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::ops::Bound::{Excluded, Included}; + +pub const MAX_SEED: u32 = 2048; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Images { + pub boot: String, + pub root: String, + pub hash: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Update { + pub flavor: String, + pub arch: String, + pub version: SemVer, + pub max_version: SemVer, + #[serde(deserialize_with = "de::deserialize_bound")] + pub waves: BTreeMap>, + pub images: Images, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Manifest { + pub updates: Vec, + #[serde(deserialize_with = "de::deserialize_migration")] + #[serde(serialize_with = "se::serialize_migration")] + pub migrations: BTreeMap<(DataVersion, DataVersion), Vec>, + pub datastore_versions: BTreeMap, +} + +impl Update { + pub fn update_wave(&self, seed: u32) -> Option<&DateTime> { + self.waves + .range((Included(0), Included(seed))) + .last() + .map(|(_, wave)| wave) + } + + pub fn update_ready(&self, seed: u32) -> bool { + // Has this client's wave started + if let Some(wave) = self.update_wave(seed) { + return *wave <= Utc::now(); + } + + // Alternately have all waves passed + if let Some((_, wave)) = self.waves.iter().last() { + return *wave <= Utc::now(); + } + + // Or there are no waves + true + } + + pub fn jitter(&self, seed: u32) -> Option> { + let prev = self.update_wave(seed); + let next = self + .waves + .range((Excluded(seed), Excluded(MAX_SEED))) + .next() + .map(|(_, wave)| wave); + if let (Some(start), Some(end)) = (prev, next) { + if Utc::now() < *end { + let mut rng = thread_rng(); + if let Some(range) = end.timestamp().checked_sub(start.timestamp()) { + return Some(*start + Duration::seconds(rng.gen_range(1, range))); + } + } + } + None + } +} diff --git a/workspaces/updater/update_metadata/src/se.rs b/workspaces/updater/update_metadata/src/se.rs new file mode 100644 index 00000000000..f66b34bae8f --- /dev/null +++ b/workspaces/updater/update_metadata/src/se.rs @@ -0,0 +1,29 @@ +use data_store_version::Version as DataVersion; +use serde::ser::Error as _; +use serde::{Serialize, Serializer}; +use std::collections::BTreeMap; + +pub(crate) fn serialize_migration( + value: &BTreeMap<(DataVersion, DataVersion), Vec>, + serializer: S, +) -> Result +where + S: Serializer, +{ + let mut map = BTreeMap::new(); + for ((from, to), val) in value { + let key = format!( + "({},{})", + serde_plain::to_string(&from).map_err(|e| S::Error::custom(format!( + "Could not serialize 'from' version: {}", + e + )))?, + serde_plain::to_string(&to).map_err(|e| S::Error::custom(format!( + "Could not serialize 'to' version: {}", + e + )))?, + ); + map.insert(key, val); + } + map.serialize(serializer) +} diff --git a/workspaces/updater/updog/Cargo.toml b/workspaces/updater/updog/Cargo.toml index 9340071d757..0132a45b0b7 100644 --- a/workspaces/updater/updog/Cargo.toml +++ b/workspaces/updater/updog/Cargo.toml @@ -13,7 +13,7 @@ lz4 = "1.23.1" rand = "0.7.0" regex = "1.1" semver = "0.9.0" -serde = "1.0.100" +serde = { version = "1.0.100", features = ["derive"] } serde_json = "1.0.40" serde_plain = "0.3.0" signpost = { path = "../signpost" } @@ -22,3 +22,10 @@ snafu = "0.5.0" time = "0.1" toml = "0.5.1" tough = { path = "../tough" } +tracing = "0.1" +tracing-subscriber = "0.1" +update_metadata = { path = "../update_metadata" } +structopt = "0.3" + +[dev-dependencies] +tempfile = "3.1.0" diff --git a/workspaces/updater/updog/src/bin/updata.rs b/workspaces/updater/updog/src/bin/updata.rs new file mode 100644 index 00000000000..92a77a77292 --- /dev/null +++ b/workspaces/updater/updog/src/bin/updata.rs @@ -0,0 +1,619 @@ +#![deny(rust_2018_idioms)] +#![warn(clippy::pedantic)] + +#[path = "../error.rs"] +mod error; + +#[macro_use] +extern crate log; + +use crate::error::Result; +use chrono::{DateTime, Utc}; +use data_store_version::Version as DataVersion; +use semver::Version as SemVer; +use snafu::{ensure, ErrorCompat, OptionExt, ResultExt}; +use std::collections::BTreeMap; +use std::fs::{self, File}; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; +use update_metadata::{Images, Manifest, Update}; + +#[derive(Debug, StructOpt)] +struct GeneralArgs { + // metadata file to create/modify + file: PathBuf, +} + +#[derive(Debug, StructOpt)] +struct AddUpdateArgs { + // metadata file to create/modify + file: PathBuf, + + // image 'flavor', eg. 'aws-k8s' + #[structopt(short = "f", long = "flavor")] + flavor: String, + + // image version + #[structopt(short = "v", long = "version")] + image_version: SemVer, + + // architecture image is built for + #[structopt(short = "a", long = "arch")] + arch: String, + + // corresponding datastore version for this image + #[structopt(short = "d", long = "data-version")] + datastore_version: DataVersion, + + // maximum valid version + #[structopt(short = "m", long = "max-version")] + max_version: Option, + + // root image target name + #[structopt(short = "r", long = "root")] + root: String, + + // boot image target name + #[structopt(short = "b", long = "boot")] + boot: String, + + // verity "hash" image target name + #[structopt(short = "h", long = "hash")] + hash: String, +} + +impl AddUpdateArgs { + fn run(self) -> Result<()> { + let mut manifest: Manifest = match load_file(&self.file) { + Ok(m) => m, + _ => Manifest::default(), // TODO only if EEXIST + }; + + let max_version = if let Some(version) = self.max_version { + version + } else { + // Default to greater of the current max version and this version + if let Some(update) = manifest.updates.first() { + std::cmp::max(&self.image_version, &update.max_version).clone() + } else { + self.image_version.clone() + } + }; + let update = Update { + flavor: self.flavor, + arch: self.arch, + version: self.image_version.clone(), + max_version, + images: Images { + root: self.root, + boot: self.boot, + hash: self.hash, + }, + waves: BTreeMap::new(), + }; + manifest + .datastore_versions + .insert(self.image_version, self.datastore_version); + update_max_version( + &mut manifest, + &update.max_version, + Some(&update.arch), + Some(&update.flavor), + ); + info!("Maximum version set to {}", &update.max_version); + manifest.updates.push(update); + write_file(&self.file, &manifest) + } +} + +#[derive(Debug, StructOpt)] +struct RemoveUpdateArgs { + // metadata file to create/modify + file: PathBuf, + + // image 'flavor', eg. 'aws-k8s' + #[structopt(short = "l", long = "flavor")] + flavor: String, + + // image version + #[structopt(short = "v", long = "version")] + image_version: SemVer, + + // architecture image is built for + #[structopt(short = "a", long = "arch")] + arch: String, + + // Whether to clean up datastore mappings that no longer reference an + // existing update. Migration paths for such datastore versions are + // preserved. + // This should _only_ be used if there are no existing users of the + // specified Thar image version. + #[structopt(short, long)] + cleanup: bool, +} + +impl RemoveUpdateArgs { + fn run(&self) -> Result<()> { + let mut manifest: Manifest = load_file(&self.file)?; + // Remove any update that exactly matches the specified update + manifest.updates.retain(|update| { + update.arch != self.arch + || update.flavor != self.flavor + || update.version != self.image_version + }); + if self.cleanup { + let remaining: Vec<&Update> = manifest + .updates + .iter() + .filter(|update| update.version == self.image_version) + .collect(); + if remaining.is_empty() { + manifest.datastore_versions.remove(&self.image_version); + } else { + info!( + "Cleanup skipped; {} {} updates remain", + remaining.len(), + self.image_version + ); + } + } + // Note: We don't revert the maximum version on removal + write_file(&self.file, &manifest)?; + if let Some(current) = manifest.updates.first() { + info!( + "Update {}-{}-{} removed. Current maximum version: {}", + self.arch, self.flavor, self.image_version, current.version + ); + } else { + info!( + "Update {}-{}-{} removed. No remaining updates", + self.arch, self.flavor, self.image_version + ); + } + Ok(()) + } +} + +#[derive(Debug, StructOpt)] +struct WaveArgs { + // metadata file to create/modify + file: PathBuf, + + // image 'flavor', eg. 'aws-k8s' + #[structopt(short = "l", long = "flavor")] + flavor: String, + + // image version + #[structopt(short = "v", long = "version")] + image_version: SemVer, + + // architecture image is built for + #[structopt(short = "a", long = "arch")] + arch: String, + + // start bound id for this wave (0 <= x < 2048) + #[structopt(short = "b", long = "bound-id")] + bound: u32, + + // start time for this wave + #[structopt(short = "s", long = "start-time")] + start: Option>, +} + +impl WaveArgs { + fn validate(updates: &[Update]) -> Result<()> { + for update in updates { + let mut waves = update.waves.iter().peekable(); + while let Some(wave) = waves.next() { + if let Some(next) = waves.peek() { + ensure!( + wave.1 < next.1, + error::WavesUnordered { + wave: *wave.0, + next: *next.0 + } + ); + } + } + } + Ok(()) + } + + fn add(self) -> Result<()> { + let mut manifest: Manifest = load_file(&self.file)?; + let matching: Vec<&mut Update> = manifest + .updates + .iter_mut() + // Find the update that exactly matches the specified update + .filter(|update| { + update.arch == self.arch + && update.flavor == self.flavor + && update.version == self.image_version + }) + .collect(); + if matching.len() > 1 { + warn!("Multiple matching updates for wave - this is weird but not a disaster"); + } + let start = self.start.context(error::WaveStartArg)?; + for update in matching { + update.waves.insert(self.bound, start); + } + Self::validate(&manifest.updates)?; + write_file(&self.file, &manifest) + } + + fn remove(self) -> Result<()> { + let mut manifest: Manifest = load_file(&self.file)?; + let matching: Vec<&mut Update> = manifest + .updates + .iter_mut() + .filter(|update| { + update.arch == self.arch + && update.flavor == self.flavor + && update.version == self.image_version + }) + .collect(); + for update in matching { + update.waves.remove(&self.bound); + } + write_file(&self.file, &manifest) + } +} + +#[derive(Debug, StructOpt)] +struct MigrationArgs { + // metadata file to create/modify + file: PathBuf, + + // starting datastore version + #[structopt(short = "f", long = "from")] + from: DataVersion, + + // target datastore version + #[structopt(short = "t", long = "to")] + to: DataVersion, + + // whether to append to or replace any existing migration list + #[structopt(short, long)] + append: bool, + + // migration names + migrations: Vec, +} + +impl MigrationArgs { + fn add(self) -> Result<()> { + let mut manifest: Manifest = load_file(&self.file)?; + // If --append is set, append the new migrations to the existing vec. + if self.append && manifest.migrations.contains_key(&(self.from, self.to)) { + let migrations = manifest.migrations.get_mut(&(self.from, self.to)).context( + error::MigrationMutable { + from: self.from, + to: self.to, + }, + )?; + migrations.extend_from_slice(&self.migrations); + // Otherwise just overwrite the existing migrations + } else { + manifest + .migrations + .insert((self.from, self.to), self.migrations); + } + write_file(&self.file, &manifest) + } + + fn remove(self) -> Result<()> { + let mut manifest: Manifest = load_file(&self.file)?; + ensure!( + manifest.migrations.contains_key(&(self.from, self.to)), + error::MigrationNotPresent { + from: self.from, + to: self.to, + } + ); + manifest.migrations.remove(&(self.from, self.to)); + write_file(&self.file, &manifest) + } +} + +#[derive(Debug, StructOpt)] +struct MaxVersionArgs { + // metadata file to create/modify + file: PathBuf, + + // maximum valid version + #[structopt(short, long)] + max_version: SemVer, +} + +impl MaxVersionArgs { + fn run(self) -> Result<()> { + let mut manifest: Manifest = load_file(&self.file)?; + update_max_version(&mut manifest, &self.max_version, None, None); + write_file(&self.file, &manifest) + } +} + +#[derive(Debug, StructOpt)] +struct MappingArgs { + // metadata file to create/modify + file: PathBuf, + + #[structopt(short, long)] + image_version: SemVer, + + #[structopt(short, long)] + data_version: DataVersion, +} + +impl MappingArgs { + fn run(self) -> Result<()> { + let mut manifest: Manifest = load_file(&self.file)?; + let version = self.image_version.clone(); + let old = manifest + .datastore_versions + .insert(self.image_version, self.data_version); + if let Some(old) = old { + warn!( + "Warning: New mapping ({},{}) replaced old mapping ({},{})", + version, self.data_version, version, old + ); + } + write_file(&self.file, &manifest) + } +} + +#[derive(Debug, StructOpt)] +#[structopt(rename_all = "kebab-case")] +enum Command { + /// Create an empty manifest + Init(GeneralArgs), + /// Add a new update to the manifest, not including wave information + AddUpdate(AddUpdateArgs), + /// Add a (bound_id, time) wave to an existing update + AddWave(WaveArgs), + /// Add one or more migrations to a (from, to) datastore mapping + AddMigration(MigrationArgs), + /// Add a image_version:data_store_version mapping to the manifest + AddVersionMapping(MappingArgs), + /// Set the global maximum image version + SetMaxVersion(MaxVersionArgs), + /// Remove an update from the manifest, including wave information + RemoveUpdate(RemoveUpdateArgs), + /// Remove all migrations for a (from, to) datastore mapping + RemoveMigrations(MigrationArgs), + /// Remove a (bound_id, time) wave from an update + RemoveWave(WaveArgs), + /// Validate a manifest file, but make no changes + Validate(GeneralArgs), +} + +fn load_file(path: &Path) -> Result { + let file = File::open(path).context(error::ManifestRead { path })?; + serde_json::from_reader(file).context(error::ManifestParse) +} + +fn write_file(path: &Path, manifest: &Manifest) -> Result<()> { + let manifest = serde_json::to_string_pretty(&manifest).context(error::UpdateSerialize)?; + fs::write(path, &manifest).context(error::ConfigWrite { path })?; + Ok(()) +} + +/// Update the maximum version for all updates that optionally match the +/// architecture and flavor of some new update. +fn update_max_version( + manifest: &mut Manifest, + version: &SemVer, + arch: Option<&str>, + flavor: Option<&str>, +) { + let matching: Vec<&mut Update> = manifest + .updates + .iter_mut() + .filter(|update| match (arch, flavor) { + (Some(arch), Some(flavor)) => update.arch == arch && update.flavor == flavor, + (Some(arch), None) => update.arch == arch, + (None, Some(flavor)) => update.flavor == flavor, + _ => true, + }) + .collect(); + for u in matching { + u.max_version = version.clone(); + } +} + +fn main_inner() -> Result<()> { + match Command::from_args() { + Command::Init(args) => write_file(&args.file, &Manifest::default()), + Command::AddUpdate(args) => args.run(), + Command::AddWave(args) => args.add(), + Command::AddMigration(args) => args.add(), + Command::AddVersionMapping(args) => args.run(), + Command::SetMaxVersion(args) => args.run(), + Command::RemoveUpdate(args) => args.run(), + Command::RemoveWave(args) => args.remove(), + Command::RemoveMigrations(args) => args.remove(), + Command::Validate(args) => match load_file(&args.file) { + Ok(_) => Ok(()), + Err(e) => Err(e), + }, + } +} + +fn main() -> ! { + std::process::exit(match main_inner() { + Ok(()) => 0, + Err(err) => { + error!("{}", err); + if let Some(var) = std::env::var_os("RUST_BACKTRACE") { + if var != "0" { + if let Some(backtrace) = err.backtrace() { + error!("\n{:?}", backtrace); + } + } + } + 1 + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::Duration; + use std::str::FromStr; + use tempfile::NamedTempFile; + + #[test] + fn max_versions() -> Result<()> { + let tmpfd = NamedTempFile::new().context(error::TmpFileCreate)?; + AddUpdateArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.3").unwrap(), + max_version: Some(SemVer::parse("1.2.3").unwrap()), + datastore_version: DataVersion::from_str("1.0").unwrap(), + boot: String::from("boot"), + root: String::from("root"), + hash: String::from("hash"), + } + .run() + .unwrap(); + AddUpdateArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.5").unwrap(), + max_version: Some(SemVer::parse("1.2.3").unwrap()), + datastore_version: DataVersion::from_str("1.0").unwrap(), + boot: String::from("boot"), + root: String::from("root"), + hash: String::from("hash"), + } + .run() + .unwrap(); + AddUpdateArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.4").unwrap(), + max_version: Some(SemVer::parse("1.2.4").unwrap()), + datastore_version: DataVersion::from_str("1.0").unwrap(), + boot: String::from("boot"), + root: String::from("root"), + hash: String::from("hash"), + } + .run() + .unwrap(); + + let m: Manifest = load_file(tmpfd.path())?; + for u in m.updates { + assert!(u.max_version == SemVer::parse("1.2.4").unwrap()); + } + Ok(()) + } + + #[test] + fn datastore_mapping() -> Result<()> { + let tmpfd = NamedTempFile::new().context(error::TmpFileCreate)?; + AddUpdateArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.3").unwrap(), + max_version: Some(SemVer::parse("1.2.3").unwrap()), + datastore_version: DataVersion::from_str("1.0").unwrap(), + boot: String::from("boot"), + root: String::from("root"), + hash: String::from("hash"), + } + .run() + .unwrap(); + AddUpdateArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.5").unwrap(), + max_version: Some(SemVer::parse("1.2.3").unwrap()), + datastore_version: DataVersion::from_str("1.1").unwrap(), + boot: String::from("boot"), + root: String::from("root"), + hash: String::from("hash"), + } + .run() + .unwrap(); + AddUpdateArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.4").unwrap(), + max_version: Some(SemVer::parse("1.2.4").unwrap()), + datastore_version: DataVersion::from_str("1.0").unwrap(), + boot: String::from("boot"), + root: String::from("root"), + hash: String::from("hash"), + } + .run() + .unwrap(); + + // TODO this needs to test against ARCH and FLAVOR not being considered + RemoveUpdateArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.4").unwrap(), + cleanup: true, + } + .run() + .unwrap(); + + let m: Manifest = load_file(tmpfd.path())?; + assert!(m + .datastore_versions + .contains_key(&SemVer::parse("1.2.3").unwrap())); + Ok(()) + } + + #[test] + fn ordered_waves() -> Result<()> { + let tmpfd = NamedTempFile::new().context(error::TmpFileCreate)?; + AddUpdateArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.3").unwrap(), + max_version: Some(SemVer::parse("1.2.3").unwrap()), + datastore_version: DataVersion::from_str("1.0").unwrap(), + boot: String::from("boot"), + root: String::from("root"), + hash: String::from("hash"), + } + .run() + .unwrap(); + + WaveArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.3").unwrap(), + bound: 1024, + start: Some(Utc::now()), + } + .add() + .unwrap(); + + assert!(WaveArgs { + file: PathBuf::from(tmpfd.path()), + flavor: String::from("yum"), + arch: String::from("x86_64"), + image_version: SemVer::parse("1.2.3").unwrap(), + bound: 1536, + start: Some(Utc::now() - Duration::hours(1)), + } + .add() + .is_err()); + + Ok(()) + } +} diff --git a/workspaces/updater/updog/src/error.rs b/workspaces/updater/updog/src/error.rs index 6068e85fc76..b2eb88e1fa4 100644 --- a/workspaces/updater/updog/src/error.rs +++ b/workspaces/updater/updog/src/error.rs @@ -117,6 +117,13 @@ pub(crate) enum Error { backtrace: Backtrace, }, + #[snafu(display("Failed to read manifest file {}: {}", path.display(), source))] + ManifestRead { + path: PathBuf, + source: std::io::Error, + backtrace: Backtrace, + }, + #[snafu(display("Metadata error: {}", source))] Metadata { source: tough::error::Error, @@ -133,6 +140,20 @@ pub(crate) enum Error { #[snafu(display("Migration not found in image: {:?}", name))] MigrationNotLocal { backtrace: Backtrace, name: PathBuf }, + #[snafu(display("Unable to get mutable reference to ({},{}) migrations", from, to))] + MigrationMutable { + backtrace: Backtrace, + from: DataVersion, + to: DataVersion, + }, + + #[snafu(display("Migration ({},{}) not present in manifest", from, to))] + MigrationNotPresent { + backtrace: Backtrace, + from: DataVersion, + to: DataVersion, + }, + #[snafu(display("Missing datastore version in metadata: {:?}", version))] MissingDataVersion { backtrace: Backtrace, @@ -244,11 +265,11 @@ pub(crate) enum Error { backtrace: Backtrace, }, - #[snafu(display("Update wave has been missed"))] - WaveMissed { backtrace: Backtrace }, + #[snafu(display("--start-time