use std::path::PathBuf;
use std::time::Duration;
use anyhow::Result;
use crate::steps::traits::ResourceLimits;
use crate::workflows::Workflow;
pub struct MaestroClient {
kube_config_path: Option<PathBuf>,
namespace: String,
dry_run: bool,
default_timeout: Option<Duration>,
log_level: Option<String>,
default_resource_limits: Option<ResourceLimits>,
}
impl MaestroClient {
pub(crate) fn new(
kube_config_path: Option<PathBuf>,
namespace: String,
dry_run: bool,
default_timeout: Option<Duration>,
log_level: Option<String>,
default_resource_limits: Option<ResourceLimits>,
) -> Self {
Self {
kube_config_path,
namespace,
dry_run,
default_timeout,
log_level,
default_resource_limits,
}
}
pub fn namespace(&self) -> &str {
&self.namespace
}
pub fn dry_run(&self) -> bool {
self.dry_run
}
pub fn default_timeout(&self) -> Option<&Duration> {
self.default_timeout.as_ref()
}
pub fn log_level(&self) -> Option<&str> {
self.log_level.as_deref()
}
pub fn default_resource_limits(&self) -> Option<&ResourceLimits> {
self.default_resource_limits.as_ref()
}
pub fn kube_config_path(&self) -> Option<&PathBuf> {
self.kube_config_path.as_ref()
}
pub fn create_workflow(&self, workflow: Workflow) -> Result<CreatedWorkflow> {
if self.dry_run {
log::info!(
"DRY RUN: Would create workflow '{}' in namespace '{}'",
workflow.name,
self.namespace
);
return Ok(CreatedWorkflow::DryRun(DryRunWorkflow {
workflow,
namespace: self.namespace.clone(),
}));
}
log::info!(
"Creating workflow '{}' in namespace '{}'",
workflow.name,
self.namespace
);
workflow.validate()?;
Ok(CreatedWorkflow::Runtime(RuntimeWorkflow {
workflow,
namespace: self.namespace.clone(),
}))
}
pub fn get_workflow(&self, _id: &str) -> Result<Option<CreatedWorkflow>> {
if self.dry_run {
log::info!("DRY RUN: Would get workflow with id '{}'", _id);
return Ok(None);
}
log::info!("Getting workflow with id '{}'", _id);
Ok(None)
}
}
pub enum CreatedWorkflow {
DryRun(DryRunWorkflow),
Runtime(RuntimeWorkflow),
}
impl CreatedWorkflow {
pub fn id(&self) -> &str {
match self {
CreatedWorkflow::DryRun(w) => w.id(),
CreatedWorkflow::Runtime(w) => w.id(),
}
}
pub fn name(&self) -> &str {
match self {
CreatedWorkflow::DryRun(w) => w.name(),
CreatedWorkflow::Runtime(w) => w.name(),
}
}
pub fn namespace(&self) -> &str {
match self {
CreatedWorkflow::DryRun(w) => w.namespace(),
CreatedWorkflow::Runtime(w) => w.namespace(),
}
}
pub fn is_dry_run(&self) -> bool {
matches!(self, CreatedWorkflow::DryRun(_))
}
}
pub trait WorkflowLike {
fn id(&self) -> &str;
fn name(&self) -> &str;
fn namespace(&self) -> &str;
}
pub struct DryRunWorkflow {
workflow: Workflow,
namespace: String,
}
impl WorkflowLike for DryRunWorkflow {
fn id(&self) -> &str {
&self.workflow.id
}
fn name(&self) -> &str {
&self.workflow.name
}
fn namespace(&self) -> &str {
&self.namespace
}
}
pub struct RuntimeWorkflow {
workflow: Workflow,
namespace: String,
}
impl WorkflowLike for RuntimeWorkflow {
fn id(&self) -> &str {
&self.workflow.id
}
fn name(&self) -> &str {
&self.workflow.name
}
fn namespace(&self) -> &str {
&self.namespace
}
}
#[cfg(test)]
mod tests {
use super::super::MaestroClientBuilder;
use super::*;
use crate::steps::traits::{ResourceLimitedStep, WorkFlowStep};
use crate::workflows::WorkflowBuilder;
#[derive(Debug, Clone)]
struct MockStep {
id: String,
}
impl MockStep {
fn new(id: impl Into<String>) -> Self {
Self { id: id.into() }
}
}
impl WorkFlowStep for MockStep {
fn step_id(&self) -> &str {
&self.id
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl ResourceLimitedStep for MockStep {
fn with_resource_limits(self, _limits: ResourceLimits) -> Self {
self
}
fn resource_limits(&self) -> Option<&ResourceLimits> {
None
}
}
#[test]
fn test_client_namespace() {
let client = MaestroClientBuilder::new()
.with_namespace("production")
.build()
.unwrap();
assert_eq!(client.namespace(), "production");
}
#[test]
fn test_client_dry_run() {
let client = MaestroClientBuilder::new()
.with_dry_run(true)
.build()
.unwrap();
assert!(client.dry_run());
}
#[test]
fn test_client_default_timeout() {
let timeout = Duration::from_secs(60);
let client = MaestroClientBuilder::new()
.with_default_timeout(timeout)
.build()
.unwrap();
assert_eq!(client.default_timeout(), Some(&timeout));
}
#[test]
fn test_client_log_level() {
let client = MaestroClientBuilder::new()
.with_log_level("debug")
.build()
.unwrap();
assert_eq!(client.log_level(), Some("debug"));
}
#[test]
fn test_client_default_resource_limits() {
let limits = ResourceLimits::new().with_cpu("500m").with_memory("512Mi");
let client = MaestroClientBuilder::new()
.with_default_resource_limits(limits)
.build()
.unwrap();
assert!(client.default_resource_limits().is_some());
}
#[test]
fn test_create_workflow_dry_run() {
let client = MaestroClientBuilder::new()
.with_dry_run(true)
.with_namespace("test")
.build()
.unwrap();
let step = MockStep::new("step-1");
let workflow = WorkflowBuilder::new()
.with_name("test-workflow")
.with_namespace("default")
.add_step(step)
.build()
.unwrap();
let result = client.create_workflow(workflow);
assert!(result.is_ok());
let created = result.unwrap();
assert!(created.is_dry_run());
assert_eq!(created.name(), "test-workflow");
}
#[test]
fn test_create_workflow_production() {
let client = MaestroClientBuilder::new()
.with_namespace("production")
.build()
.unwrap();
let step = MockStep::new("step-1");
let workflow = WorkflowBuilder::new()
.with_name("test-workflow")
.with_namespace("default")
.add_step(step)
.build()
.unwrap();
let result = client.create_workflow(workflow);
assert!(result.is_ok());
let created = result.unwrap();
assert!(!created.is_dry_run());
assert_eq!(created.name(), "test-workflow");
}
#[test]
fn test_get_workflow_dry_run() {
let client = MaestroClientBuilder::new()
.with_dry_run(true)
.build()
.unwrap();
let result = client.get_workflow("test-id");
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_get_workflow_production() {
let client = MaestroClientBuilder::new().build().unwrap();
let result = client.get_workflow("test-id");
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
}