From 11fd970400d119f5932cf5c84af57577c4f91a19 Mon Sep 17 00:00:00 2001 From: James Andariese Date: Tue, 31 Oct 2023 06:50:05 -0500 Subject: [PATCH] split module into smaller pieces --- src/cfg.rs | 117 +++++++++++++++++++++++++++++++++++++ src/errors.rs | 40 +++++++++++++ src/main.rs | 155 +------------------------------------------------- 3 files changed, 160 insertions(+), 152 deletions(-) create mode 100644 src/cfg.rs create mode 100644 src/errors.rs diff --git a/src/cfg.rs b/src/cfg.rs new file mode 100644 index 0000000..0aa69fc --- /dev/null +++ b/src/cfg.rs @@ -0,0 +1,117 @@ +use crate::{*,errors::*}; + +#[derive(ClapParser, Debug, Clone)] +/// git-remote-k8s +/// +/// This should usually be run by git. You can set git up to use it +/// by running git remote add k k8s://default/ns/repo. +/// +/// see https://git.strudelline.net/cascade/git-remote-k8s for more info +pub struct Config { + #[arg( + short, + long, + env = "GIT_REMOTE_K8S_IMAGE", + default_value = "alpine/git:latest" + )] + /// Docker image used for git Jobs + pub image: String, + + #[arg(index = 1)] + /// remote name + pub remote_name: String, + + #[arg(index = 2)] + /// remote URL + pub remote_url: String, + + #[arg( + short, + long, + env = "GIT_REMOTE_K8S_DEBUG", + action=ArgAction::Count, + )] + /// verbosity, may be specified more than once + pub verbose: u8, + + #[arg( + long("storage-class-name"), + env = "GIT_REMOTE_K8S_PVC_STORAGECLASSNAME" + )] + /// storageClassName to use for the backing PVC _if it is created_. + /// + /// this will be used to create a new PVC but if a PVC already + /// exists by the requested name, it will be used. + pub storageclass: Option, + + #[arg( + short('s'), + long("initial-volume-size"), + default_value("1Gi"), + env = "GIT_REMOTE_K8S_INITIAL_VOLUME_SIZE" + )] + pub initial_size: String, +} + + +impl Config { + /// parse and validate a k8s remote pvc short-URL into a triple of Strings of the form (context, namespace, pvc) + /// + /// this utilizes a regex instead of url::Url to ensure that it returns sensible errors + // TODO: find a way to memoize this cleanly. probably give it access to a memoizing context from AppContext. + pub fn parse_and_validate(&self) -> Result<(String, String, String)> { + let caps = REMOTE_PATTERN + .captures(&self.remote_url) + .ok_or(ConfigError::RemoteInvalid)?; + let scheme = if caps.name("scheme_prefix").is_none() { + SCHEME + } else { + caps.name("scheme") + .ok_or(ConfigError::RemoteInvalidScheme)? + .as_str() + }; + if scheme != SCHEME { + bail!(ConfigError::RemoteInvalidScheme); + } + let kctx = caps + .name("context") + .ok_or(ConfigError::RemoteNoContext)? + .as_str(); + let ns = caps + .name("namespace") + .ok_or(ConfigError::RemoteNoNamespace)? + .as_str(); + let pvc = caps.name("pvc").ok_or(ConfigError::RemoteNoPVC)?.as_str(); + // regex::Regex::find(REMOTE_PATTERN); + if kctx == "" { + bail!(ConfigError::RemoteNoContext); + }; + if ns == "" { + bail!(ConfigError::RemoteNoNamespace); + }; + if pvc == "" { + bail!(ConfigError::RemoteNoPVC); + }; + if let Some(trailing) = caps.name("trailing") { + if trailing.as_str() != "" { + bail!(ConfigError::RemoteTrailingElements); + } + } + Ok((kctx.to_owned(), ns.to_owned(), pvc.to_owned())) + } + + pub fn get_remote_context(&self) -> Result { + let (r, _, _) = self.parse_and_validate()?; + Ok(r) + } + + pub fn get_remote_namespace(&self) -> Result { + let (_, r, _) = self.parse_and_validate()?; + Ok(r) + } + + pub fn get_remote_pvc(&self) -> Result { + let (_, _, r) = self.parse_and_validate()?; + Ok(r) + } +} \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..3c466f7 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,40 @@ +use crate::*; + +#[derive(ThisError, Debug)] +pub enum ApplicationError { + /// cluster state problems + #[error("cluster is in an inconsistent state")] + RemoteClusterInconsistent, + + /// pod state problems + #[error("pod metadata doesn't contain a name")] + PodNoName, + #[error("pod metadata doesn't contain a namespace")] + PodNoNamespace, + #[error("couldn't open pod's stdin")] + PodCouldNotOpenStdin, + #[error("couldn't open pod's stdout")] + PodCouldNotOpenStdout, + #[error("couldn't open pod's stderr")] + PodCouldNotOpenStderr, + #[error("pod failed to start")] + PodCouldNotStart, + #[error("worker pod did not continue running")] + PodDidNotWait, +} + +#[derive(ThisError, Debug)] +pub enum ConfigError { + #[error("no namespace present in remote URL")] + RemoteNoNamespace, + #[error("trailing elements after pvc in remote URL")] + RemoteTrailingElements, + #[error("no context present in remote URL")] + RemoteNoContext, + #[error("no PVC name present in remote URL")] + RemoteNoPVC, + #[error("invalid remote URL")] + RemoteInvalid, + #[error("remote URL has an invalid (or no) scheme")] + RemoteInvalidScheme, +} diff --git a/src/main.rs b/src/main.rs index 7b2af97..8c5a4ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,159 +42,10 @@ static REMOTE_PATTERN: Lazy = Lazy::new(|| { #[cfg(test)] mod test; -#[derive(ClapParser, Debug, Clone)] -/// git-remote-k8s -/// -/// This should usually be run by git. You can set git up to use it -/// by running git remote add k k8s://default/ns/repo. -/// -/// see https://git.strudelline.net/cascade/git-remote-k8s for more info -struct Config { - #[arg( - short, - long, - env = "GIT_REMOTE_K8S_IMAGE", - default_value = "alpine/git:latest" - )] - /// Docker image used for git Jobs - image: String, +mod cfg; +mod errors; - #[arg(index = 1)] - /// remote name - remote_name: String, - - #[arg(index = 2)] - /// remote URL - remote_url: String, - - #[arg( - short, - long, - env = "GIT_REMOTE_K8S_DEBUG", - action=ArgAction::Count, - )] - /// verbosity, may be specified more than once - verbose: u8, - - #[arg( - long("storage-class-name"), - env = "GIT_REMOTE_K8S_PVC_STORAGECLASSNAME" - )] - /// storageClassName to use for the backing PVC _if it is created_. - /// - /// this will be used to create a new PVC but if a PVC already - /// exists by the requested name, it will be used. - storageclass: Option, - - #[arg( - short('s'), - long("initial-volume-size"), - default_value("1Gi"), - env = "GIT_REMOTE_K8S_INITIAL_VOLUME_SIZE" - )] - initial_size: String, -} - -#[derive(ThisError, Debug)] -pub enum ApplicationError { - /// cluster state problems - #[error("cluster is in an inconsistent state")] - RemoteClusterInconsistent, - - /// pod state problems - #[error("pod metadata doesn't contain a name")] - PodNoName, - #[error("pod metadata doesn't contain a namespace")] - PodNoNamespace, - #[error("couldn't open pod's stdin")] - PodCouldNotOpenStdin, - #[error("couldn't open pod's stdout")] - PodCouldNotOpenStdout, - #[error("couldn't open pod's stderr")] - PodCouldNotOpenStderr, - #[error("pod failed to start")] - PodCouldNotStart, - #[error("worker pod did not continue running")] - PodDidNotWait, -} - -#[derive(ThisError, Debug)] -pub enum ConfigError { - #[error("no namespace present in remote URL")] - RemoteNoNamespace, - #[error("trailing elements after pvc in remote URL")] - RemoteTrailingElements, - #[error("no context present in remote URL")] - RemoteNoContext, - #[error("no PVC name present in remote URL")] - RemoteNoPVC, - #[error("invalid remote URL")] - RemoteInvalid, - #[error("remote URL has an invalid (or no) scheme")] - RemoteInvalidScheme, -} - -impl Config { - /// parse and validate a k8s remote pvc short-URL into a triple of Strings of the form (context, namespace, pvc) - /// - /// this utilizes a regex instead of url::Url to ensure that it returns sensible errors - // TODO: find a way to memoize this cleanly. probably give it access to a memoizing context from AppContext. - fn parse_and_validate(&self) -> Result<(String, String, String)> { - let caps = REMOTE_PATTERN - .captures(&self.remote_url) - .ok_or(ConfigError::RemoteInvalid)?; - let scheme = if caps.name("scheme_prefix").is_none() { - SCHEME - } else { - caps.name("scheme") - .ok_or(ConfigError::RemoteInvalidScheme)? - .as_str() - }; - if scheme != SCHEME { - bail!(ConfigError::RemoteInvalidScheme); - } - let kctx = caps - .name("context") - .ok_or(ConfigError::RemoteNoContext)? - .as_str(); - let ns = caps - .name("namespace") - .ok_or(ConfigError::RemoteNoNamespace)? - .as_str(); - let pvc = caps.name("pvc").ok_or(ConfigError::RemoteNoPVC)?.as_str(); - // regex::Regex::find(REMOTE_PATTERN); - if kctx == "" { - bail!(ConfigError::RemoteNoContext); - }; - if ns == "" { - bail!(ConfigError::RemoteNoNamespace); - }; - if pvc == "" { - bail!(ConfigError::RemoteNoPVC); - }; - if let Some(trailing) = caps.name("trailing") { - if trailing.as_str() != "" { - bail!(ConfigError::RemoteTrailingElements); - } - } - Ok((kctx.to_owned(), ns.to_owned(), pvc.to_owned())) - } - - fn get_remote_context(&self) -> Result { - let (r, _, _) = self.parse_and_validate()?; - Ok(r) - } - - fn get_remote_namespace(&self) -> Result { - let (_, r, _) = self.parse_and_validate()?; - Ok(r) - } - - fn get_remote_pvc(&self) -> Result { - let (_, _, r) = self.parse_and_validate()?; - Ok(r) - } -} +use crate::{cfg::*,errors::*}; struct AppContext { config: Arc,