mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-08 05:03:49 -05:00
wip
This commit is contained in:
1343
Cargo.lock
generated
1343
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,5 +6,6 @@ edition = "2024"
|
|||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bollard = "0.19.2"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
rustc-hash = "2.1.1"
|
rustc-hash = "2.1.1"
|
||||||
|
|||||||
51
cup/src/auth.rs
Normal file
51
cup/src/auth.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
|
pub struct AuthToken {
|
||||||
|
token_type: TokenType,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TokenType {
|
||||||
|
Basic,
|
||||||
|
Bearer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TokenType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Basic => "Basic",
|
||||||
|
Self::Bearer => "Bearer",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthToken {
|
||||||
|
pub fn from_basic(token: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
token_type: TokenType::Basic,
|
||||||
|
value: token.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bearer(token: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
token_type: TokenType::Bearer,
|
||||||
|
value: token.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_type(&self) -> &'static str {
|
||||||
|
match &self.token_type {
|
||||||
|
TokenType::Basic => "Basic",
|
||||||
|
TokenType::Bearer => "Bearer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_value(&self) -> &str {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
10
cup/src/docker.rs
Normal file
10
cup/src/docker.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
use crate::socket::Socket;
|
||||||
|
|
||||||
|
/// Wraps a Docker client and provides useful functions get data from it.
|
||||||
|
pub struct Docker {
|
||||||
|
socket: Socket,
|
||||||
|
client: bollard::Docker
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Docker {
|
||||||
|
}
|
||||||
13
cup/src/image_config.rs
Normal file
13
cup/src/image_config.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use crate::version::{VersionType, standard::StandardUpdateType};
|
||||||
|
|
||||||
|
pub struct ImageConfig {
|
||||||
|
version_type: VersionType,
|
||||||
|
// Will only be read if update type is standard
|
||||||
|
ignored_update_types: Vec<StandardUpdateType>,
|
||||||
|
/// Whether to skip checking the image (default is false)
|
||||||
|
ignore: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageConfig {
|
||||||
|
|
||||||
|
}
|
||||||
3
cup/src/image_to_check.rs
Normal file
3
cup/src/image_to_check.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub struct ImageToCheck {
|
||||||
|
|
||||||
|
}
|
||||||
111
cup/src/lib.rs
111
cup/src/lib.rs
@@ -1 +1,110 @@
|
|||||||
pub mod version;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
auth::AuthToken, image_config::ImageConfig, image_to_check::ImageToCheck, reference::Reference,
|
||||||
|
reference_matcher::ReferenceMatcher, registry::Registry, registry_config::RegistryConfig,
|
||||||
|
socket::Socket,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
|
pub mod image_config;
|
||||||
|
pub mod image_to_check;
|
||||||
|
pub mod reference;
|
||||||
|
pub mod reference_matcher;
|
||||||
|
pub mod registry;
|
||||||
|
pub mod registry_config;
|
||||||
|
pub mod socket;
|
||||||
|
pub mod version;
|
||||||
|
pub mod docker;
|
||||||
|
|
||||||
|
/// This struct is the main interface to Cup's functions.
|
||||||
|
/// Any useful user-provided or generated data is stored here for reuse during the lifetime of the struct.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Cup {
|
||||||
|
images: Vec<ImageToCheck>,
|
||||||
|
socket: Socket,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Cup {
|
||||||
|
/// Returns a new empty instance with all values initialized to their defaults.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builder() -> CupBuilder {
|
||||||
|
CupBuilder::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check for updates to the images provided (and any images configured through options)
|
||||||
|
pub async fn check(images: &[&Reference]) -> Vec<Result<CheckResult, CheckError>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CupBuilder {
|
||||||
|
/// Whether or not to check for updates to local images in addition to the other images.
|
||||||
|
check_local_images: bool,
|
||||||
|
/// Whether or not to check for updates to images used in the swarm in addition to the other images. If the host is not a manager node this will only check the locally running swarm images.
|
||||||
|
check_swarm_images: bool,
|
||||||
|
registry_config: FxHashMap<Registry, RegistryConfig>,
|
||||||
|
overrides: FxHashMap<ReferenceMatcher, ImageConfig>,
|
||||||
|
socket: Socket,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CupBuilder {
|
||||||
|
pub fn with_local_images(&mut self) {
|
||||||
|
self.check_local_images = true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_swarm_images(&mut self) {
|
||||||
|
self.check_swarm_images = true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_registry_auth(&mut self, registry: Registry, token: AuthToken) {
|
||||||
|
match self.registry_config.get_mut(®istry) {
|
||||||
|
Some(registry_config) => registry_config.auth = Some(token),
|
||||||
|
None => {
|
||||||
|
self.registry_config.insert(
|
||||||
|
registry,
|
||||||
|
RegistryConfig {
|
||||||
|
auth: Some(token),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_insecure_registry(&mut self, registry: Registry) {
|
||||||
|
match self.registry_config.get_mut(®istry) {
|
||||||
|
Some(registry_config) => registry_config.insecure = true,
|
||||||
|
None => {
|
||||||
|
self.registry_config.insert(
|
||||||
|
registry,
|
||||||
|
RegistryConfig {
|
||||||
|
insecure: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_image_config(&mut self, reference: ReferenceMatcher, config: ImageConfig) {
|
||||||
|
self.overrides.insert(reference, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_socket(&mut self, socket: Socket) {
|
||||||
|
self.socket = socket
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Cup {
|
||||||
|
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CheckResult {}
|
||||||
|
pub struct CheckError {}
|
||||||
|
|||||||
5
cup/src/reference.rs
Normal file
5
cup/src/reference.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub struct Reference(String);
|
||||||
|
|
||||||
|
impl Reference {
|
||||||
|
|
||||||
|
}
|
||||||
38
cup/src/reference_matcher.rs
Normal file
38
cup/src/reference_matcher.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
/// When customizing image configuration, there may be a need to operate on a batch of images. The ReferenceMatcher describes which references the configuration should apply to.
|
||||||
|
pub enum ReferenceMatcher {
|
||||||
|
/// The reference is exactly equal to the string provided
|
||||||
|
ExactMatch(String),
|
||||||
|
/// The reference starts with the string provided, useful for configuring based on a registry or repository
|
||||||
|
PrefixMatch(String),
|
||||||
|
/// The reference ends with the string provided, useful for configuring based on a tag
|
||||||
|
SuffixMatch(String),
|
||||||
|
/// The reference contains the string provided
|
||||||
|
Contains(String),
|
||||||
|
/// For more complicated matching logic a regular expression can be used.
|
||||||
|
Custom(Regex),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ReferenceMatcher {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(ReferenceMatcher::Custom(a), ReferenceMatcher::Custom(b)) => a.as_str() == b.as_str(),
|
||||||
|
(a, b) => a.eq(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ReferenceMatcher {}
|
||||||
|
|
||||||
|
impl Hash for ReferenceMatcher {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
match self {
|
||||||
|
Self::Custom(r) => r.as_str().hash(state),
|
||||||
|
o => o.hash(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReferenceMatcher {}
|
||||||
49
cup/src/registry.rs
Normal file
49
cup/src/registry.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use std::{error::Error, fmt::Display, str::FromStr, sync::OnceLock};
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
|
pub struct Registry {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Regex representing the value of `domainAndPort` from https://github.com/distribution/reference/blob/727f80d42224f6696b8e1ad16b06aadf2c6b833b/regexp.go#L108
|
||||||
|
/// Used to verify whether a string is a registry hostname
|
||||||
|
const REGISTRY_REGEX: &str = r"^(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))*|\[(?:[a-fA-F0-9:]+)\])(?::[0-9]+)?$";
|
||||||
|
static REGISTRY: OnceLock<Regex> = OnceLock::new();
|
||||||
|
|
||||||
|
impl FromStr for Registry {
|
||||||
|
type Err = ParseRegistryError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let regex = REGISTRY.get_or_init(|| Regex::new(REGISTRY_REGEX).unwrap());
|
||||||
|
if s.is_empty() {
|
||||||
|
Ok(Self {
|
||||||
|
value: String::from("registry-1.docker.io"),
|
||||||
|
})
|
||||||
|
} else if regex.is_match(s) {
|
||||||
|
Ok(Self {
|
||||||
|
value: s.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(ParseRegistryError {
|
||||||
|
registry: s.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParseRegistryError {
|
||||||
|
pub registry: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ParseRegistryError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "`{}` is not a valid registry hostname", self.registry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ParseRegistryError {}
|
||||||
|
|
||||||
|
// TODO: Add tests
|
||||||
9
cup/src/registry_config.rs
Normal file
9
cup/src/registry_config.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use crate::auth::AuthToken;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RegistryConfig {
|
||||||
|
/// An optional authentication token to use when making requests to this registry
|
||||||
|
pub(super) auth: Option<AuthToken>,
|
||||||
|
/// Whether or not to connect to the registry over unencrypted HTTP
|
||||||
|
pub(super) insecure: bool
|
||||||
|
}
|
||||||
7
cup/src/socket.rs
Normal file
7
cup/src/socket.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#[derive(Default)]
|
||||||
|
pub enum Socket {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Unix(String),
|
||||||
|
Tcp(String)
|
||||||
|
}
|
||||||
1
cup/src/version/digest.rs
Normal file
1
cup/src/version/digest.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub struct DigestVersion {}
|
||||||
@@ -15,10 +15,10 @@ pub struct ExtendedVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExtendedVersion {
|
impl ExtendedVersion {
|
||||||
pub fn parse(version_string: &str, regex: &str) -> Result<Self, VersionParseError> {
|
pub fn parse(version_string: &str, regex: &str) -> Result<Self, ParseVersionError> {
|
||||||
let regex = Regex::new(regex).map_err(|e| VersionParseError {
|
let regex = Regex::new(regex).map_err(|e| ParseVersionError {
|
||||||
version_string: version_string.to_string(),
|
version_string: version_string.to_string(),
|
||||||
kind: VersionParseErrorKind::InvalidRegex(e),
|
kind: ParseVersionErrorKind::InvalidRegex(e),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Check if all capture groups are named or anonymous
|
// Check if all capture groups are named or anonymous
|
||||||
@@ -29,9 +29,9 @@ impl ExtendedVersion {
|
|||||||
match prev_state {
|
match prev_state {
|
||||||
None => Ok(Some(name)), // First iteration
|
None => Ok(Some(name)), // First iteration
|
||||||
Some(prev_state) => match (prev_state, name) {
|
Some(prev_state) => match (prev_state, name) {
|
||||||
(Some(_), None) | (None, Some(_)) => Err(VersionParseError {
|
(Some(_), None) | (None, Some(_)) => Err(ParseVersionError {
|
||||||
version_string: version_string.to_string(),
|
version_string: version_string.to_string(),
|
||||||
kind: VersionParseErrorKind::NonUniformCaptureGroups,
|
kind: ParseVersionErrorKind::NonUniformCaptureGroups,
|
||||||
}),
|
}),
|
||||||
_ => Ok(Some(name)),
|
_ => Ok(Some(name)),
|
||||||
},
|
},
|
||||||
@@ -51,22 +51,22 @@ impl ExtendedVersion {
|
|||||||
.capture_names()
|
.capture_names()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let capture = captures.name(name).ok_or_else(|| VersionParseError {
|
let capture = captures.name(name).ok_or_else(|| ParseVersionError {
|
||||||
version_string: version_string.to_string(),
|
version_string: version_string.to_string(),
|
||||||
kind: VersionParseErrorKind::GroupDidNotMatch(name.to_string()),
|
kind: ParseVersionErrorKind::GroupDidNotMatch(name.to_string()),
|
||||||
})?;
|
})?;
|
||||||
let component =
|
let component =
|
||||||
VersionComponent::from_str(capture.as_str()).map_err(|_| {
|
VersionComponent::from_str(capture.as_str()).map_err(|_| {
|
||||||
VersionParseError {
|
ParseVersionError {
|
||||||
version_string: version_string.to_string(),
|
version_string: version_string.to_string(),
|
||||||
kind: VersionParseErrorKind::GroupDidNotMatch(
|
kind: ParseVersionErrorKind::GroupDidNotMatch(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
Ok((name.to_string(), component))
|
Ok((name.to_string(), component))
|
||||||
})
|
})
|
||||||
.collect::<Result<FxHashMap<String, VersionComponent>, VersionParseError>>(
|
.collect::<Result<FxHashMap<String, VersionComponent>, ParseVersionError>>(
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
captures
|
captures
|
||||||
@@ -77,20 +77,20 @@ impl ExtendedVersion {
|
|||||||
.map(|(i, m)| {
|
.map(|(i, m)| {
|
||||||
VersionComponent::from_str(m.as_str())
|
VersionComponent::from_str(m.as_str())
|
||||||
.map(|comp| (i, comp))
|
.map(|comp| (i, comp))
|
||||||
.map_err(|e| VersionParseError {
|
.map_err(|e| ParseVersionError {
|
||||||
version_string: version_string.to_string(),
|
version_string: version_string.to_string(),
|
||||||
kind: VersionParseErrorKind::ParseComponent(e),
|
kind: ParseVersionErrorKind::ParseComponent(e),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<FxHashMap<String, VersionComponent>, VersionParseError>>(
|
.collect::<Result<FxHashMap<String, VersionComponent>, ParseVersionError>>(
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
Self { components }
|
Self { components }
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
return Err(VersionParseError {
|
return Err(ParseVersionError {
|
||||||
version_string: version_string.to_string(),
|
version_string: version_string.to_string(),
|
||||||
kind: VersionParseErrorKind::NoMatch,
|
kind: ParseVersionErrorKind::NoMatch,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -100,12 +100,12 @@ impl ExtendedVersion {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct VersionParseError {
|
pub struct ParseVersionError {
|
||||||
version_string: String,
|
version_string: String,
|
||||||
kind: VersionParseErrorKind,
|
kind: ParseVersionErrorKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for VersionParseError {
|
impl Display for ParseVersionError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@@ -115,7 +115,7 @@ impl Display for VersionParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for VersionParseError {
|
impl Error for ParseVersionError {
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
Some(&self.kind)
|
Some(&self.kind)
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ impl Error for VersionParseError {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub enum VersionParseErrorKind {
|
pub enum ParseVersionErrorKind {
|
||||||
/// The regex supplied could not be compiled
|
/// The regex supplied could not be compiled
|
||||||
InvalidRegex(regex::Error),
|
InvalidRegex(regex::Error),
|
||||||
/// The regex supplied has both named and anonymous capture groups
|
/// The regex supplied has both named and anonymous capture groups
|
||||||
@@ -136,7 +136,7 @@ pub enum VersionParseErrorKind {
|
|||||||
ParseComponent(ParseIntError),
|
ParseComponent(ParseIntError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for VersionParseErrorKind {
|
impl Display for ParseVersionErrorKind {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::InvalidRegex(_) => write!(f, "invalid regex"),
|
Self::InvalidRegex(_) => write!(f, "invalid regex"),
|
||||||
@@ -152,7 +152,7 @@ impl Display for VersionParseErrorKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for VersionParseErrorKind {
|
impl Error for ParseVersionErrorKind {
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
Self::InvalidRegex(e) => Some(e),
|
Self::InvalidRegex(e) => Some(e),
|
||||||
@@ -172,8 +172,8 @@ mod tests {
|
|||||||
use crate::version::version_component::VersionComponent;
|
use crate::version::version_component::VersionComponent;
|
||||||
|
|
||||||
use super::ExtendedVersion;
|
use super::ExtendedVersion;
|
||||||
use super::VersionParseError;
|
use super::ParseVersionError;
|
||||||
use super::VersionParseErrorKind;
|
use super::ParseVersionErrorKind;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_with_anonymous() {
|
fn parse_with_anonymous() {
|
||||||
@@ -274,9 +274,9 @@ mod tests {
|
|||||||
"1.0.0-test2",
|
"1.0.0-test2",
|
||||||
r"(\d+)\.(\d+))\.(\d+)-test(\d+)" // Whoops, someone left an extra ) somewhere...
|
r"(\d+)\.(\d+))\.(\d+)-test(\d+)" // Whoops, someone left an extra ) somewhere...
|
||||||
),
|
),
|
||||||
Err(VersionParseError {
|
Err(ParseVersionError {
|
||||||
version_string: String::from("1.0.0-test2"),
|
version_string: String::from("1.0.0-test2"),
|
||||||
kind: VersionParseErrorKind::InvalidRegex(
|
kind: ParseVersionErrorKind::InvalidRegex(
|
||||||
Regex::new(r"(\d+)\.(\d+))\.(\d+)-test(\d+)").unwrap_err()
|
Regex::new(r"(\d+)\.(\d+))\.(\d+)-test(\d+)").unwrap_err()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -287,9 +287,9 @@ mod tests {
|
|||||||
fn invalid_component() {
|
fn invalid_component() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ExtendedVersion::parse("50h+2-3_40", r"([a-z0-9]+)\+(\d+)-(\d+)_(\d+)"),
|
ExtendedVersion::parse("50h+2-3_40", r"([a-z0-9]+)\+(\d+)-(\d+)_(\d+)"),
|
||||||
Err(VersionParseError {
|
Err(ParseVersionError {
|
||||||
version_string: String::from("50h+2-3_40"),
|
version_string: String::from("50h+2-3_40"),
|
||||||
kind: VersionParseErrorKind::ParseComponent("50h".parse::<usize>().unwrap_err())
|
kind: ParseVersionErrorKind::ParseComponent("50h".parse::<usize>().unwrap_err())
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -301,9 +301,9 @@ mod tests {
|
|||||||
"4.1.2.5",
|
"4.1.2.5",
|
||||||
r"(?<Major>\d+)\.(?<Minor>\d+)\.(?<Patch>\d+)\.(\d+)"
|
r"(?<Major>\d+)\.(?<Minor>\d+)\.(?<Patch>\d+)\.(\d+)"
|
||||||
),
|
),
|
||||||
Err(VersionParseError {
|
Err(ParseVersionError {
|
||||||
version_string: String::from("4.1.2.5"),
|
version_string: String::from("4.1.2.5"),
|
||||||
kind: VersionParseErrorKind::NonUniformCaptureGroups
|
kind: ParseVersionErrorKind::NonUniformCaptureGroups
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -315,9 +315,9 @@ mod tests {
|
|||||||
"1-sometool-0.2.5-alpine",
|
"1-sometool-0.2.5-alpine",
|
||||||
r"(?:\d+)-somet0ol-(\d+)\.(\d+)\.(\d+)-alpine"
|
r"(?:\d+)-somet0ol-(\d+)\.(\d+)\.(\d+)-alpine"
|
||||||
),
|
),
|
||||||
Err(VersionParseError {
|
Err(ParseVersionError {
|
||||||
version_string: String::from("1-sometool-0.2.5-alpine"),
|
version_string: String::from("1-sometool-0.2.5-alpine"),
|
||||||
kind: VersionParseErrorKind::NoMatch
|
kind: ParseVersionErrorKind::NoMatch
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -329,9 +329,9 @@ mod tests {
|
|||||||
"2.0.5-alpine",
|
"2.0.5-alpine",
|
||||||
r"(?<Eenie>\d+)\.(?<Meenie>\d+)\.(?<Miney>\d+)|(?<Moe>moe)-alpine"
|
r"(?<Eenie>\d+)\.(?<Meenie>\d+)\.(?<Miney>\d+)|(?<Moe>moe)-alpine"
|
||||||
),
|
),
|
||||||
Err(VersionParseError {
|
Err(ParseVersionError {
|
||||||
version_string: String::from("2.0.5-alpine"),
|
version_string: String::from("2.0.5-alpine"),
|
||||||
kind: VersionParseErrorKind::GroupDidNotMatch(String::from("Moe"))
|
kind: ParseVersionErrorKind::GroupDidNotMatch(String::from("Moe"))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use crate::version::date::DateVersion;
|
use crate::version::date::DateVersion;
|
||||||
|
use crate::version::digest::DigestVersion;
|
||||||
use crate::version::extended::ExtendedVersion;
|
use crate::version::extended::ExtendedVersion;
|
||||||
use crate::version::standard::StandardVersion;
|
use crate::version::standard::StandardVersion;
|
||||||
|
|
||||||
pub mod date;
|
pub mod date;
|
||||||
|
pub mod digest;
|
||||||
pub mod extended;
|
pub mod extended;
|
||||||
pub mod standard;
|
pub mod standard;
|
||||||
|
|
||||||
@@ -17,6 +19,17 @@ pub enum Version {
|
|||||||
Extended(ExtendedVersion),
|
Extended(ExtendedVersion),
|
||||||
/// The "Date" versioning schema is for images which are versioned based on date and or time and come with their own quirky rules to compare them. Refer to `DateVersion` for more info.
|
/// The "Date" versioning schema is for images which are versioned based on date and or time and come with their own quirky rules to compare them. Refer to `DateVersion` for more info.
|
||||||
Date(DateVersion),
|
Date(DateVersion),
|
||||||
|
/// Version checking based on local and remote image digests
|
||||||
|
Digest(DigestVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum VersionType {
|
||||||
|
/// Tries to automatically infer the version type.
|
||||||
|
Auto,
|
||||||
|
Standard,
|
||||||
|
Extended,
|
||||||
|
Date,
|
||||||
|
Digest
|
||||||
}
|
}
|
||||||
|
|
||||||
mod version_component {
|
mod version_component {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
|
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
|
||||||
|
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use super::version_component::VersionComponent;
|
use super::version_component::VersionComponent;
|
||||||
|
|
||||||
/// A versioning scheme I'd call SemVer-inspired. The main difference from [SemVer](https://semver.org) is that the minor and patch versions are optional.
|
/// A versioning scheme I'd call SemVer-inspired. The main difference from [SemVer](https://semver.org) is that the minor and patch versions are optional.
|
||||||
@@ -13,28 +15,28 @@ pub struct StandardVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for StandardVersion {
|
impl FromStr for StandardVersion {
|
||||||
type Err = VersionParseError;
|
type Err = ParseVersionError;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let splits = s.split('.');
|
let splits = s.split('.');
|
||||||
if splits.clone().any(|split| split.is_empty()) {
|
if splits.clone().any(|split| split.is_empty()) {
|
||||||
return Err(VersionParseError {
|
return Err(ParseVersionError {
|
||||||
version_string: s.to_string(),
|
version_string: s.to_string(),
|
||||||
kind: VersionParseErrorKind::IncorrectFormat,
|
kind: ParseVersionErrorKind::IncorrectFormat,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut component_iter = splits.map(|component| {
|
let mut component_iter = splits.map(|component| {
|
||||||
VersionComponent::from_str(component).map_err(|e| VersionParseError {
|
VersionComponent::from_str(component).map_err(|e| ParseVersionError {
|
||||||
version_string: s.to_string(),
|
version_string: s.to_string(),
|
||||||
kind: VersionParseErrorKind::ParseComponent(e),
|
kind: ParseVersionErrorKind::ParseComponent(e),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let major = component_iter.next().transpose()?.unwrap();
|
let major = component_iter.next().transpose()?.unwrap();
|
||||||
let minor = component_iter.next().transpose()?;
|
let minor = component_iter.next().transpose()?;
|
||||||
let patch = component_iter.next().transpose()?;
|
let patch = component_iter.next().transpose()?;
|
||||||
if component_iter.next().is_some() {
|
if component_iter.next().is_some() {
|
||||||
return Err(VersionParseError {
|
return Err(ParseVersionError {
|
||||||
version_string: s.to_string(),
|
version_string: s.to_string(),
|
||||||
kind: VersionParseErrorKind::TooManyComponents(4 + component_iter.count()),
|
kind: ParseVersionErrorKind::TooManyComponents(4 + component_iter.count()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -45,15 +47,22 @@ impl FromStr for StandardVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
|
pub enum StandardUpdateType {
|
||||||
|
Major,
|
||||||
|
Minor,
|
||||||
|
Patch
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct VersionParseError {
|
pub struct ParseVersionError {
|
||||||
pub version_string: String,
|
pub version_string: String,
|
||||||
pub kind: VersionParseErrorKind,
|
pub kind: ParseVersionErrorKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for VersionParseError {
|
impl Display for ParseVersionError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@@ -63,7 +72,7 @@ impl Display for VersionParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for VersionParseError {
|
impl Error for ParseVersionError {
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
Some(&self.kind)
|
Some(&self.kind)
|
||||||
}
|
}
|
||||||
@@ -71,7 +80,7 @@ impl Error for VersionParseError {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub enum VersionParseErrorKind {
|
pub enum ParseVersionErrorKind {
|
||||||
/// A version component could not be parsed
|
/// A version component could not be parsed
|
||||||
ParseComponent(ParseIntError),
|
ParseComponent(ParseIntError),
|
||||||
/// The version string is not in the format expected by `StandardVersion`
|
/// The version string is not in the format expected by `StandardVersion`
|
||||||
@@ -80,7 +89,7 @@ pub enum VersionParseErrorKind {
|
|||||||
TooManyComponents(usize),
|
TooManyComponents(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for VersionParseErrorKind {
|
impl Display for ParseVersionErrorKind {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
Self::IncorrectFormat => write!(f, "version string is incorrectly formatted"),
|
Self::IncorrectFormat => write!(f, "version string is incorrectly formatted"),
|
||||||
@@ -94,7 +103,7 @@ impl Display for VersionParseErrorKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for VersionParseErrorKind {
|
impl Error for ParseVersionErrorKind {
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
match &self {
|
match &self {
|
||||||
Self::ParseComponent(e) => Some(e),
|
Self::ParseComponent(e) => Some(e),
|
||||||
@@ -108,7 +117,7 @@ impl Error for VersionParseErrorKind {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::{StandardVersion, VersionParseError, VersionParseErrorKind};
|
use super::{StandardVersion, ParseVersionError, ParseVersionErrorKind};
|
||||||
use super::super::version_component::VersionComponent;
|
use super::super::version_component::VersionComponent;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -190,9 +199,9 @@ mod tests {
|
|||||||
fn parse_invalid_string() {
|
fn parse_invalid_string() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
StandardVersion::from_str(".1.0"),
|
StandardVersion::from_str(".1.0"),
|
||||||
Err(VersionParseError {
|
Err(ParseVersionError {
|
||||||
version_string: String::from(".1.0"),
|
version_string: String::from(".1.0"),
|
||||||
kind: VersionParseErrorKind::IncorrectFormat
|
kind: ParseVersionErrorKind::IncorrectFormat
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -201,9 +210,9 @@ mod tests {
|
|||||||
fn parse_invalid_component() {
|
fn parse_invalid_component() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
StandardVersion::from_str("0.1.O"),
|
StandardVersion::from_str("0.1.O"),
|
||||||
Err(VersionParseError {
|
Err(ParseVersionError {
|
||||||
version_string: String::from("0.1.O"),
|
version_string: String::from("0.1.O"),
|
||||||
kind: VersionParseErrorKind::ParseComponent(
|
kind: ParseVersionErrorKind::ParseComponent(
|
||||||
"O".parse::<u32>().unwrap_err()
|
"O".parse::<u32>().unwrap_err()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -214,9 +223,9 @@ mod tests {
|
|||||||
fn parse_four_components() {
|
fn parse_four_components() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
StandardVersion::from_str("1.2.4.0"),
|
StandardVersion::from_str("1.2.4.0"),
|
||||||
Err(VersionParseError {
|
Err(ParseVersionError {
|
||||||
version_string: String::from("1.2.4.0"),
|
version_string: String::from("1.2.4.0"),
|
||||||
kind: VersionParseErrorKind::TooManyComponents(4)
|
kind: ParseVersionErrorKind::TooManyComponents(4)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user