-
-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathfeature_gates.rs
More file actions
229 lines (198 loc) · 8.26 KB
/
Copy pathfeature_gates.rs
File metadata and controls
229 lines (198 loc) · 8.26 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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
use std::{collections::HashMap, str::FromStr};
use snafu::{OptionExt as _, ResultExt as _, Snafu};
use winnow::{
Parser as _,
ascii::{alphanumeric0, alphanumeric1, digit1, space0, space1},
combinator::{delimited, separated, separated_pair},
};
use crate::client::{
Client, CreateRawRequestSnafu, ParseFeatureGateSnafu, PerformRawRequestSnafu, Result,
};
impl Client {
/// Retrieves and parses all feature gates via a raw request to the `/metrics` endpoint.
///
/// This list of feature gates in combination with [`KubeClient::apiserver_version`] can be used
/// to enable gated behaviour.
pub async fn get_feature_gates(&self) -> Result<Vec<FeatureGate>> {
let request =
http::Request::get("/metrics")
.body(vec![])
.context(CreateRawRequestSnafu {
method: http::Method::GET,
})?;
let response = self
.client
.request_text(request)
.await
.context(PerformRawRequestSnafu)?;
response
.lines()
.filter(|l| l.starts_with("kubernetes_feature_enabled"))
.map(FeatureGate::from_str)
.collect::<Result<Vec<FeatureGate>, _>>()
.map_err(|error| ParseFeatureGateSnafu { error }.build())
}
/// Retrieves enabled feature gates.
///
/// Uses [`Client::get_feature_gates`] internally.
pub async fn get_enabled_feature_gates(&self) -> Result<Vec<FeatureGate>> {
let feature_gates = self.get_feature_gates().await?;
let enabled_feature_gates = feature_gates.into_iter().filter(|fg| fg.enabled).collect();
Ok(enabled_feature_gates)
}
/// Retrieves disabled feature gates.
///
/// Uses [`Client::get_feature_gates`] internally.
pub async fn get_disabled_feature_gates(&self) -> Result<Vec<FeatureGate>> {
let feature_gates = self.get_feature_gates().await?;
let disabled_feature_gates = feature_gates.into_iter().filter(|fg| !fg.enabled).collect();
Ok(disabled_feature_gates)
}
}
#[derive(Debug, Snafu)]
enum FeatureGateParseError {
#[snafu(display("required feature gate metric label missing, expected 'name' and 'stage'"))]
MissingLabel,
#[snafu(display("failed to parse feature stage"))]
ParseStage { source: strum::ParseError },
#[snafu(display("failed to parse string as integer"))]
ParseInt { source: std::num::ParseIntError },
}
#[derive(Debug)]
pub struct FeatureGate {
/// The name of the feature gate, eg. `AllowDNSOnlyNodeCSR`.
pub name: String,
/// In which stage the feature is, eg. `ALPHA`.
pub stage: FeatureStage,
/// Whether the feature is enabled or disabled.
pub enabled: bool,
}
impl FromStr for FeatureGate {
type Err = String;
fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
Self::parse_from_metric
.parse(s)
.map_err(|err| err.to_string())
}
}
impl FeatureGate {
/// Parses a feature gate from the line-based `/metrics` response.
///
/// This function expects feature gates to be passed as individual lines.
fn parse_from_metric(input: &mut &str) -> winnow::Result<Self> {
(
Self::parse_metric_name,
delimited('{', Self::parse_labels, '}'),
// At least one space after the metric and the value
space1,
// The counter value
digit1,
)
.try_map(|((), mut kv_pairs, _, count)| {
let name = kv_pairs
.remove("name")
.context(MissingLabelSnafu)?
.to_owned();
let stage = kv_pairs
.remove("stage")
.context(MissingLabelSnafu)?
.parse()
.context(ParseStageSnafu)?;
let count = count.parse::<u8>().context(ParseIntSnafu)?;
// TODO (@Techassi): Potentially replace this with TryFrom instead.
// The TryFrom<u8> impl for bool is only available in Rust 1.95+
let enabled = count != 0;
Ok::<Self, FeatureGateParseError>(Self {
name,
stage,
enabled,
})
})
.parse_next(input)
}
/// Parses (and removes) the well-known, static metric name.
fn parse_metric_name(input: &mut &str) -> winnow::Result<()> {
"kubernetes_feature_enabled".void().parse_next(input)
}
/// Parses and collects a list of labels contained within `{` and `}`.
fn parse_labels<'s>(input: &mut &'s str) -> winnow::Result<HashMap<&'s str, &'s str>> {
separated(
// We expect at least two labels: name and stage
2..,
// The value of the label can be empty
separated_pair(alphanumeric1, '=', ('"', alphanumeric0, '"'))
.map(|(key, (_, value, _))| (key, value)),
// There might be spaces between labels (separated by comma)
(',', space0),
)
.parse_next(input)
}
}
/// A feature can be in one of four different stages.
///
/// See the [list of feature gates] and [feature stages] in the official documentation.
///
/// [list of feature gates]: https://v1-35.docs.kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-gates
/// [feature stages]: https://v1-35.docs.kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages
#[derive(Debug, strum::Display, strum::EnumString)]
#[strum(serialize_all = "UPPERCASE")]
pub enum FeatureStage {
/// An Alpha feature.
///
/// - Disabled by default.
/// - Might be buggy. Enabling the feature may expose bugs.
/// - Support for feature may be dropped at any time without notice.
/// - The API may change in incompatible ways in a later software release without notice.
/// - Recommended for use only in short-lived testing clusters, due to increased risk of bugs
/// and lack of long-term support.
///
/// Taken from the Kubernetes documentation.
Alpha,
/// A Beta feature.
///
/// - Usually enabled by default. Beta API groups are [disabled by default].
/// - The feature is well tested. Enabling the feature is considered safe.
/// - Support for the overall feature will not be dropped, though details may change.
/// - The schema and/or semantics of objects may change in incompatible ways in a subsequent
/// beta or stable release. When this happens, we will provide instructions for migrating to
/// the next version. This may require deleting, editing, and re-creating API objects. The
/// editing process may require some thought. This may require downtime for applications that
/// rely on the feature.
/// - Recommended for only non-business-critical uses because of potential for incompatible
/// changes in subsequent releases. If you have multiple clusters that can be upgraded
/// independently, you may be able to relax this restriction.
///
/// Taken from the Kubernetes documentation.
Beta,
/// A General Availability feature.
///
/// - The feature is always enabled; you cannot disable it.
/// - The corresponding feature gate is no longer needed.
/// - Stable versions of features will appear in released software for many subsequent versions.
///
/// Taken from the Kubernetes documentation.
#[strum(serialize = "")]
GeneralAvailability,
/// A feature is deprecated.
///
/// The official documentation doesn't explain this stage at all, but it exists (in metrics).
Deprecated,
}
#[cfg(test)]
mod tests {
use crate::client::{initialize_operator, tests::test_cluster_info_opts};
#[tokio::test]
#[ignore = "Tests depending on Kubernetes are not ran by default"]
async fn k8s_test_feature_gates() {
let client = initialize_operator(None, &test_cluster_info_opts())
.await
.expect("KUBECONFIG variable must be configured.");
let feature_gates = client
.get_feature_gates()
.await
.expect("list of feature gates must parse");
for feature_gate in feature_gates {
println!("{feature_gate:?}");
}
}
}