use std::collections::HashMap;
use std::marker::PhantomData;
use crate::api::{IngressConfig, JobSubmitRequestItem, ServiceConfig};
use crate::k8s::io::api::core::v1::PodSpec;
#[doc(hidden)]
pub struct NoPodSpec;
#[doc(hidden)]
pub struct HasPodSpec;
pub struct JobRequestItemBuilder<S> {
priority: f64,
namespace: String,
client_id: String,
labels: HashMap<String, String>,
annotations: HashMap<String, String>,
scheduler: String,
external_job_uri: String,
ingress: Vec<IngressConfig>,
services: Vec<ServiceConfig>,
pod_specs: Vec<PodSpec>,
_state: PhantomData<S>,
}
impl JobRequestItemBuilder<NoPodSpec> {
pub fn new() -> Self {
Self {
priority: 0.0,
namespace: String::new(),
client_id: String::new(),
labels: HashMap::new(),
annotations: HashMap::new(),
scheduler: String::new(),
external_job_uri: String::new(),
ingress: Vec::new(),
services: Vec::new(),
pod_specs: Vec::new(),
_state: PhantomData,
}
}
}
impl Default for JobRequestItemBuilder<NoPodSpec> {
fn default() -> Self {
Self::new()
}
}
impl<S> JobRequestItemBuilder<S> {
#[must_use]
pub fn priority(mut self, p: f64) -> Self {
self.priority = p;
self
}
#[must_use]
pub fn namespace(mut self, ns: impl Into<String>) -> Self {
self.namespace = ns.into();
self
}
#[must_use]
pub fn client_id(mut self, id: impl Into<String>) -> Self {
self.client_id = id.into();
self
}
#[must_use]
pub fn labels(mut self, l: HashMap<String, String>) -> Self {
self.labels = l;
self
}
#[must_use]
pub fn label(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
self.labels.insert(k.into(), v.into());
self
}
#[must_use]
pub fn annotations(mut self, a: HashMap<String, String>) -> Self {
self.annotations = a;
self
}
#[must_use]
pub fn annotation(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
self.annotations.insert(k.into(), v.into());
self
}
#[must_use]
pub fn scheduler(mut self, s: impl Into<String>) -> Self {
self.scheduler = s.into();
self
}
#[must_use]
pub fn external_job_uri(mut self, uri: impl Into<String>) -> Self {
self.external_job_uri = uri.into();
self
}
#[must_use]
pub fn ingress(mut self, i: Vec<IngressConfig>) -> Self {
self.ingress = i;
self
}
#[must_use]
pub fn add_ingress(mut self, i: IngressConfig) -> Self {
self.ingress.push(i);
self
}
#[must_use]
pub fn services(mut self, s: Vec<ServiceConfig>) -> Self {
self.services = s;
self
}
#[must_use]
pub fn add_service(mut self, s: ServiceConfig) -> Self {
self.services.push(s);
self
}
#[must_use]
pub fn pod_spec(self, spec: PodSpec) -> JobRequestItemBuilder<HasPodSpec> {
self.pod_specs(vec![spec])
}
#[must_use]
pub fn pod_specs(self, specs: Vec<PodSpec>) -> JobRequestItemBuilder<HasPodSpec> {
JobRequestItemBuilder {
priority: self.priority,
namespace: self.namespace,
client_id: self.client_id,
labels: self.labels,
annotations: self.annotations,
scheduler: self.scheduler,
external_job_uri: self.external_job_uri,
ingress: self.ingress,
services: self.services,
pod_specs: specs,
_state: PhantomData,
}
}
}
impl JobRequestItemBuilder<HasPodSpec> {
#[must_use]
pub fn build(self) -> JobSubmitRequestItem {
#[allow(deprecated)]
JobSubmitRequestItem {
priority: self.priority,
namespace: self.namespace,
client_id: self.client_id,
labels: self.labels,
annotations: self.annotations,
pod_specs: self.pod_specs,
ingress: self.ingress,
services: self.services,
scheduler: self.scheduler,
external_job_uri: self.external_job_uri,
pod_spec: None,
required_node_labels: HashMap::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn minimal_pod_spec() -> PodSpec {
PodSpec {
containers: vec![],
..Default::default()
}
}
#[test]
fn builder_with_pod_specs_builds_correctly() {
let spec = minimal_pod_spec();
let item = JobRequestItemBuilder::new()
.namespace("default")
.priority(1.0)
.pod_specs(vec![spec])
.build();
assert_eq!(item.namespace, "default");
assert_eq!(item.priority, 1.0);
assert_eq!(item.pod_specs.len(), 1);
}
#[test]
fn pod_spec_singular_shorthand() {
let item = JobRequestItemBuilder::new()
.pod_spec(minimal_pod_spec())
.build();
assert_eq!(item.pod_specs.len(), 1);
}
#[test]
fn pod_spec_called_twice_replaces_previous() {
let spec_a = minimal_pod_spec();
let mut spec_b = minimal_pod_spec();
spec_b.restart_policy = Some("Never".to_string());
let item = JobRequestItemBuilder::new()
.pod_spec(spec_a)
.pod_spec(spec_b)
.build();
assert_eq!(item.pod_specs.len(), 1);
assert_eq!(item.pod_specs[0].restart_policy.as_deref(), Some("Never"));
}
#[test]
fn label_and_annotation_helpers() {
let item = JobRequestItemBuilder::new()
.label("app", "my-app")
.label("env", "prod")
.annotation("owner", "team-a")
.pod_spec(minimal_pod_spec())
.build();
assert_eq!(item.labels.get("app").map(String::as_str), Some("my-app"));
assert_eq!(item.labels.get("env").map(String::as_str), Some("prod"));
assert_eq!(
item.annotations.get("owner").map(String::as_str),
Some("team-a")
);
}
#[test]
fn optional_fields_default_to_empty() {
let item = JobRequestItemBuilder::new()
.pod_specs(vec![minimal_pod_spec()])
.build();
assert_eq!(item.priority, 0.0);
assert!(item.namespace.is_empty());
assert!(item.client_id.is_empty());
assert!(item.labels.is_empty());
assert!(item.annotations.is_empty());
assert!(item.scheduler.is_empty());
assert!(item.ingress.is_empty());
assert!(item.services.is_empty());
}
}