diff --git a/src/languages/docker.rs b/src/languages/docker.rs index f713ea0..b75c55c 100644 --- a/src/languages/docker.rs +++ b/src/languages/docker.rs @@ -126,7 +126,7 @@ impl Docker { Ok(Cow::Borrowed(path)) } - async fn docker_cmd() -> Result { + pub(crate) async fn docker_cmd() -> Result { let mut command = Cmd::new("docker", "run container"); command.arg("run").arg("--rm"); @@ -212,7 +212,7 @@ impl LanguageImpl for Docker { .args(&cmds[1..]) .args(hook_args.as_ref()) .args(batch) - .stderr(std::process::Stdio::inherit()) + .check(false) .envs(env_vars.as_ref()); let mut output = cmd.output().await?; diff --git a/src/languages/docker_image.rs b/src/languages/docker_image.rs new file mode 100644 index 0000000..8f2b28e --- /dev/null +++ b/src/languages/docker_image.rs @@ -0,0 +1,74 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use crate::hook::Hook; +use crate::languages::docker::Docker; +use crate::languages::{LanguageImpl, DEFAULT_VERSION}; +use crate::run::run_by_batch; + +#[derive(Debug, Copy, Clone)] +pub struct DockerImage; + +impl LanguageImpl for DockerImage { + fn default_version(&self) -> &str { + DEFAULT_VERSION + } + + fn environment_dir(&self) -> Option<&str> { + None + } + + async fn install(&self, _: &Hook) -> anyhow::Result<()> { + Ok(()) + } + + async fn check_health(&self) -> anyhow::Result<()> { + todo!() + } + + async fn run( + &self, + hook: &Hook, + filenames: &[&String], + env_vars: Arc>, + ) -> anyhow::Result<(i32, Vec)> { + let cmds = shlex::split(&hook.entry).ok_or(anyhow::anyhow!("Failed to parse entry"))?; + + let cmds = Arc::new(cmds); + let hook_args = Arc::new(hook.args.clone()); + + let run = move |batch: Vec| { + let cmds = cmds.clone(); + let hook_args = hook_args.clone(); + let env_vars = env_vars.clone(); + + async move { + let mut cmd = Docker::docker_cmd().await?; + let cmd = cmd + .args(&cmds[..]) + .args(hook_args.as_ref()) + .args(batch) + .check(false) + .envs(env_vars.as_ref()); + + let mut output = cmd.output().await?; + output.stdout.extend(output.stderr); + let code = output.status.code().unwrap_or(1); + anyhow::Ok((code, output.stdout)) + } + }; + + let results = run_by_batch(hook, filenames, run).await?; + + // Collect results + let mut combined_status = 0; + let mut combined_output = Vec::new(); + + for (code, output) in results { + combined_status |= code; + combined_output.extend(output); + } + + Ok((combined_status, combined_output)) + } +} diff --git a/src/languages/mod.rs b/src/languages/mod.rs index 66b7303..ddb2075 100644 --- a/src/languages/mod.rs +++ b/src/languages/mod.rs @@ -7,6 +7,7 @@ use crate::config::Language; use crate::hook::Hook; mod docker; +mod docker_image; mod fail; mod node; mod python; @@ -17,6 +18,7 @@ static NODE: node::Node = node::Node; static SYSTEM: system::System = system::System; static FAIL: fail::Fail = fail::Fail; static DOCKER: docker::Docker = docker::Docker; +static DOCKER_IMAGE: docker_image::DockerImage = docker_image::DockerImage; pub const DEFAULT_VERSION: &str = "default"; @@ -41,6 +43,7 @@ impl Language { Self::System => SYSTEM.default_version(), Self::Fail => FAIL.default_version(), Self::Docker => DOCKER.default_version(), + Self::DockerImage => DOCKER_IMAGE.default_version(), _ => todo!(), } } @@ -52,6 +55,7 @@ impl Language { Self::System => SYSTEM.environment_dir(), Self::Fail => FAIL.environment_dir(), Self::Docker => DOCKER.environment_dir(), + Self::DockerImage => DOCKER_IMAGE.environment_dir(), _ => todo!(), } } @@ -63,6 +67,7 @@ impl Language { Self::System => SYSTEM.install(hook).await, Self::Fail => FAIL.install(hook).await, Self::Docker => DOCKER.install(hook).await, + Self::DockerImage => DOCKER_IMAGE.install(hook).await, _ => todo!(), } } @@ -74,6 +79,7 @@ impl Language { Self::System => SYSTEM.check_health().await, Self::Fail => FAIL.check_health().await, Self::Docker => DOCKER.check_health().await, + Self::DockerImage => DOCKER_IMAGE.check_health().await, _ => todo!(), } } @@ -90,6 +96,7 @@ impl Language { Self::System => SYSTEM.run(hook, filenames, env_vars).await, Self::Fail => FAIL.run(hook, filenames, env_vars).await, Self::Docker => DOCKER.run(hook, filenames, env_vars).await, + Self::DockerImage => DOCKER_IMAGE.run(hook, filenames, env_vars).await, _ => todo!(), } } diff --git a/src/languages/system.rs b/src/languages/system.rs index 804dadb..e76271b 100644 --- a/src/languages/system.rs +++ b/src/languages/system.rs @@ -47,7 +47,6 @@ impl LanguageImpl for System { .args(&cmds[1..]) .args(hook_args.as_ref()) .args(batch) - .stderr(std::process::Stdio::inherit()) .envs(env_vars.as_ref()) .check(false) .output() diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 6cd9d5f..ee93b24 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -181,6 +181,7 @@ impl TestContext { pub fn init_project(&self) { Command::new("git") .arg("init") + .arg("--initial-branch=master") .current_dir(&self.temp_dir) .assert() .success(); @@ -247,7 +248,7 @@ pub const INSTA_FILTERS: &[(&str, &str)] = &[ "Caused by: No such file or directory (os error 2)", ), // Time seconds - (r"(\d+\.)?\d+s", "[TIME]"), + (r"(\d+\.)?\d+(ms|s)", "[TIME]"), ]; #[allow(unused_macros)] diff --git a/tests/languages/docker.rs b/tests/languages/docker.rs index 9f353b9..b652f74 100644 --- a/tests/languages/docker.rs +++ b/tests/languages/docker.rs @@ -13,6 +13,7 @@ fn docker() { hooks: - id: hello-world entry: "echo Hello, world!" + verbose: true always_run: true "#}); @@ -25,6 +26,9 @@ fn docker() { Cloning https://github.com/j178/pre-commit-docker-hooks@master Installing environment for https://github.com/j178/pre-commit-docker-hooks@master Hello World..............................................................Passed + - hook id: hello-world + - duration: [TIME] + Hello, world! .pre-commit-config.yaml ----- stderr ----- "#); diff --git a/tests/languages/docker_image.rs b/tests/languages/docker_image.rs new file mode 100644 index 0000000..b912343 --- /dev/null +++ b/tests/languages/docker_image.rs @@ -0,0 +1,80 @@ +use anyhow::Result; +use assert_cmd::Command; +use assert_fs::fixture::{FileWriteStr, PathChild}; + +use crate::common::{cmd_snapshot, TestContext}; + +#[test] +fn docker_image() -> Result<()> { + let context = TestContext::new(); + context.init_project(); + + let cwd = context.workdir(); + // Test suit from https://github.com/super-linter/super-linter/tree/main/test/linters/gitleaks/bad + cwd.child("gitleaks_bad_01.txt") + .write_str(indoc::indoc! {r" + aws_access_key_id = AROA47DSWDEZA3RQASWB + aws_secret_access_key = wQwdsZDiWg4UA5ngO0OSI2TkM4kkYxF6d2S1aYWM + "})?; + + Command::new("docker") + .args(["pull", "zricethezav/gitleaks:v8.21.2"]) + .assert() + .success(); + + context.write_pre_commit_config(indoc::indoc! {r" + repos: + - repo: local + hooks: + - id: gitleaks-docker + name: Detect hardcoded secrets + language: docker_image + entry: zricethezav/gitleaks:v8.21.2 git --pre-commit --redact --staged --verbose + pass_filenames: false + "}); + context.git_add("."); + + let filters = context + .filters() + .into_iter() + .chain([(r"\d\d?:\d\d(AM|PM)", "[TIME]")]) + .collect::>(); + + cmd_snapshot!(filters, context.run(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + Detect hardcoded secrets.................................................Failed + - hook id: gitleaks-docker + - exit code: 1 + Finding: aws_access_key_id = REDACTED + Secret: REDACTED + RuleID: generic-api-key + Entropy: 3.521928 + File: gitleaks_bad_01.txt + Line: 1 + Fingerprint: gitleaks_bad_01.txt:generic-api-key:1 + + Finding: aws_secret_access_key = REDACTED + Secret: REDACTED + RuleID: generic-api-key + Entropy: 4.703056 + File: gitleaks_bad_01.txt + Line: 2 + Fingerprint: gitleaks_bad_01.txt:generic-api-key:2 + + + ○ + │╲ + │ ○ + ○ ░ + ░ gitleaks + + [TIME] INF 1 commits scanned. + [TIME] INF scan completed in [TIME] + [TIME] WRN leaks found: 2 + + ----- stderr ----- + "#); + Ok(()) +} diff --git a/tests/languages/main.rs b/tests/languages/main.rs index 2d01d7a..d81ff70 100644 --- a/tests/languages/main.rs +++ b/tests/languages/main.rs @@ -3,4 +3,6 @@ mod common; #[cfg(all(feature = "docker", target_os = "linux"))] mod docker; +#[cfg(all(feature = "docker", target_os = "linux"))] +mod docker_image; mod fail;