-
-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathmod.rs
More file actions
183 lines (159 loc) · 6.11 KB
/
Copy pathmod.rs
File metadata and controls
183 lines (159 loc) · 6.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
use chrono::{DateTime, Utc};
use futures::FutureExt;
use snafu::{ResultExt, Snafu};
use stackable_shared::time::Duration;
use tokio::select;
use tracing::{Level, instrument};
/// Available options to configure a [`EndOfSupportChecker`].
///
/// Additionally, this struct can be used as operator CLI arguments. This functionality is only
/// available if the feature `clap` is enabled.
#[cfg_attr(feature = "clap", derive(clap::Args))]
#[derive(Debug, PartialEq, Eq)]
pub struct EndOfSupportOptions {
/// The end-of-support check mode. Currently, only "offline" is supported.
#[cfg_attr(feature = "clap", arg(
long = "eos-check-mode",
env = "EOS_CHECK_MODE",
default_value_t = EndOfSupportCheckMode::default(),
value_enum
))]
pub check_mode: EndOfSupportCheckMode,
/// The interval in which the end-of-support check should run.
#[cfg_attr(feature = "clap", arg(
long = "eos-interval",
env = "EOS_INTERVAL",
default_value_t = Self::default_interval()
))]
pub interval: Duration,
/// If the end-of-support check should be disabled entirely.
#[cfg_attr(feature = "clap", arg(long = "eos-disabled", env = "EOS_DISABLED"))]
pub disabled: bool,
/// The support duration (how long the operator should be considered supported after
/// it's built-date).
///
/// This field is currently not exposed as a CLI argument or environment variable.
#[cfg_attr(feature = "clap", arg(skip = Self::default_support_duration()))]
pub support_duration: Duration,
}
impl EndOfSupportOptions {
fn default_interval() -> Duration {
if cfg!(debug_assertions) {
Duration::from_secs(30)
} else {
Duration::from_days_unchecked(1)
}
}
fn default_support_duration() -> Duration {
Duration::from_days_unchecked(365)
}
}
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum EndOfSupportCheckMode {
#[default]
Offline,
}
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("failed to parse built-time"))]
ParseBuiltTime { source: chrono::ParseError },
}
pub struct EndOfSupportChecker {
built_datetime: DateTime<Utc>,
eos_datetime: DateTime<Utc>,
interval: Duration,
disabled: bool,
}
impl EndOfSupportChecker {
/// Creates and returns a new end-of-support checker.
///
/// - The `built_time` string indicates when a specific operator was built. It is recommended
/// to use `built`'s `BUILT_TIME_UTC` constant.
/// - The `options` allow customizing the checker. It is recommended to use values provided by
/// CLI args, see [`EndOfSupportOptions`], [`MaintenanceOptions`](crate::cli::MaintenanceOptions),
/// and [`RunArguments`](crate::cli::RunArguments).
pub fn new(built_time: &str, options: EndOfSupportOptions) -> Result<Self, Error> {
let EndOfSupportOptions {
interval,
support_duration,
disabled,
..
} = options;
// Parse the built-time from the RFC2822-encoded string when this is compiled as a release
// build. If this is a debug/dev build, use the current datetime instead.
let built_datetime = if cfg!(debug_assertions) {
Utc::now()
} else {
DateTime::parse_from_rfc2822(built_time)
.context(ParseBuiltTimeSnafu)?
.to_utc()
};
// Add the support duration to the built date. This marks the end-of-support date.
let eos_datetime = built_datetime + *support_duration;
Ok(Self {
built_datetime,
eos_datetime,
interval,
disabled,
})
}
/// Run the end-of-support checker.
///
/// It is recommended to run the end-of-support checker via [`futures::try_join!`] or
/// [`tokio::join`] alongside other futures (eg. for controllers).
pub async fn run<F>(self, shutdown_signal: F)
where
F: Future<Output = ()>,
{
// Immediately return if the end-of-support checker is disabled.
if self.disabled {
return;
}
let mut interval = tokio::time::interval(self.interval.into());
let shutdown_signal = shutdown_signal.fuse();
tokio::pin!(shutdown_signal);
loop {
select! {
// We used a biased polling strategy to always check if a
// shutdown signal was received before polling the EoS check
// interval.
biased;
_ = &mut shutdown_signal => {
tracing::trace!("received shutdown signal");
break;
}
// The first tick ticks immediately.
_ = interval.tick() => {
let now = Utc::now();
tracing::info_span!(
"checking end-of-support state",
eos.interval = self.interval.to_string(),
eos.now = now.to_rfc3339(),
);
// Continue the loop and wait for the next tick to run the
// check again.
if now <= self.eos_datetime {
continue;
}
self.emit_warning(now);
}
}
}
}
/// Emits the end-of-support warning.
#[instrument(level = Level::DEBUG, skip(self))]
fn emit_warning(&self, now: DateTime<Utc>) {
let built_datetime = self.built_datetime.to_rfc3339();
let build_age = Duration::try_from(now - self.built_datetime)
.expect("time delta of now and built datetime must not be less than 0")
.to_string();
tracing::warn!(
eos.built.datetime = built_datetime,
eos.build.age = build_age,
"This operator version was built on {built_datetime} ({build_age} ago) and may have reached end-of-support. \
Running unsupported versions may contain security vulnerabilities. \
Please upgrade to a supported version as soon as possible."
);
}
}