split module into smaller pieces
This commit is contained in:
parent
97fadf6c92
commit
11fd970400
117
src/cfg.rs
Normal file
117
src/cfg.rs
Normal file
|
@ -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<String>,
|
||||||
|
|
||||||
|
#[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<String> {
|
||||||
|
let (r, _, _) = self.parse_and_validate()?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_remote_namespace(&self) -> Result<String> {
|
||||||
|
let (_, r, _) = self.parse_and_validate()?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_remote_pvc(&self) -> Result<String> {
|
||||||
|
let (_, _, r) = self.parse_and_validate()?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
}
|
40
src/errors.rs
Normal file
40
src/errors.rs
Normal file
|
@ -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,
|
||||||
|
}
|
155
src/main.rs
155
src/main.rs
|
@ -42,159 +42,10 @@ static REMOTE_PATTERN: Lazy<regex::Regex> = Lazy::new(|| {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
#[derive(ClapParser, Debug, Clone)]
|
mod cfg;
|
||||||
/// git-remote-k8s
|
mod errors;
|
||||||
///
|
|
||||||
/// 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,
|
|
||||||
|
|
||||||
#[arg(index = 1)]
|
use crate::{cfg::*,errors::*};
|
||||||
/// 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<String>,
|
|
||||||
|
|
||||||
#[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<String> {
|
|
||||||
let (r, _, _) = self.parse_and_validate()?;
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_remote_namespace(&self) -> Result<String> {
|
|
||||||
let (_, r, _) = self.parse_and_validate()?;
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_remote_pvc(&self) -> Result<String> {
|
|
||||||
let (_, _, r) = self.parse_and_validate()?;
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppContext {
|
struct AppContext {
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user