Skip to content

Commit

Permalink
added tag pattern policy, renamed pattern policy
Browse files Browse the repository at this point in the history
  • Loading branch information
WhySoBad committed Feb 17, 2024
1 parent 6da67c1 commit 066339b
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 33 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/cargo-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: Cargo test

on:
push:
branches:
- master
workflow_dispatch:
workflow_call:

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Publish docker image

on:
create:
push:
tags:
- '*'
workflow_dispatch:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ services:
abwart.rule.weekend.revisions: 2
# only run the policies of the `weekend` rule in the weekend at midnight
abwart.rule.weekend.schedule: 0 0 0 * * Sun,Sat
# only apply policies of the `weekend` rule to images matching the regex pattern
abwart.rule.weekend.pattern: \w+-(alpha|beta)
# apply policies of the `weekend` rule to all images matching the regex pattern
abwart.rule.weekend.image.pattern: \w+-(alpha|beta)
# only keep 10 revisions of all images stored in the registry by default
abwart.default.revisions: 10
# apply policies every day at midnight UTC by default
Expand Down Expand Up @@ -70,7 +70,7 @@ to open a new issue or contribute to an open issue!
- [ ] Add configuration whether the garbage collector should be run inside the container
- [x] Add tests to rule parsing and rule affections
- [ ] Add ping to registry container in instance creation
- [ ] Add policy to match tags by pattern
- [x] Add policy to match tags by pattern

## Credits

