From e50cbd2a5db6e2ff1e4ad9df8ca39d3576709b51 Mon Sep 17 00:00:00 2001 From: Tom Kirchner Date: Thu, 14 May 2020 15:08:07 -0700 Subject: [PATCH] updata: allow specifying a start time for waves Previously, wave start times would always be relative to when you ran updata, meaning you'd have to hold off on running updata until you were ready to start a release process. This lets you (optionally) pick any start time for the start of the waves, and the given start_after offsets will be relative to that time. To keep the code and the deployment process simple, this also requires specifying relative offsets in wave definition files, rather than absolute times; we only used relative offsets anyway so that the files were reusable and it was easier to understand the delays between waves, but this codifies that behavior. --- sources/parse-datetime/src/lib.rs | 15 ++++++++--- sources/updater/update_metadata/src/error.rs | 10 +++++--- sources/updater/update_metadata/src/lib.rs | 9 ++++--- sources/updater/updog/src/bin/updata.rs | 27 +++++++++++++++++--- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/sources/parse-datetime/src/lib.rs b/sources/parse-datetime/src/lib.rs index 0b88f7b2477..63986129567 100644 --- a/sources/parse-datetime/src/lib.rs +++ b/sources/parse-datetime/src/lib.rs @@ -47,7 +47,7 @@ pub use error::Error; type Result = std::result::Result; /// Parses a user-specified datetime, either in full RFC 3339 format, or a shorthand like "in 7 -/// days" +/// days" that's taken as an offset from the time the function is run. pub fn parse_datetime(input: &str) -> Result> { // If the user gave an absolute date in a standard format, accept it. let try_dt: std::result::Result, chrono::format::ParseError> = @@ -57,6 +57,15 @@ pub fn parse_datetime(input: &str) -> Result> { return Ok(utc); } + let offset = parse_offset(input)?; + + let now = Utc::now(); + let then = now + offset; + Ok(then) +} + +/// Parses a user-specified datetime offset in the form of a shorthand like "in 7 days". +pub fn parse_offset(input: &str) -> Result { // Otherwise, pull apart a request like "in 5 days" to get an exact datetime. let mut parts: Vec<&str> = input.split_whitespace().collect(); ensure!( @@ -95,9 +104,7 @@ pub fn parse_datetime(input: &str) -> Result> { } }; - let now = Utc::now(); - let then = now + duration; - Ok(then) + Ok(duration) } #[cfg(test)] diff --git a/sources/updater/update_metadata/src/error.rs b/sources/updater/update_metadata/src/error.rs index d394a0afc8c..1225b0a0279 100644 --- a/sources/updater/update_metadata/src/error.rs +++ b/sources/updater/update_metadata/src/error.rs @@ -46,9 +46,13 @@ pub enum Error { #[snafu(display("Migration {} matches regex but missing name", name))] BadRegexName { name: String }, - #[snafu(display("Unable to parse datetime from string '{}': {}", datetime, source))] - BadDateTime { - datetime: String, + #[snafu(display( + "Unable to parse 'start_after' offset from string '{}': {}", + offset, + source + ))] + BadOffset { + offset: String, source: parse_datetime::Error, }, diff --git a/sources/updater/update_metadata/src/lib.rs b/sources/updater/update_metadata/src/lib.rs index 4978e10f899..c9c76ab6086 100644 --- a/sources/updater/update_metadata/src/lib.rs +++ b/sources/updater/update_metadata/src/lib.rs @@ -7,7 +7,7 @@ mod se; use crate::error::Result; use chrono::{DateTime, Duration, Utc}; use lazy_static::lazy_static; -use parse_datetime::parse_datetime; +use parse_datetime::parse_offset; use rand::{thread_rng, Rng}; use regex::Regex; use semver::Version; @@ -293,6 +293,7 @@ impl Manifest { variant: String, arch: String, image_version: Version, + start_at: DateTime, waves: &UpdateWaves, ) -> Result { let matching = self.get_matching_updates(variant, arch, image_version); @@ -311,10 +312,10 @@ impl Manifest { } ); - let start_time = parse_datetime(&wave.start_after).context(error::BadDateTime { - datetime: &wave.start_after, + let offset = parse_offset(&wave.start_after).context(error::BadOffset { + offset: &wave.start_after, })?; - update.waves.insert(seed, start_time); + update.waves.insert(seed, start_at + offset); // Get the appropriate seed from the percentage given // First get the percentage as a decimal, diff --git a/sources/updater/updog/src/bin/updata.rs b/sources/updater/updog/src/bin/updata.rs index 81b720970e2..b1c0bad3c11 100644 --- a/sources/updater/updog/src/bin/updata.rs +++ b/sources/updater/updog/src/bin/updata.rs @@ -8,6 +8,7 @@ mod error; extern crate log; use crate::error::Result; +use chrono::{DateTime, Utc}; use semver::Version; use simplelog::{Config as LogConfig, LevelFilter, TermLogger, TerminalMode}; use snafu::{ErrorCompat, OptionExt, ResultExt}; @@ -143,6 +144,19 @@ struct WaveArgs { // file that contains wave structure #[structopt(short = "w", long = "wave-file", conflicts_with_all = &["bound", "start"])] wave_file: Option, + + // The user can specify the starting point for the the wave offsets, if they don't want them to + // start when they run this tool. + // + // For example, let's say you have a wave with start_after "1 hour" and you run this tool at + // 2020-02-02 02:00. If you don't specify --start-at, it will assume "now", and the wave will + // start 1 hour from then, i.e. 2020-02-02 03:00. + // + // If instead you specify --start-at "2020-02-02T10:00:00Z" then the first wave will start 1 + // hour after that, i.e. 2020-02-02 11:00. + /// Wave offsets will be relative to this RFC3339 datetime, instead of right now + #[structopt(long = "start-at")] + start_at: Option>, } impl WaveArgs { @@ -154,8 +168,15 @@ impl WaveArgs { fs::read_to_string(&wave_file).context(error::ConfigRead { path: &wave_file })?; let waves: UpdateWaves = toml::from_str(&wave_str).context(error::ConfigParse { path: &wave_file })?; - let num_matching = - manifest.set_waves(self.variant, self.arch, self.image_version, &waves)?; + + let start_at = self.start_at.unwrap_or(Utc::now()); + let num_matching = manifest.set_waves( + self.variant, + self.arch, + self.image_version, + start_at, + &waves, + )?; if num_matching > 1 { warn!("Multiple matching updates for wave - this is weird but not a disaster"); @@ -294,7 +315,7 @@ mod tests { let image_version = manifest.updates[0].version.clone(); assert!(manifest - .set_waves(variant, arch, image_version, &waves) + .set_waves(variant, arch, image_version, Utc::now(), &waves) .is_ok()); assert!(manifest.updates[0].waves.len() == 4)