|
| 1 | +use futures::AsyncWriteExt; |
| 2 | +use gitlab_runner::uploader::Uploader; |
| 3 | +use gitlab_runner::{outputln, JobHandler, JobResult, Phase, Runner}; |
| 4 | +use gitlab_runner_mock::{GitlabRunnerMock, MockJobState, MockJobStepName, MockJobStepWhen}; |
| 5 | +use std::io::{Cursor, Read}; |
| 6 | +use tracing::instrument::WithSubscriber; |
| 7 | +use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; |
| 8 | +use tracing_subscriber::Registry; |
| 9 | +use zip::ZipArchive; |
| 10 | + |
| 11 | +struct MaskTest { |
| 12 | + log_text: String, |
| 13 | +} |
| 14 | + |
| 15 | +#[async_trait::async_trait] |
| 16 | +impl JobHandler for MaskTest { |
| 17 | + async fn step(&mut self, _script: &[String], _phase: Phase) -> JobResult { |
| 18 | + outputln!("{}", self.log_text.split('|').collect::<Vec<_>>().join("")); |
| 19 | + Ok(()) |
| 20 | + } |
| 21 | + |
| 22 | + async fn upload_artifacts(&mut self, uploader: &mut Uploader) -> JobResult { |
| 23 | + let mut f = uploader.masked_file("masked".to_string()).await; |
| 24 | + f.write_all( |
| 25 | + format!( |
| 26 | + "{}\n", |
| 27 | + self.log_text.split('|').collect::<Vec<_>>().join("") |
| 28 | + ) |
| 29 | + .as_bytes(), |
| 30 | + ) |
| 31 | + .await |
| 32 | + .expect("Couldn't write test data"); |
| 33 | + drop(f); |
| 34 | + let mut f = uploader.file("baseline".to_string()).await; |
| 35 | + f.write_all(self.log_text.as_bytes()) |
| 36 | + .await |
| 37 | + .expect("Couldn't write test data"); |
| 38 | + |
| 39 | + Ok(()) |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +#[derive(Debug, Clone, Default)] |
| 44 | +struct TestCase { |
| 45 | + prefixes: Vec<String>, |
| 46 | + input: String, |
| 47 | + expected: String, |
| 48 | + name: String, |
| 49 | +} |
| 50 | + |
| 51 | +macro_rules! emit_prefixes { |
| 52 | + ( $( $pfx:expr ),* ) => { |
| 53 | + vec![ |
| 54 | + $( |
| 55 | + $pfx.to_string() |
| 56 | + ),* |
| 57 | + ] |
| 58 | + }; |
| 59 | +} |
| 60 | + |
| 61 | +macro_rules! parse_test_case { |
| 62 | + ( $data:expr, ) => {}; |
| 63 | + |
| 64 | + ( $data:expr, $name:literal : { input: $input:expr, expected: $expected:expr, } ) => { |
| 65 | + $data.push( TestCase { prefixes : Vec::new(), input: $input.to_string(), expected: format!("{}\n", $expected), name: $name.to_string() } ); |
| 66 | + }; |
| 67 | + |
| 68 | + ( $data:expr, $name:literal : { input: $input:expr, expected: $expected:expr, }, $($tail:tt)+ ) => { |
| 69 | + $data.push( TestCase { prefixes : Vec::new(), input: $input.to_string(), expected: format!("{}\n", $expected), name: $name.to_string() } ); |
| 70 | + parse_test_case![$data, $($tail)*] |
| 71 | + }; |
| 72 | + |
| 73 | + ( $data:expr, $name:literal : { prefixes: []string{ $($pfx:expr),* }, input: $input:expr, expected: $expected:expr, } ) => { |
| 74 | + $data.push( TestCase { prefixes : emit_prefixes![$($pfx),*], input: $input.to_string(), expected: format!("{}\n", $expected), name: $name.to_string() } ); |
| 75 | + }; |
| 76 | + |
| 77 | + ( $data:expr, $name:literal : { prefixes: []string{ $($pfx:expr),* }, input: $input:expr, expected: $expected:expr, }, $($tail:tt)* ) => { |
| 78 | + $data.push( TestCase { prefixes : emit_prefixes![$($pfx),*], input: $input.to_string(), expected: format!("{}\n", $expected), name: $name.to_string() } ); |
| 79 | + parse_test_case![$data, $($tail)*] |
| 80 | + }; |
| 81 | +} |
| 82 | + |
| 83 | +macro_rules! test_cases { |
| 84 | + () => {}; |
| 85 | + |
| 86 | + ( $($tail:tt)* ) => { |
| 87 | + { |
| 88 | + let mut data = Vec::new(); |
| 89 | + parse_test_case!(data, $($tail)+); |
| 90 | + data |
| 91 | + } |
| 92 | + }; |
| 93 | +} |
| 94 | + |
| 95 | +#[tokio::test] |
| 96 | +async fn mask_test() { |
| 97 | + // These test data are taken directly from the official |
| 98 | + // gitlab-runner source, and are still formatted for Go. We use a |
| 99 | + // simple pair of macros to convert them into an appropriate Rust |
| 100 | + // data structure. |
| 101 | + let test_cases = test_cases![ |
| 102 | + "simple prefix masking": { |
| 103 | + input: "Lorem ipsum dolor sit amet, ex ea commodo glpat-imperdiet in voluptate velit esse", |
| 104 | + expected: "Lorem ipsum dolor sit amet, ex ea commodo glpat-[MASKED] in voluptate velit esse", |
| 105 | + }, |
| 106 | + "prefix at the end of the line": { |
| 107 | + input: "Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esseglpat-imperdiet", |
| 108 | + expected: "Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esseglpat-[MASKED]", |
| 109 | + }, |
| 110 | + "prefix at the beginning of the line": { |
| 111 | + input: "glpat-imperdiet Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse", |
| 112 | + expected: "glpat-[MASKED] Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse", |
| 113 | + }, |
| 114 | + "prefix inside of the line": { |
| 115 | + input: "esseglpat-imperdiet=_-. end Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit", |
| 116 | + expected: "esseglpat-[MASKED] end Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit", |
| 117 | + }, |
| 118 | + "two prefix concatenate": { |
| 119 | + input: "glpat-impglpat-erdiet Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse", |
| 120 | + expected: "glpat-[MASKED] Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse", |
| 121 | + }, |
| 122 | + "multiple packets pat masking": { |
| 123 | + input: "glpat|-imperdiet Lorem ipsum dolor sit amet, ex ea commodo gl|pat-imperdiet in voluptate velit esse", |
| 124 | + expected: "glpat-[MASKED] Lorem ipsum dolor sit amet, ex ea commodo glpat-[MASKED] in voluptate velit esse", |
| 125 | + }, |
| 126 | + "second multiple packets pat masking": { |
| 127 | + input: "glpat| -imperdiet Lorem ipsum dolor sit amet", |
| 128 | + expected: "glpat -imperdiet Lorem ipsum dolor sit amet", |
| 129 | + }, |
| 130 | + "long input": { |
| 131 | + input: "Lorglpat-ipsu dolor sit amglpat-t, consglpat-ctglpat-tur adipiscing glpat-lit, sglpat-d do glpat-iusmod tglpat-mpor incididunt ut laborglpat-=_ glpat-t dolorglpat-=_ magna aliqua.", |
| 132 | + expected: "Lorglpat-[MASKED] dolor sit amglpat-[MASKED], consglpat-[MASKED] adipiscing glpat-[MASKED], sglpat-[MASKED] do glpat-[MASKED] tglpat-[MASKED] incididunt ut laborglpat-[MASKED] glpat-[MASKED] dolorglpat-[MASKED] magna aliqua.", |
| 133 | + }, |
| 134 | + "multiple packets long input": { |
| 135 | + input: "Lorglpat-ipsu dolor sit amglp|at-t, consglpat-ctg|lpat-tur adipiscing glpat-lit, sglpat-|d do glpat-iusmod t|glpat-mpor incididunt ut |laborglpat-=_ glpat-t dolorglpat-=_ magna aliqua.", |
| 136 | + expected: "Lorglpat-[MASKED] dolor sit amglpat-[MASKED], consglpat-[MASKED] adipiscing glpat-[MASKED], sglpat-[MASKED] do glpat-[MASKED] tglpat-[MASKED] incididunt ut laborglpat-[MASKED] glpat-[MASKED] dolorglpat-[MASKED] magna aliqua.", |
| 137 | + }, |
| 138 | + "second long input": { |
| 139 | + input: "Lorglpat- ipsu dolor sit amglpat-t, consglpat-ctglpat-tur adipiscing glpat-lit, sglpat-d do glpat-iusmod tglpat-mpor incididunt ut laborglpat-=_ glpat-t dolorglpat-=_ magna aliqua.", |
| 140 | + expected: "Lorglpat- ipsu dolor sit amglpat-[MASKED], consglpat-[MASKED] adipiscing glpat-[MASKED], sglpat-[MASKED] do glpat-[MASKED] tglpat-[MASKED] incididunt ut laborglpat-[MASKED] glpat-[MASKED] dolorglpat-[MASKED] magna aliqua.", |
| 141 | + }, |
| 142 | + "custom prefix with default one at the beginning of the line": { |
| 143 | + prefixes: []string{"token-"}, |
| 144 | + input: "token-imperdiet Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse", |
| 145 | + expected: "token-[MASKED] Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse", |
| 146 | + }, |
| 147 | + "custom prefix with default one multiple packets long input": { |
| 148 | + prefixes: []string{"tok-"}, |
| 149 | + input: "Lortok-ipsu dolor sit amt|ok-t, cons-ctg|lpat-tur adipiscing tok-lit, stok-|d do tok-iusmod t|tok-mpor incididunt ut |labortok-=_ tok-t dolortok-=_ magna aliqua. Tglpat-llus orci ac auctor auguglpat-eee mauris auguglpat-wEr_ lorem", |
| 150 | + expected: "Lortok-[MASKED] dolor sit amtok-[MASKED], cons-ctglpat-[MASKED] adipiscing tok-[MASKED], stok-[MASKED] do tok-[MASKED] ttok-[MASKED] incididunt ut labortok-[MASKED] tok-[MASKED] dolortok-[MASKED] magna aliqua. Tglpat-[MASKED] orci ac auctor auguglpat-[MASKED] mauris auguglpat-[MASKED] lorem", |
| 151 | + }, |
| 152 | + "ignored eleventh prefix and more": { |
| 153 | + prefixes: []string{"mask1-", "mask2-", "mask3-", "mask4-", "mask5-", "mask6-", "mask7-", "mask8-", "mask9-", "mask10-", "mask11-"}, |
| 154 | + input: "Lormask1-ipsu dolor sit amm|ask2-t, cons-ctg|lpat-tur adipiscing mask5-lit, smask11-|d do mask7-iusmod t|glpat-mpor incididunt ut |labormask10-=_ mask9-t", |
| 155 | + expected: "Lormask1-[MASKED] dolor sit ammask2-[MASKED], cons-ctglpat-[MASKED] adipiscing mask5-[MASKED], smask11-d do mask7-[MASKED] tglpat-[MASKED] incididunt ut labormask10-=_ mask9-[MASKED]", |
| 156 | + }, |
| 157 | + "whitespaced prefixes": { |
| 158 | + prefixes: []string{" mask1- ", " mask2-", "mask3- ", "mask4-", "mask5-", "mask6-", "mask7-", "mask8-", "mask9-"}, |
| 159 | + input: "Lormask1-ipsu dolor sit amm|ask2-t, cons-ctg|lpat-tur adipiscing mask5-lit, smask11-|d do mask7-iusmod t|glpat-mpor incididunt ut |labormask10-=_ mask9-t", |
| 160 | + expected: "Lormask1-[MASKED] dolor sit ammask2-[MASKED], cons-ctglpat-[MASKED] adipiscing mask5-[MASKED], smask11-d do mask7-[MASKED] tglpat-[MASKED] incididunt ut labormask10-=_ mask9-[MASKED]", |
| 161 | + }, |
| 162 | + ]; |
| 163 | + |
| 164 | + let mock = GitlabRunnerMock::start().await; |
| 165 | + |
| 166 | + for t in test_cases { |
| 167 | + println!("{}", t.name); |
| 168 | + let mut log_job = mock.job_builder(t.name.to_string()); |
| 169 | + log_job.add_step( |
| 170 | + MockJobStepName::Script, |
| 171 | + vec!["dummy".to_string()], |
| 172 | + 3600, |
| 173 | + MockJobStepWhen::OnSuccess, |
| 174 | + false, |
| 175 | + ); |
| 176 | + |
| 177 | + log_job.add_token_prefix("glpat-".to_string()); |
| 178 | + for pfx in t.prefixes.iter().take(9) { |
| 179 | + log_job.add_token_prefix(pfx.to_string()); |
| 180 | + } |
| 181 | + |
| 182 | + log_job.add_artifact_paths(vec!["*".to_string()]); |
| 183 | + let log_job = log_job.build(); |
| 184 | + mock.enqueue_job(log_job.clone()); |
| 185 | + |
| 186 | + let dir = tempfile::tempdir().unwrap(); |
| 187 | + |
| 188 | + let (mut runner, layer) = Runner::new_with_layer( |
| 189 | + mock.uri(), |
| 190 | + mock.runner_token().to_string(), |
| 191 | + dir.path().to_path_buf(), |
| 192 | + ); |
| 193 | + |
| 194 | + let subscriber = Registry::default().with(layer); |
| 195 | + async { |
| 196 | + // Upload job comes first |
| 197 | + let inp = t.input.clone(); |
| 198 | + let got_job = runner |
| 199 | + .request_job(|_job| async move { Ok(MaskTest { log_text: inp }) }) |
| 200 | + .await |
| 201 | + .unwrap(); |
| 202 | + |
| 203 | + assert!(got_job); |
| 204 | + runner.wait_for_space(1).await; |
| 205 | + assert_eq!(MockJobState::Success, log_job.state()); |
| 206 | + |
| 207 | + let data = log_job.log(); |
| 208 | + println!( |
| 209 | + "IN: {}\n LEFT: {} RIGHT: {}", |
| 210 | + t.input, |
| 211 | + String::from_utf8_lossy(data.as_slice()), |
| 212 | + t.expected |
| 213 | + ); |
| 214 | + assert_eq!(data.as_slice(), t.expected.as_bytes()); |
| 215 | + } |
| 216 | + .with_subscriber(subscriber) |
| 217 | + .await; |
| 218 | + |
| 219 | + let data = { |
| 220 | + let raw = mock |
| 221 | + .get_job_artifact(log_job.id()) |
| 222 | + .expect("failed to get job artifact"); |
| 223 | + let mut z = |
| 224 | + ZipArchive::new(Cursor::new(raw)).expect("failed to open job artifact as zip"); |
| 225 | + let mut f = z |
| 226 | + .by_name("masked") |
| 227 | + .expect("failed to obtain masked log file"); |
| 228 | + let mut data = Vec::new(); |
| 229 | + f.read_to_end(&mut data) |
| 230 | + .expect("failed to read data from masked log file"); |
| 231 | + data |
| 232 | + }; |
| 233 | + |
| 234 | + println!( |
| 235 | + "IN: {}\n LEFT: {} RIGHT: {}", |
| 236 | + t.input, |
| 237 | + String::from_utf8_lossy(data.as_slice()), |
| 238 | + t.expected |
| 239 | + ); |
| 240 | + assert_eq!(data.as_slice(), t.expected.as_bytes()); |
| 241 | + } |
| 242 | +} |
0 commit comments