From 83b87cf9a77241e2fcf0dc8db9b5ada5497d80ad Mon Sep 17 00:00:00 2001 From: Jonathan Lee <107072447+jj22ee@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:02:59 -0700 Subject: [PATCH] Add `cp -r` support to cp-utility tool (#285) *Issue #, if available:* ADOT SDK instrumentation Docker Images only support `cp -a` copy command so that the [CWAgentOperator can extract](https://github.com/aws/amazon-cloudwatch-agent-operator/blob/main/pkg/instrumentation/nodejs.go#L65) the instrumentation SDK via `cp -a`, but upstream OTel Operator has [changed a few months ago](https://github.com/open-telemetry/opentelemetry-operator/commit/4cd6dcb3350da220eb54df738b4ae82d2168a0c2) to use `cp -r` instead of `-a` to copy files from the OTel SDK Instrumentation Images. Today, OTel Operator is not compatible with some of the ADOT SDKs, and in the future, CWAgent Operator may also change to use `cp -r` as well *Description of changes:* - Copy changes from Java's PR to add `cp -r` support - https://github.com/aws-observability/aws-otel-java-instrumentation/pull/843 - Modify the above `cp -r` support with bug fix implemented in Python's `cp -a` command - https://github.com/aws-observability/aws-otel-python-instrumentation/pull/214#discussion_r1653671950 *Testing:* - Tested on PetClinic Sample App's Python Services with latest OTel Operator + Collector ![image](https://github.com/user-attachments/assets/c08aec2b-01ce-46c4-abfd-27c5b4f16c6b) By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --- tools/cp-utility/src/main.rs | 120 +++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 5 deletions(-) diff --git a/tools/cp-utility/src/main.rs b/tools/cp-utility/src/main.rs index 6e140611b..83b69d2bb 100644 --- a/tools/cp-utility/src/main.rs +++ b/tools/cp-utility/src/main.rs @@ -28,6 +28,8 @@ enum CopyType { SingleFile, /// equivalent to cp -a Archive, + /// equivalent to cp -r + Recursive, } /// Encapsulate a copy operation @@ -42,10 +44,10 @@ struct CopyOperation { /// Parse command line arguments and transform into `CopyOperation` fn parse_args(args: Vec<&str>) -> io::Result { - if !(args.len() == 3 || args.len() == 4 && args[1].eq("-a")) { + if !(args.len() == 3 || (args.len() == 4 && (args[1] == "-a" || args[1] == "-r"))) { return Err(io::Error::new( io::ErrorKind::InvalidInput, - "Invalid parameters. Expected cp [-a] ", + "Invalid parameters. Expected cp [-a | -r] ", )); } @@ -53,7 +55,11 @@ fn parse_args(args: Vec<&str>) -> io::Result { return Ok(CopyOperation { source: PathBuf::from(args[2]), destination: PathBuf::from(args[3]), - copy_type: CopyType::Archive, + copy_type: match args[1] { + "-a" => CopyType::Archive, + "-r" => CopyType::Recursive, + _ => panic!("Invalid option. Expected -a or -r"), + }, }); } @@ -69,10 +75,40 @@ fn do_copy(operation: CopyOperation) -> io::Result<()> { match operation.copy_type { CopyType::Archive => copy_archive(&operation.source, &operation.destination)?, CopyType::SingleFile => fs::copy(&operation.source, &operation.destination).map(|_| ())?, + CopyType::Recursive => copy_recursive(&operation.source, &operation.destination)?, }; Ok(()) } +fn copy_recursive(source: &Path, dest: &Path) -> io::Result<()> { + let mut stack = VecDeque::new(); + stack.push_back((source.to_path_buf(), dest.to_path_buf())); + while let Some((current_source, current_dest)) = stack.pop_back() { + if current_source.is_dir() { + if !current_dest.exists() { + fs::create_dir(¤t_dest)?; + } + for entry in fs::read_dir(current_source)? { + let next_source = entry?.path(); + let next_dest = + current_dest + .clone() + .join(next_source.file_name().ok_or(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid source file", + ))?); + stack.push_back((next_source, next_dest)); + } + } else if current_source.is_symlink() { + // Follow symbolic links as regular files + fs::copy(current_source, current_dest)?; + } else if current_source.is_file() { + fs::copy(current_source, current_dest)?; + } + } + Ok(()) +} + // Execute the recursive type of copy operation fn copy_archive(source: &Path, dest: &Path) -> io::Result<()> { let mut stack = VecDeque::new(); @@ -100,7 +136,6 @@ fn copy_archive(source: &Path, dest: &Path) -> io::Result<()> { fs::copy(current_source, current_dest)?; } } - Ok(()) } @@ -163,7 +198,7 @@ mod tests { fn parser_failure() { // prepare let inputs = vec![ - vec!["cp", "-r", "foo.txt", "bar.txt"], + vec!["cp", "-r", "foo.txt", "bar.txt", "foo1.txt"], vec!["cp", "-a", "param1", "param2", "param3"], vec!["cp", "param1", "param2", "param3"], ]; @@ -177,6 +212,24 @@ mod tests { } } + #[test] + fn parser_correct() { + // prepare + let inputs = vec![ + vec!["cp", "-r", "foo.txt", "bar.txt"], + vec!["cp", "-a", "param1", "param2"], + vec!["cp", "param1", "param2"], + ]; + + for input in inputs.into_iter() { + // act + let result = parse_args(input.clone()); + + // assert + assert!(result.is_ok(), "input should fail {:?}", input); + } + } + #[test] fn test_copy_single() { // prepare @@ -220,6 +273,51 @@ mod tests { assert!(result.is_err()); } + #[test] + fn test_copy_recursive() { + // prepare + let tempdir = tempfile::tempdir().unwrap(); + let test_base = tempdir.path().to_path_buf(); + ["foo", "foo/foo0", "foo/foo1", "foo/bar"] + .iter() + .for_each(|x| create_dir(&test_base, x)); + let files = [ + "foo/file1.txt", + "foo/file2.txt", + "foo/foo1/file3.txt", + "foo/bar/file4.txt", + ]; + files.iter().for_each(|x| create_file(&test_base, x)); + [("foo/symlink1.txt", "./file1.txt")] + .iter() + .for_each(|(x, y)| create_symlink(&test_base, x, y)); + + // act + let recursive_copy = CopyOperation { + copy_type: CopyType::Recursive, + source: test_base.join("foo"), + destination: test_base.join("bar"), + }; + do_copy(recursive_copy).unwrap(); + + // assert + files.iter().for_each(|x| { + assert_same_file( + &test_base.join(x), + &test_base.join(x.replace("foo/", "bar/")), + ) + }); + assert_same_file( + &test_base.join("foo/symlink1.txt"), + &test_base.join("bar/symlink1.txt"), + ); + // recursive copy will treat symlink as a file + assert_recursive_same_link( + &test_base.join("foo/symlink1.txt"), + &test_base.join("bar/symlink1.txt"), + ) + } + #[test] fn test_copy_archive() { // prepare @@ -342,4 +440,16 @@ mod tests { assert_eq!(fs::read_link(source).unwrap(), fs::read_link(dest).unwrap()); } + + fn assert_recursive_same_link(source: &Path, dest: &Path) { + assert!(source.exists()); + assert!(dest.exists()); + assert!(source.is_symlink()); + assert!(dest.is_file()); + + assert_eq!( + fs::read_to_string(source).unwrap(), + fs::read_to_string(dest).unwrap() + ); + } }