Expand Down
25 changes: 20 additions & 5 deletions docs/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,36 @@ The minimum age policy ensures all tags which are deleted are older than a given
age.min: 10d
```

### Tag pattern policy
> Affection type: `Target`
>
> Identifier: `tag.pattern`
>
> Default: `.+`

The tag pattern policy matches all tags by name against a regex. For regex parsing the [regex](https://docs.rs/regex/latest/regex/) crate is used.
Any valid regex (supported by this crate) can be used.

```yaml
# Would match all tags which end in -beta or -alpha (e.g. frontend-alpha)
tag.pattern: .+-(beta|alpha)
```

## Repository policies

Repository policies are used to determine for which images a rule should be applied

### Pattern policy
### Image pattern policy
> Affection type: `Target`
>
> Identifier: `pattern`
> Identifier: `image.pattern`
>
> Default: `.+`

The pattern policy matches all repositories by name against a regex. For regex parsing the [regex](https://docs.rs/regex/latest/regex/) crate is used.
The image pattern policy matches all repositories by name against a regex. For regex parsing the [regex](https://docs.rs/regex/latest/regex/) crate is used.
Any valid regex (supported by this crate) can be used.

```yaml
# Would only match images which end in -beta or -alpha (e.g. frontend-alpha)
pattern: .+-(beta|alpha)
# Would match all images which end in -beta or -alpha (e.g. frontend-alpha)
image.pattern: .+-(beta|alpha)
```
6 changes: 4 additions & 2 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ use crate::{label, NAME};
use crate::config::Config;
use crate::policies::age_max::{AGE_MAX_LABEL, AgeMaxPolicy};
use crate::policies::age_min::{AGE_MIN_LABEL, AgeMinPolicy};
use crate::policies::pattern::{PATTERN_LABEL, PatternPolicy};
use crate::policies::image_pattern::{IMAGE_PATTERN_LABEL, ImagePatternPolicy};
use crate::policies::revision::{REVISION_LABEL, RevisionPolicy};
use crate::policies::tag_pattern::{TAG_PATTERN_LABEL, TagPatternPolicy};
use crate::rule::{parse_rule, parse_schedule, Rule};

#[derive(Debug)]
Expand Down Expand Up @@ -155,7 +156,8 @@ impl Instance {
let default_rule_pattern = Instance::get_default_rule_pattern();
let default_rule_name = id.to_string();
let mut default_rule = Rule::new(default_rule_name.clone());
default_rule.repository_policies.insert(PATTERN_LABEL, Box::<PatternPolicy>::default());
default_rule.repository_policies.insert(IMAGE_PATTERN_LABEL, Box::<ImagePatternPolicy>::default());
default_rule.tag_policies.insert(TAG_PATTERN_LABEL, Box::<TagPatternPolicy>::default());
default_rule.tag_policies.insert(AGE_MAX_LABEL, Box::<AgeMaxPolicy>::default());
default_rule.tag_policies.insert(AGE_MIN_LABEL, Box::<AgeMinPolicy>::default());
default_rule.tag_policies.insert(REVISION_LABEL, Box::<RevisionPolicy>::default());
Expand Down
24 changes: 12 additions & 12 deletions src/policies/pattern.rs → src/policies/image_pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ use regex::Regex;
use crate::api::repository::Repository;
use crate::policies::{AffectionType, Policy};

pub const PATTERN_LABEL: &str = "pattern";
pub const IMAGE_PATTERN_LABEL: &str = "image.pattern";

/// Policy to match all repositories whose name matches the provided
/// regex pattern
/// # Example
/// ```
/// let policy = PatternPolicy::new("test-\\w+");
/// let policy = ImagePatternPolicy::new("test-\\w+");
///
/// // returns all repositories whose name contains `test-<chars>` whereby
/// // `<chars>` is any alphanumeric character sequence of length >= 1
/// let affected = policy.affects(&repositories);
/// ```
#[derive(Debug, Clone)]
pub struct PatternPolicy {
pub struct ImagePatternPolicy {
pattern: Option<Regex>
}

impl PatternPolicy {
impl ImagePatternPolicy {
pub fn new(value: &str) -> Self {
if value.trim() == "" {
return Self { pattern: None }
Expand All @@ -35,13 +35,13 @@ impl PatternPolicy {
}
}

impl Default for PatternPolicy {
impl Default for ImagePatternPolicy {
fn default() -> Self {
Self { pattern: Some(Regex::new(".*").expect("Default regex should compile")) }
}
}

impl Policy<Repository> for PatternPolicy {
impl Policy<Repository> for ImagePatternPolicy {
fn affects(&self, elements: Vec<Repository>) -> Vec<Repository> {
if let Some(pattern) = &self.pattern {
elements.into_iter().filter(|repo| pattern.is_match(&repo.name)).collect()
Expand All @@ -55,7 +55,7 @@ impl Policy<Repository> for PatternPolicy {
}

fn id(&self) -> &'static str {
PATTERN_LABEL
IMAGE_PATTERN_LABEL
}

fn enabled(&self) -> bool {
Expand All @@ -66,38 +66,38 @@ impl Policy<Repository> for PatternPolicy {

#[cfg(test)]
mod test {
use crate::policies::pattern::PatternPolicy;
use crate::policies::image_pattern::ImagePatternPolicy;
use crate::policies::Policy;
use crate::test::get_repositories;

#[test]
pub fn test_matching() {
let repositories = get_repositories(vec!["test-matching", "not-matching"]);
let policy = PatternPolicy::new("test-.*");
let policy = ImagePatternPolicy::new("test-.*");
assert!(policy.pattern.is_some());
assert_eq!(policy.affects(repositories.clone()), vec![repositories[0].clone()]);
}

#[test]
pub fn test_empty() {
let repositories = get_repositories(vec!["test-matching", "not-matching"]);
let policy = PatternPolicy::new("");
let policy = ImagePatternPolicy::new("");
assert!(policy.pattern.is_none());
assert_eq!(policy.affects(repositories), vec![]);
}

#[test]
pub fn test_default() {
let repositories = get_repositories(vec!["test-matching", "not-matching"]);
let policy = PatternPolicy::default();
let policy = ImagePatternPolicy::default();
assert!(policy.pattern.is_some());
assert_eq!(policy.affects(repositories.clone()), repositories)
}

#[test]
pub fn test_invalid_regex() {
let repositories = get_repositories(vec!["test-matching", "not-matching"]);
let policy = PatternPolicy::new("([a-zA-Z]+"); // the regex is invalid
let policy = ImagePatternPolicy::new("([a-zA-Z]+"); // the regex is invalid
assert!(policy.pattern.is_none());
assert_eq!(policy.affects(repositories), vec![]);
}
Expand Down
3 changes: 2 additions & 1 deletion src/policies/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use crate::api::tag::Tag;

pub mod age_max;
pub mod age_min;
pub mod pattern;
pub mod image_pattern;
pub mod revision;
pub mod tag_pattern;

pub type PolicyMap<T> = HashMap<&'static str, Box<dyn Policy<T>>>;

Expand Down
105 changes: 105 additions & 0 deletions src/policies/tag_pattern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use log::info;
use regex::Regex;
use crate::api::tag::Tag;
use crate::policies::{AffectionType, Policy};

pub const TAG_PATTERN_LABEL: &str = "tag.pattern";

/// Policy to match all tags whose name matches the provided
/// regex pattern
/// # Example
/// ```
/// let policy = TagPatternPolicy::new("test-\\w+");
///
/// // returns all tags whose name contains `test-<chars>` whereby
/// // `<chars>` is any alphanumeric character sequence of length >= 1
/// let affected = policy.affects(&tags);
/// ```
#[derive(Debug, Clone)]
pub struct TagPatternPolicy {
pattern: Option<Regex>
}

impl TagPatternPolicy {
pub fn new(value: &str) -> Self {
if value.trim() == "" {
return Self { pattern: None }
}
match Regex::new(value) {
Ok(regex) => Self { pattern: Some(regex) },
Err(err) => {
info!("Received invalid pattern '{value}'. Reason: {err}");
Self { pattern: None }
}
}
}
}

impl Default for TagPatternPolicy {
fn default() -> Self {
Self { pattern: Some(Regex::new(".*").expect("Default regex should compile")) }
}
}

impl Policy<Tag> for TagPatternPolicy {
fn affects(&self, elements: Vec<Tag>) -> Vec<Tag> {
if let Some(pattern) = &self.pattern {
elements.into_iter().filter(|tag| pattern.is_match(&tag.name)).collect()
} else {
vec![]
}
}

fn affection_type(&self) -> AffectionType {
AffectionType::Target
}

fn id(&self) -> &'static str {
TAG_PATTERN_LABEL
}

fn enabled(&self) -> bool {
self.pattern.is_some()
}
}


#[cfg(test)]
mod test {
use chrono::Duration;
use crate::policies::Policy;
use crate::policies::tag_pattern::TagPatternPolicy;
use crate::test::get_tags_by_name;

#[test]
pub fn test_matching() {
let tags = get_tags_by_name(vec!["test-matching", "not-matching"], Duration::seconds(1), 1);
let policy = TagPatternPolicy::new("test-.*");
assert!(policy.pattern.is_some());
assert_eq!(policy.affects(tags.clone()), vec![tags[0].clone()]);
}

#[test]
pub fn test_empty() {
let tags = get_tags_by_name(vec!["test-matching", "not-matching"], Duration::seconds(1), 1);
let policy = TagPatternPolicy::new("");
assert!(policy.pattern.is_none());
assert_eq!(policy.affects(tags), vec![]);
}

#[test]
pub fn test_default() {
let tags = get_tags_by_name(vec!["test-matching", "not-matching"], Duration::seconds(1), 1);
let policy = TagPatternPolicy::default();
assert!(policy.pattern.is_some());
assert_eq!(policy.affects(tags.clone()), tags)
}

#[test]
pub fn test_invalid_regex() {
let tags = get_tags_by_name(vec!["test-matching", "not-matching"], Duration::seconds(1), 1);
let policy = TagPatternPolicy::new("([a-zA-Z]+"); // the regex is invalid
assert!(policy.pattern.is_none());
assert_eq!(policy.affects(tags), vec![]);
}
}
Loading

0 comments on commit 066339b

Please sign in to comment.