Skip to content

Commit 9091fa5

Browse files
Add ListenerOperatorVolumeSourceBuilder (#496)
## Description Add ListenerOperatorVolumeSourceBuilder
1 parent 420ff75 commit 9091fa5

File tree

3 files changed

+268
-0
lines changed

3 files changed

+268
-0
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- Builder for `EphemeralVolumeSource`s added which are used by the listener-operator ([#496]).
10+
11+
[#496]: https://github.com/stackabletech/operator-rs/pull/496
12+
713
## [0.26.0] - 2022-10-20
814

915
### Added

src/builder/pod/mod.rs

+134
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use k8s_openapi::{
1515
};
1616
use std::collections::BTreeMap;
1717

18+
use super::{ListenerOperatorVolumeSourceBuilder, ListenerReference};
19+
1820
/// A builder to build [`Pod`] objects.
1921
///
2022
#[derive(Clone, Default)]
@@ -146,6 +148,138 @@ impl PodBuilder {
146148
self
147149
}
148150

151+
/// Add a [`Volume`] for the storage class `listeners.stackable.tech` with the given listener
152+
/// class.
153+
///
154+
/// # Example
155+
///
156+
/// ```
157+
/// # use stackable_operator::builder::PodBuilder;
158+
/// # use stackable_operator::builder::ContainerBuilder;
159+
/// let pod = PodBuilder::new()
160+
/// .metadata_default()
161+
/// .add_container(
162+
/// ContainerBuilder::new("container")
163+
/// .unwrap()
164+
/// .add_volume_mount("listener", "/path/to/volume")
165+
/// .build(),
166+
/// )
167+
/// .add_listener_volume_by_listener_class("listener", "nodeport")
168+
/// .build()
169+
/// .unwrap();
170+
///
171+
/// assert_eq!("\
172+
/// apiVersion: v1
173+
/// kind: Pod
174+
/// metadata: {}
175+
/// spec:
176+
/// affinity: {}
177+
/// containers:
178+
/// - name: container
179+
/// volumeMounts:
180+
/// - mountPath: /path/to/volume
181+
/// name: listener
182+
/// enableServiceLinks: false
183+
/// volumes:
184+
/// - ephemeral:
185+
/// volumeClaimTemplate:
186+
/// metadata:
187+
/// annotations:
188+
/// listeners.stackable.tech/listener-class: nodeport
189+
/// spec:
190+
/// accessModes:
191+
/// - ReadWriteMany
192+
/// resources:
193+
/// requests:
194+
/// storage: '1'
195+
/// storageClassName: listeners.stackable.tech
196+
/// name: listener
197+
/// ", serde_yaml::to_string(&pod).unwrap())
198+
/// ```
199+
pub fn add_listener_volume_by_listener_class(
200+
&mut self,
201+
volume_name: &str,
202+
listener_class: &str,
203+
) -> &mut Self {
204+
self.add_volume(Volume {
205+
name: volume_name.into(),
206+
ephemeral: Some(
207+
ListenerOperatorVolumeSourceBuilder::new(&ListenerReference::ListenerClass(
208+
listener_class.into(),
209+
))
210+
.build(),
211+
),
212+
..Volume::default()
213+
});
214+
self
215+
}
216+
217+
/// Add a [`Volume`] for the storage class `listeners.stackable.tech` with the given listener
218+
/// name.
219+
///
220+
/// # Example
221+
///
222+
/// ```
223+
/// # use stackable_operator::builder::PodBuilder;
224+
/// # use stackable_operator::builder::ContainerBuilder;
225+
/// let pod = PodBuilder::new()
226+
/// .metadata_default()
227+
/// .add_container(
228+
/// ContainerBuilder::new("container")
229+
/// .unwrap()
230+
/// .add_volume_mount("listener", "/path/to/volume")
231+
/// .build(),
232+
/// )
233+
/// .add_listener_volume_by_listener_name("listener", "preprovisioned-listener")
234+
/// .build()
235+
/// .unwrap();
236+
///
237+
/// assert_eq!("\
238+
/// apiVersion: v1
239+
/// kind: Pod
240+
/// metadata: {}
241+
/// spec:
242+
/// affinity: {}
243+
/// containers:
244+
/// - name: container
245+
/// volumeMounts:
246+
/// - mountPath: /path/to/volume
247+
/// name: listener
248+
/// enableServiceLinks: false
249+
/// volumes:
250+
/// - ephemeral:
251+
/// volumeClaimTemplate:
252+
/// metadata:
253+
/// annotations:
254+
/// listeners.stackable.tech/listener-name: preprovisioned-listener
255+
/// spec:
256+
/// accessModes:
257+
/// - ReadWriteMany
258+
/// resources:
259+
/// requests:
260+
/// storage: '1'
261+
/// storageClassName: listeners.stackable.tech
262+
/// name: listener
263+
/// ", serde_yaml::to_string(&pod).unwrap())
264+
/// ```
265+
pub fn add_listener_volume_by_listener_name(
266+
&mut self,
267+
volume_name: &str,
268+
listener_name: &str,
269+
) -> &mut Self {
270+
self.add_volume(Volume {
271+
name: volume_name.into(),
272+
ephemeral: Some(
273+
ListenerOperatorVolumeSourceBuilder::new(&ListenerReference::ListenerName(
274+
listener_name.into(),
275+
))
276+
.build(),
277+
),
278+
..Volume::default()
279+
});
280+
self
281+
}
282+
149283
pub fn image_pull_secrets(
150284
&mut self,
151285
secrets: impl IntoIterator<Item = String> + Iterator<Item = String>,

src/builder/pod/volume.rs

+128
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,91 @@ enum SecretOperatorVolumeScope {
339339
Service { name: String },
340340
}
341341

342+
/// Reference to a listener class or listener name
343+
#[derive(Clone, Debug, Eq, PartialEq)]
344+
pub enum ListenerReference {
345+
ListenerClass(String),
346+
ListenerName(String),
347+
}
348+
349+
impl ListenerReference {
350+
/// Return the key and value for a Kubernetes object annotation
351+
fn to_annotation(&self) -> (String, String) {
352+
match self {
353+
ListenerReference::ListenerClass(value) => (
354+
"listeners.stackable.tech/listener-class".into(),
355+
value.into(),
356+
),
357+
ListenerReference::ListenerName(value) => (
358+
"listeners.stackable.tech/listener-name".into(),
359+
value.into(),
360+
),
361+
}
362+
}
363+
}
364+
365+
/// Builder for an [`EphemeralVolumeSource`] containing the listener configuration
366+
///
367+
/// # Example
368+
///
369+
/// ```
370+
/// # use k8s_openapi::api::core::v1::Volume;
371+
/// # use stackable_operator::builder::ListenerReference;
372+
/// # use stackable_operator::builder::ListenerOperatorVolumeSourceBuilder;
373+
/// # use stackable_operator::builder::PodBuilder;
374+
/// let mut pod_builder = PodBuilder::new();
375+
///
376+
/// let volume_source = ListenerOperatorVolumeSourceBuilder::new(
377+
/// &ListenerReference::ListenerClass("nodeport".into()),
378+
/// )
379+
/// .build();
380+
/// pod_builder
381+
/// .add_volume(Volume {
382+
/// name: "listener".to_string(),
383+
/// ephemeral: Some(volume_source),
384+
/// ..Volume::default()
385+
/// });
386+
///
387+
/// // There is also a shortcut for the code above:
388+
/// pod_builder
389+
/// .add_listener_volume_by_listener_class("listener", "nodeport");
390+
/// ```
391+
#[derive(Clone, Debug, Eq, PartialEq)]
392+
pub struct ListenerOperatorVolumeSourceBuilder {
393+
listener_reference: ListenerReference,
394+
}
395+
396+
impl ListenerOperatorVolumeSourceBuilder {
397+
/// Create a builder for the given listener class or listener name
398+
pub fn new(listener_reference: &ListenerReference) -> Self {
399+
Self {
400+
listener_reference: listener_reference.to_owned(),
401+
}
402+
}
403+
404+
/// Build an [`EphemeralVolumeSource`] from the builder
405+
pub fn build(&self) -> EphemeralVolumeSource {
406+
EphemeralVolumeSource {
407+
volume_claim_template: Some(PersistentVolumeClaimTemplate {
408+
metadata: Some(
409+
ObjectMetaBuilder::new()
410+
.annotations([self.listener_reference.to_annotation()].into())
411+
.build(),
412+
),
413+
spec: PersistentVolumeClaimSpec {
414+
storage_class_name: Some("listeners.stackable.tech".to_string()),
415+
resources: Some(ResourceRequirements {
416+
requests: Some([("storage".to_string(), Quantity("1".to_string()))].into()),
417+
..ResourceRequirements::default()
418+
}),
419+
access_modes: Some(vec!["ReadWriteMany".to_string()]),
420+
..PersistentVolumeClaimSpec::default()
421+
},
422+
}),
423+
}
424+
}
425+
}
426+
342427
#[cfg(test)]
343428
mod tests {
344429
use super::*;
@@ -399,4 +484,47 @@ mod tests {
399484
assert_eq!(vm.sub_path, Some("sub_path".to_string()));
400485
assert_eq!(vm.sub_path_expr, Some("sub_path_expr".to_string()));
401486
}
487+
488+
#[test]
489+
fn test_listener_operator_volume_source_builder() {
490+
let builder = ListenerOperatorVolumeSourceBuilder::new(&ListenerReference::ListenerClass(
491+
"public".into(),
492+
));
493+
494+
let volume_source = builder.build();
495+
496+
let volume_claim_template = volume_source.volume_claim_template;
497+
let annotations = volume_claim_template
498+
.as_ref()
499+
.and_then(|template| template.metadata.as_ref())
500+
.and_then(|metadata| metadata.annotations.as_ref())
501+
.cloned()
502+
.unwrap_or_default();
503+
let spec = volume_claim_template.unwrap_or_default().spec;
504+
let access_modes = spec.access_modes.unwrap_or_default();
505+
let requests = spec
506+
.resources
507+
.and_then(|resources| resources.requests)
508+
.unwrap_or_default();
509+
510+
assert_eq!(1, annotations.len());
511+
assert_eq!(
512+
Some((
513+
&"listeners.stackable.tech/listener-class".to_string(),
514+
&"public".to_string()
515+
)),
516+
annotations.iter().next()
517+
);
518+
assert_eq!(
519+
Some("listeners.stackable.tech".to_string()),
520+
spec.storage_class_name
521+
);
522+
assert_eq!(1, access_modes.len());
523+
assert_eq!(Some(&"ReadWriteMany".to_string()), access_modes.first());
524+
assert_eq!(1, requests.len());
525+
assert_eq!(
526+
Some((&"storage".to_string(), &Quantity("1".into()))),
527+
requests.iter().next()
528+
);
529+
}
402530
}

0 commit comments

Comments
 (0)