use crates::git_checks_core::impl_prelude::*;
#[derive(Builder, Debug, Clone, Copy)]
#[builder(field(private))]
pub struct SubmoduleWatch {
#[builder(default = "false")]
reject_additions: bool,
#[builder(default = "false")]
reject_removals: bool,
}
impl SubmoduleWatch {
pub fn builder() -> SubmoduleWatchBuilder {
SubmoduleWatchBuilder::default()
}
}
impl Default for SubmoduleWatch {
fn default() -> Self {
SubmoduleWatch {
reject_additions: false,
reject_removals: false,
}
}
}
impl Check for SubmoduleWatch {
fn name(&self) -> &str {
"submodule-watch"
}
fn check(&self, ctx: &CheckGitContext, commit: &Commit) -> Result<CheckResult, Box<dyn Error>> {
let mut result = CheckResult::new();
for diff in &commit.diffs {
if diff.new_mode != "160000" && diff.old_mode != "160000" {
continue;
}
let mut added = false;
let mut removed = false;
match diff.status {
StatusChange::Added => added = true,
StatusChange::Deleted => removed = true,
StatusChange::Modified(_) => {
if diff.old_mode != diff.new_mode {
if diff.old_mode == "160000" {
removed = true;
} else {
added = true;
}
}
},
_ => (),
}
let is_configured = SubmoduleContext::new(ctx, diff.name.as_ref()).is_some();
if added && !is_configured {
if self.reject_additions {
result.add_error(format!(
"commit {} adds a submodule at `{}` which is not allowed.",
commit.sha1, diff.name,
));
} else {
result.add_alert(
format!(
"commit {} adds a submodule at `{}`.",
commit.sha1, diff.name,
),
false,
);
}
result.make_temporary();
}
if removed {
if self.reject_removals {
result.add_error(format!(
"commit {} removes the submodule at `{}` which is not allowed.",
commit.sha1, diff.name,
));
} else {
result.add_alert(
format!(
"commit {} removes the submodule at `{}`.",
commit.sha1, diff.name,
),
false,
);
}
}
}
Ok(result)
}
}
#[cfg(feature = "config")]
pub(crate) mod config {
use crates::git_checks_config::{CommitCheckConfig, IntoCheck};
use crates::inventory;
#[cfg(test)]
use crates::serde_json;
use SubmoduleWatch;
#[derive(Deserialize, Debug)]
pub struct SubmoduleWatchConfig {
#[serde(default)]
reject_additions: Option<bool>,
#[serde(default)]
reject_removals: Option<bool>,
}
impl IntoCheck for SubmoduleWatchConfig {
type Check = SubmoduleWatch;
fn into_check(self) -> Self::Check {
let mut builder = SubmoduleWatch::builder();
if let Some(reject_additions) = self.reject_additions {
builder.reject_additions(reject_additions);
}
if let Some(reject_removals) = self.reject_removals {
builder.reject_removals(reject_removals);
}
builder
.build()
.expect("configuration mismatch for `SubmoduleWatch`")
}
}
register_checks! {
SubmoduleWatchConfig {
"submodule_watch" => CommitCheckConfig,
},
}
#[test]
fn test_submodule_watch_config_empty() {
let json = json!({});
let check: SubmoduleWatchConfig = serde_json::from_value(json).unwrap();
assert_eq!(check.reject_additions, None);
assert_eq!(check.reject_removals, None);
}
#[test]
fn test_submodule_watch_config_all_fields() {
let json = json!({
"reject_additions": true,
"reject_removals": true,
});
let check: SubmoduleWatchConfig = serde_json::from_value(json).unwrap();
assert_eq!(check.reject_additions, Some(true));
assert_eq!(check.reject_removals, Some(true));
}
}
#[cfg(test)]
mod tests {
use test::*;
use SubmoduleWatch;
const ADD_SUBMODULE_TOPIC: &str = "fe90ee22ae3ce4b4dc41f8d0876e59355ff1e21c";
const REMOVE_SUBMODULE_TOPIC: &str = "336dbaa31d512033fe77eaba7f92ebfecbd17a39";
#[test]
fn test_submodule_watch_builder_default() {
assert!(SubmoduleWatch::builder().build().is_ok());
}
#[test]
fn test_submodule_watch_add() {
let check = SubmoduleWatch::default();
let result = run_check("test_submodule_watch_add", ADD_SUBMODULE_TOPIC, check);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 1);
assert_eq!(
result.alerts()[0],
"commit fe90ee22ae3ce4b4dc41f8d0876e59355ff1e21c adds a submodule at `submodule`.",
);
assert_eq!(result.errors().len(), 0);
assert_eq!(result.temporary(), true);
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), true);
}
#[test]
fn test_submodule_watch_add_reject() {
let check = SubmoduleWatch::builder()
.reject_additions(true)
.build()
.unwrap();
let result = run_check(
"test_submodule_watch_add_reject",
ADD_SUBMODULE_TOPIC,
check,
);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 0);
assert_eq!(result.errors().len(), 1);
assert_eq!(
result.errors()[0],
"commit fe90ee22ae3ce4b4dc41f8d0876e59355ff1e21c adds a submodule at `submodule` \
which is not allowed.",
);
assert_eq!(result.temporary(), true);
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), false);
}
#[test]
fn test_submodule_watch_add_configured() {
let check = SubmoduleWatch::default();
let conf = make_check_conf(&check);
let result = test_check_submodule_configure(
"test_submodule_watch_add_configured",
ADD_SUBMODULE_TOPIC,
&conf,
"submodule",
);
test_result_ok(result);
}
#[test]
fn test_submodule_watch_add_configured_reject() {
let check = SubmoduleWatch::builder()
.reject_additions(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_submodule_configure(
"test_submodule_watch_add_configured_reject",
ADD_SUBMODULE_TOPIC,
&conf,
"submodule",
);
test_result_ok(result);
}
#[test]
fn test_submodule_watch_remove() {
let check = SubmoduleWatch::default();
let conf = make_check_conf(&check);
let result = test_check_submodule_base(
"test_submodule_watch_remove",
REMOVE_SUBMODULE_TOPIC,
ADD_SUBMODULE_TOPIC,
&conf,
);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 1);
assert_eq!(
result.alerts()[0],
"commit 336dbaa31d512033fe77eaba7f92ebfecbd17a39 removes the submodule at `submodule`.",
);
assert_eq!(result.errors().len(), 0);
assert_eq!(result.temporary(), false);
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), true);
}
#[test]
fn test_submodule_watch_remove_reject() {
let check = SubmoduleWatch::builder()
.reject_removals(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_submodule_base(
"test_submodule_watch_remove_reject",
REMOVE_SUBMODULE_TOPIC,
ADD_SUBMODULE_TOPIC,
&conf,
);
test_result_errors(result, &[
"commit 336dbaa31d512033fe77eaba7f92ebfecbd17a39 removes the submodule at `submodule` \
which is not allowed.",
]);
}
}