diff --git a/src/tools/genpolicy/src/policy.rs b/src/tools/genpolicy/src/policy.rs index 3a920a3dce..b3b0b95b6b 100644 --- a/src/tools/genpolicy/src/policy.rs +++ b/src/tools/genpolicy/src/policy.rs @@ -599,9 +599,6 @@ impl AgentPolicy { let mut yaml_string = String::new(); for i in 0..self.resources.len() { let annotation = self.resources[i].generate_initdata_anno(self); - if self.config.base64_out { - println!("{annotation}"); - } yaml_string += &self.resources[i].serialize(&annotation); } @@ -614,8 +611,7 @@ impl AgentPolicy { .unwrap() .write_all(yaml_string.as_bytes()) .unwrap(); - } else { - // When input YAML came through stdin, print the output YAML to stdout. + } else if !self.config.base64_out && !self.config.raw_out { std::io::stdout().write_all(yaml_string.as_bytes()).unwrap(); } } @@ -639,13 +635,18 @@ impl AgentPolicy { let json_data = serde_json::to_string_pretty(&policy_data).unwrap(); let policy = format!("{}\npolicy_data := {json_data}", &self.rules); + let mut initdata = self.config.initdata.clone(); + initdata.insert_data("policy.rego", policy.clone()); + let encoded = kata_types::initdata::encode_initdata(&initdata); + if self.config.raw_out { std::io::stdout().write_all(policy.as_bytes()).unwrap(); } - let mut initdata = self.config.initdata.clone(); - initdata.insert_data("policy.rego", policy); + if self.config.base64_out { + std::io::stdout().write_all(encoded.as_bytes()).unwrap(); + } - kata_types::initdata::encode_initdata(&initdata) + encoded } pub fn get_container_policy( diff --git a/src/tools/genpolicy/tests/generate/main.rs b/src/tools/genpolicy/tests/generate/main.rs index f228eb9a0d..164db59c95 100644 --- a/src/tools/genpolicy/tests/generate/main.rs +++ b/src/tools/genpolicy/tests/generate/main.rs @@ -5,6 +5,7 @@ use assert_cmd::prelude::*; use std::fs::{self}; +use std::io::Write; use std::path; use std::process::Command; @@ -68,6 +69,131 @@ fn secret_in_separate_file() -> Result<(), Box> { Ok(()) } +#[test] +fn output_behavior() -> Result<(), Box> { + struct TestCase { + name: &'static str, + flag: Option<&'static str>, + use_yaml_file: bool, + expect_yaml_in_stdout: bool, + expect_base64_in_stdout: bool, + expect_raw_in_stdout: bool, + } + + let test_cases = &[ + // --yaml-file alone: file modified, no stdout + TestCase { + name: "yaml_file_only", + flag: None, + use_yaml_file: true, + expect_yaml_in_stdout: false, + expect_base64_in_stdout: false, + expect_raw_in_stdout: false, + }, + // --yaml-file --base64-out: file modified, base64 to stdout + TestCase { + name: "yaml_file_with_base64_out", + flag: Some("--base64-out"), + use_yaml_file: true, + expect_yaml_in_stdout: false, + expect_base64_in_stdout: true, + expect_raw_in_stdout: false, + }, + // --yaml-file --raw-out: file modified, raw to stdout + TestCase { + name: "yaml_file_with_raw_out", + flag: Some("--raw-out"), + use_yaml_file: true, + expect_yaml_in_stdout: false, + expect_base64_in_stdout: false, + expect_raw_in_stdout: true, + }, + // stdin alone: annotated YAML to stdout + TestCase { + name: "stdin_only", + flag: None, + use_yaml_file: false, + expect_yaml_in_stdout: true, + expect_base64_in_stdout: false, + expect_raw_in_stdout: false, + }, + // stdin --base64-out: only base64 to stdout (suppress YAML) + TestCase { + name: "stdin_with_base64_out", + flag: Some("--base64-out"), + use_yaml_file: false, + expect_yaml_in_stdout: false, + expect_base64_in_stdout: true, + expect_raw_in_stdout: false, + }, + // stdin --raw-out: only raw to stdout (suppress YAML) + TestCase { + name: "stdin_with_raw_out", + flag: Some("--raw-out"), + use_yaml_file: false, + expect_yaml_in_stdout: false, + expect_base64_in_stdout: false, + expect_raw_in_stdout: true, + }, + ]; + + for tc in test_cases.iter() { + let workdir = prepare_workdir(tc.name, &["simple_pod.yaml"]); + let pod_yaml_path = workdir.join("simple_pod.yaml"); + + let output = if tc.use_yaml_file { + let mut cmd = Command::cargo_bin("genpolicy")?; + cmd.arg("--yaml-file").arg(&pod_yaml_path); + if let Some(flag) = tc.flag { + cmd.arg(flag); + } + cmd.output()? + } else { + let pod_yaml_content = fs::read_to_string(&pod_yaml_path)?; + let mut cmd = Command::cargo_bin("genpolicy")?; + cmd.current_dir(&workdir); + if let Some(flag) = tc.flag { + cmd.arg(flag); + } + let mut child = cmd + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn()?; + child + .stdin + .take() + .unwrap() + .write_all(pod_yaml_content.as_bytes())?; + child.wait_with_output()? + }; + + assert!(output.status.success(), "{}: command failed", tc.name); + + let stdout = String::from_utf8_lossy(&output.stdout); + let has_yaml = stdout.contains("apiVersion:"); + let has_raw = stdout.contains("policy_data"); + let has_base64 = !stdout.trim().is_empty() && !has_yaml && !has_raw; + + assert_eq!( + has_yaml, tc.expect_yaml_in_stdout, + "{}: yaml in stdout", + tc.name + ); + assert_eq!( + has_raw, tc.expect_raw_in_stdout, + "{}: raw policy in stdout", + tc.name + ); + assert_eq!( + has_base64, tc.expect_base64_in_stdout, + "{}: base64 policy in stdout", + tc.name + ); + } + + Ok(()) +} + fn prepare_workdir(test_case_dir: &str, files_to_copy: &[&str]) -> path::PathBuf { // Prepare temp dir for running genpolicy. let workdir = path::PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(test_case_dir); diff --git a/src/tools/genpolicy/tests/generate/testdata/simple_pod.yaml b/src/tools/genpolicy/tests/generate/testdata/simple_pod.yaml new file mode 100644 index 0000000000..ef51949a22 --- /dev/null +++ b/src/tools/genpolicy/tests/generate/testdata/simple_pod.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: simple-pod +spec: + restartPolicy: Never + runtimeClassName: kata-cc + containers: + - name: busybox + image: "quay.io/prometheus/busybox:latest" + command: + - /bin/sh + args: + - "-c" + - echo hello