-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathmod.rs
More file actions
281 lines (248 loc) · 9.65 KB
/
Copy pathmod.rs
File metadata and controls
281 lines (248 loc) · 9.65 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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//! Controller-level vocabulary: the [`ValidatedCluster`] type and the `build` / `validate`
//! sub-modules.
use std::{collections::BTreeMap, str::FromStr};
// Re-exported so the rest of the controller refers to `crate::controller::RoleGroupName`.
pub use stackable_operator::v2::types::operator::RoleGroupName;
use stackable_operator::{
commons::{
affinity::StackableAffinity,
product_image_selection::ResolvedProductImage,
resources::{NoRuntimeLimits, Resources},
},
k8s_openapi::api::{
apps::v1::DaemonSet,
core::v1::{ConfigMap, Service},
},
kube::{Resource as KubeResource, api::ObjectMeta},
kvp::Labels,
shared::time::Duration,
v2::{
HasName, HasUid, NameIsValidLabelValue,
kvp::label::{recommended_labels, role_group_selector, role_selector},
role_group_utils::ResourceNames,
role_utils::{GenericCommonConfig, RoleGroupConfig},
types::{
kubernetes::{NamespaceName, Uid},
operator::{
ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, RoleName,
},
},
},
};
use crate::{
crd::{
APP_NAME, OPERATOR_NAME, OpaConfig, OpaConfigOverrides, OpaRole, OpaStorageConfig,
user_info_fetcher, v1alpha2,
},
opa_controller::OPA_CONTROLLER_NAME,
};
pub mod build;
pub mod validate;
/// The validated [`v1alpha2::OpaCluster`].
///
/// The output of the validate step: config fragments and `configOverrides` merged and validated
/// for every role group.
pub struct ValidatedCluster {
/// Object metadata (name, namespace, UID) of the owning `OpaCluster`, built from the validated
/// fields below. Lets [`ValidatedCluster`] implement [`KubeResource`] so the build steps can
/// derive owner references and object metadata without touching the raw `OpaCluster` spec.
metadata: ObjectMeta,
pub name: ClusterName,
pub namespace: NamespaceName,
pub uid: Uid,
pub image: ResolvedProductImage,
pub cluster_config: ValidatedClusterConfig,
pub role_group_configs: BTreeMap<OpaRole, BTreeMap<RoleGroupName, OpaRoleGroupConfig>>,
}
impl ValidatedCluster {
pub(crate) fn new(
name: ClusterName,
namespace: NamespaceName,
uid: Uid,
image: ResolvedProductImage,
cluster_config: ValidatedClusterConfig,
role_group_configs: BTreeMap<OpaRole, BTreeMap<RoleGroupName, OpaRoleGroupConfig>>,
) -> Self {
let metadata = ObjectMeta {
name: Some(name.to_string()),
namespace: Some(namespace.to_string()),
uid: Some(uid.to_string()),
..ObjectMeta::default()
};
Self {
metadata,
name,
namespace,
uid,
image,
cluster_config,
role_group_configs,
}
}
/// Whether the cluster serves HTTPS, derived from the validated cluster config.
pub fn is_tls_enabled(&self) -> bool {
self.cluster_config.tls.is_some()
}
/// The name of the role-level load-balanced Kubernetes `Service`, as used in the discovery URL.
pub fn server_role_service_name(&self) -> String {
format!("{name}-{role}", name = self.name, role = OpaRole::Server)
}
/// The single OPA role name (`server`).
pub fn role_name() -> RoleName {
RoleName::from_str(&OpaRole::Server.to_string())
.expect("the server role name is a valid role name")
}
/// Type-safe names for the resources of a given role group.
pub(crate) fn resource_names(&self, role_group_name: &RoleGroupName) -> ResourceNames {
ResourceNames {
cluster_name: self.name.clone(),
role_name: Self::role_name(),
role_group_name: role_group_name.clone(),
}
}
/// The product version as a type-safe label value.
///
/// `app_version_label_value` is constructed to be a valid label value, so it is also a valid
/// [`ProductVersion`].
fn product_version(&self) -> ProductVersion {
ProductVersion::from_str(&self.image.app_version_label_value)
.expect("the app version label value is a valid product version")
}
/// Recommended labels for a role-group resource.
///
/// For role-level or cluster-level resources (e.g. the role `Service` or the discovery
/// `ConfigMap`) pass a placeholder role-group name such as `global` or `discovery`.
pub fn recommended_labels(&self, role_group_name: &RoleGroupName) -> Labels {
recommended_labels(
self,
&product_name(),
&self.product_version(),
&operator_name(),
&controller_name(),
&Self::role_name(),
role_group_name,
)
}
/// Selector labels matching the pods of a role group.
pub fn role_group_selector(&self, role_group_name: &RoleGroupName) -> Labels {
role_group_selector(self, &product_name(), &Self::role_name(), role_group_name)
}
/// Selector labels matching all pods of the (single) OPA role.
pub fn role_selector(&self) -> Labels {
role_selector(self, &product_name(), &Self::role_name())
}
/// Returns an [`ObjectMetaBuilder`](stackable_operator::builder::meta::ObjectMetaBuilder)
/// pre-filled with the namespace, an owner reference back to this cluster, and the recommended
/// labels for a resource named `name` in `role_group_name`.
///
/// Consolidates the metadata chain repeated by the child-resource builders. Call sites that
/// need extra labels/annotations chain them onto the returned builder before calling `build()`.
pub(crate) fn object_meta(
&self,
name: impl Into<String>,
role_group_name: &RoleGroupName,
) -> stackable_operator::builder::meta::ObjectMetaBuilder {
let mut builder = stackable_operator::builder::meta::ObjectMetaBuilder::new();
builder
.name_and_namespace(self)
.name(name)
.ownerreference(
stackable_operator::v2::builder::meta::ownerreference_from_resource(
self,
None,
Some(true),
),
)
.with_labels(self.recommended_labels(role_group_name));
builder
}
}
/// The product name (`opa`) as a type-safe label value.
pub(crate) fn product_name() -> ProductName {
ProductName::from_str(APP_NAME).expect("'opa' is a valid product name")
}
/// The operator name as a type-safe label value.
pub(crate) fn operator_name() -> OperatorName {
OperatorName::from_str(OPERATOR_NAME).expect("the operator name is a valid label value")
}
/// The controller name as a type-safe label value.
pub(crate) fn controller_name() -> ControllerName {
ControllerName::from_str(OPA_CONTROLLER_NAME)
.expect("the controller name is a valid label value")
}
impl HasName for ValidatedCluster {
fn to_name(&self) -> String {
self.name.to_string()
}
}
impl HasUid for ValidatedCluster {
fn to_uid(&self) -> Uid {
self.uid.clone()
}
}
impl NameIsValidLabelValue for ValidatedCluster {
fn to_label_value(&self) -> String {
self.name.to_label_value()
}
}
impl KubeResource for ValidatedCluster {
type DynamicType = <v1alpha2::OpaCluster as KubeResource>::DynamicType;
type Scope = <v1alpha2::OpaCluster as KubeResource>::Scope;
fn kind(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> {
v1alpha2::OpaCluster::kind(dt)
}
fn group(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> {
v1alpha2::OpaCluster::group(dt)
}
fn version(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> {
v1alpha2::OpaCluster::version(dt)
}
fn plural(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> {
v1alpha2::OpaCluster::plural(dt)
}
fn meta(&self) -> &ObjectMeta {
&self.metadata
}
fn meta_mut(&mut self) -> &mut ObjectMeta {
&mut self.metadata
}
}
/// Every Kubernetes resource produced by the [`build`](build::build) step.
///
/// OPA runs as a `DaemonSet` (one Pod per node), so there are no `StatefulSet`s, PDBs or
/// `Listener`s. `services` holds the role-level `Service` and the per-role-group headless and
/// metrics `Service`s; `config_maps` holds the per-role-group `ConfigMap`s and the cluster-level
/// discovery `ConfigMap`.
pub struct KubernetesResources {
pub daemon_sets: Vec<DaemonSet>,
pub services: Vec<Service>,
pub config_maps: Vec<ConfigMap>,
}
/// Cluster-wide settings resolved once during validation, so the build steps no longer need the
/// raw `OpaCluster` to render config (except for owner references).
pub struct ValidatedClusterConfig {
pub user_info: Option<user_info_fetcher::v1alpha2::Config>,
pub tls: Option<v1alpha2::OpaTls>,
pub listener_class: v1alpha2::CurrentlySupportedListenerClasses,
}
/// The validated, merged configuration of a single OPA role group.
pub type OpaRoleGroupConfig =
RoleGroupConfig<ValidatedOpaConfig, GenericCommonConfig, OpaConfigOverrides>;
/// A validated OPA config: the merged [`OpaConfig`].
#[derive(Clone, Debug, PartialEq)]
pub struct ValidatedOpaConfig {
pub resources: Resources<OpaStorageConfig, NoRuntimeLimits>,
pub logging: validate::ValidatedLogging,
pub affinity: StackableAffinity,
pub graceful_shutdown_timeout: Option<Duration>,
}
impl ValidatedOpaConfig {
pub(crate) fn from_merged(merged: OpaConfig, logging: validate::ValidatedLogging) -> Self {
Self {
resources: merged.resources,
logging,
affinity: merged.affinity,
graceful_shutdown_timeout: merged.graceful_shutdown_timeout,
}
}
}