From 0e7acd27cf52641f7d13eaac386ed1bff6302a53 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Sat, 18 Oct 2025 18:48:24 +0100 Subject: [PATCH 1/3] fix: return ExitCode throughout rather than using process exit for pact-broker-cli --- Cargo.lock | 19 ++-- Cargo.toml | 4 +- examples/fail.json | 2 +- examples/verifier_example-rust.sh | 2 +- src/bin.rs | 73 ++++++++---- src/cli/pact_broker_ruby.rs | 182 +++++++++++++----------------- 6 files changed, 146 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2ba524..03a9bbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2027,7 +2027,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.9.2", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2135,9 +2135,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -2176,9 +2176,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" dependencies = [ "cc", "libc", @@ -2228,8 +2228,7 @@ dependencies = [ [[package]] name = "pact-broker-cli" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c7cd07df42619a3922cdd5246fa134aaf90ebad538030c34641b7c92a6276" +source = "git+https://github.com/pact-foundation/pact-broker-cli?branch=fix%2Fpropogate-exit-code#736993fe8775abdae168c81d2ef3764baaa184b5" dependencies = [ "anyhow", "base64 0.22.1", @@ -3401,11 +3400,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.4", "core-foundation 0.9.4", "core-foundation-sys", "libc", diff --git a/Cargo.toml b/Cargo.toml index d985338..2605544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,12 +31,12 @@ home = "0.5.5" # pact-plugin-cli = { version = "*", path = "/Users/saf/dev/pact-foundation/pact-plugins/cli"} # pact_verifier_cli = { version = "*", path = "/Users/saf/dev/pact-foundation/pact-reference/rust/pact_verifier_cli"} # pact-broker-cli = { version = "*", path = "/Users/saf/dev/pact-foundation/pact-broker-cli"} -# pact-broker-cli = { version = "*", git = "https://github.com/pact-foundation/pact-broker-cli", branch = "main"} +pact-broker-cli = { version = "*", git = "https://github.com/pact-foundation/pact-broker-cli", branch = "fix/propogate-exit-code"} pact-stub-server = { version = "*", git = "https://github.com/YOU54F/pact-stub-server.git", branch = "feat/cli_as_lib"} pact_mock_server_cli = { version = "2.0.0-beta.1", git = "https://github.com/YOU54F/pact-core-mock-server.git", branch = "feat/add-pact-specification-option-to-cli"} pact-plugin-cli = { version = "*", git = "https://github.com/pact-foundation/pact-plugins", branch = "feat/cli_as_lib"} pact_verifier_cli = { version = "*", git = "https://github.com/YOU54F/pact-reference", branch = "feat/cli_as_lib"} -pact-broker-cli = { version = "0.2.1" } +# pact-broker-cli = { version = "0.2.1" } [dev-dependencies] trycmd = "0.15.0" diff --git a/examples/fail.json b/examples/fail.json index 8dc1490..21a63fc 100644 --- a/examples/fail.json +++ b/examples/fail.json @@ -1,6 +1,6 @@ { "consumer": { - "name": "me" + "name": "mee" }, "provider": { "name": "they" diff --git a/examples/verifier_example-rust.sh b/examples/verifier_example-rust.sh index 11e427b..adb39f9 100755 --- a/examples/verifier_example-rust.sh +++ b/examples/verifier_example-rust.sh @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -BIN = ENV['BIN'] || 'pact-provider-verifier' +BIN = ENV['BIN'] || 'pact' puts "=> Starting API" pipe = IO.popen("ruby examples/api.rb") diff --git a/src/bin.rs b/src/bin.rs index 80f429c..8308b2e 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -8,37 +8,71 @@ use std::{process::ExitCode, str::FromStr}; use crate::cli::pact_broker_docker; use crate::cli::pact_broker_ruby; -pub fn main() -> Result<(), ExitCode> { +pub fn main() -> ExitCode { let app = cli::build_cli(); - let cloned_app = app.clone(); - match app.clone().try_get_matches() { + let res = match app.clone().try_get_matches() { Ok(results) => match results.subcommand() { - Some(("broker", args)) | Some(("pactflow", args)) => { + Some(("broker", args)) => { // if args subcommand is docker or standalone, offset to those subcommands let subcommand = args.subcommand_name(); match subcommand { Some("docker") => { let docker_args = args.subcommand_matches("docker").unwrap(); - return pact_broker_docker::run(docker_args); + return match pact_broker_docker::run(docker_args) { + Ok(_) => ExitCode::SUCCESS, + Err(code) => code, + }; // return Ok(()); } Some("ruby") => { let standalone_args = args.subcommand_matches("ruby").unwrap(); - pact_broker_ruby::run(standalone_args); - return Ok(()); + let res = pact_broker_ruby::run(standalone_args); + match res { + Ok(_) => return ExitCode::SUCCESS, + Err(err) => { + return { + eprintln!("{}", err); + ExitCode::from(1) + } + } + } } _ => {} } let raw_args: Vec = std::env::args().collect(); let matches_result = Ok(args.clone()); - pact_broker_cli::handle_matches(&matches_result, Some(raw_args)) + match pact_broker_cli::handle_matches(&matches_result, Some(raw_args)) { + Ok(()) => Ok(()), + Err(e) => Err(e), + } + } + Some(("pactflow", args)) => { + match pact_broker_cli::cli::pactflow_client::run(args, std::env::args().collect()) { + Ok(_) => Ok(()), + Err(error) => Err(ExitCode::from(error as u8)), + } + } + Some(("stub", args)) => { + let res = pact_stub_server_cli::process_stub_command(args); + res + } + Some(("completions", args)) => { + let res = generate_completions(args); + res + } + Some(("plugin", args)) => { + let res = pact_plugin_cli::process_plugin_command(args); + res + } + Some(("mock", args)) => { + let res = pact_mock_server_cli::process_mock_command(args); + res + } + Some(("verifier", args)) => { + let res = pact_verifier_cli::process_verifier_command(args); + res } - Some(("stub", args)) => pact_stub_server_cli::process_stub_command(args), - Some(("completions", args)) => generate_completions(args), - Some(("plugin", args)) => pact_plugin_cli::process_plugin_command(args), - Some(("mock", args)) => pact_mock_server_cli::process_mock_command(args), - Some(("verifier", args)) => pact_verifier_cli::process_verifier_command(args), _ => { cli::build_cli().print_help().unwrap(); Ok(()) @@ -47,16 +81,12 @@ pub fn main() -> Result<(), ExitCode> { Err(err) => match err.kind() { ErrorKind::DisplayHelp => { - // let _ = err.print(); err.exit(); } ErrorKind::DisplayVersion => { let error_message = err.render().to_string(); let versions = [ - ( - "pact-verifier", - pact_verifier_cli::print_version as fn(), - ), + ("pact-verifier", pact_verifier_cli::print_version as fn()), ("pact-mock", pact_mock_server_cli::print_version as fn()), ("pact-stub", pact_stub_server_cli::print_version as fn()), ]; @@ -64,14 +94,17 @@ pub fn main() -> Result<(), ExitCode> { if error_message.contains(name) { print_fn(); println!(); - return Ok(()); + return ExitCode::SUCCESS; } } - // let _ = err.print(); err.exit(); } _ => err.exit(), }, + }; + match res { + Ok(_) => ExitCode::SUCCESS, + Err(code) => code, } } diff --git a/src/cli/pact_broker_ruby.rs b/src/cli/pact_broker_ruby.rs index 799a56d..7d210be 100644 --- a/src/cli/pact_broker_ruby.rs +++ b/src/cli/pact_broker_ruby.rs @@ -1,3 +1,5 @@ +use clap::error::{ContextKind, ContextValue, ErrorKind}; +use clap::{Arg, ArgMatches, Command, Error}; use std::{ env, fs, io::{Read, Write}, @@ -5,8 +7,6 @@ use std::{ process::{Command as Cmd, ExitStatus}, }; -use clap::{Arg, ArgMatches, Command}; - pub fn add_ruby_broker_subcommand() -> Command { Command::new("ruby") .about("Install & Run the Pact Broker using system Ruby in $HOME/.pact/pact-broker") @@ -117,39 +117,18 @@ run app Ok(()) } -pub fn run(args: &ArgMatches) { - let home_dir = home::home_dir().unwrap_or_else(|| { - println!("Could not determine home directory."); - std::process::exit(1); - }); +pub fn run(args: &ArgMatches) -> Result<(), String> { + let home_dir = home::home_dir().ok_or("Could not determine home directory.")?; let broker_dir = home_dir.join(".pact/pact-broker"); let pid_file_path = broker_dir.join("broker.pid"); match args.subcommand() { Some(("start", args)) => { - // Check Ruby version - if let Err(msg) = check_ruby_version() { - println!("⚠️ {}", msg); - println!("Please install Ruby >= 3.1 and ensure it is on your PATH."); - std::process::exit(1); - } - - // check bundler version - if let Err(msg) = check_bundler_installed() { - println!("⚠️ {}", msg); - println!( - "Please install Bundler (gem install bundler) and ensure it is on your PATH." - ); - std::process::exit(1); - } + check_ruby_version()?; + check_bundler_installed()?; + write_gemfile_and_config(&broker_dir) + .map_err(|e| format!("Failed to write Gemfile/config.ru: {}", e))?; - // Write Gemfile and config.ru - if let Err(e) = write_gemfile_and_config(&broker_dir) { - println!("Failed to write Gemfile/config.ru: {}", e); - std::process::exit(1); - } - - // Run bundle install println!("🚀 Running bundle install in {}", broker_dir.display()); let status = Cmd::new("ruby") .arg("-S") @@ -157,15 +136,15 @@ pub fn run(args: &ArgMatches) { .arg("install") .current_dir(&broker_dir) .status() - .expect("Failed to run bundle install"); + .map_err(|_| "Failed to run bundle install".to_string())?; if !status.success() { - println!("⚠️ bundle install failed. Please check your Ruby and Bundler setup."); - std::process::exit(1); + return Err( + "⚠️ bundle install failed. Please check your Ruby and Bundler setup." + .to_string(), + ); } - // Prepare to start the broker println!("🚀 Starting Pact Broker with Puma..."); - // Use 'ruby -S bundle' for better cross-platform compatibility let mut child_cmd = Cmd::new("ruby"); child_cmd.arg("-S").arg("bundle"); child_cmd @@ -175,84 +154,80 @@ pub fn run(args: &ArgMatches) { .arg(&pid_file_path) .current_dir(&broker_dir); - if let Ok(mut child) = child_cmd.spawn() { - let pid = child.id(); - println!("🚀 Pact Broker is running on http://localhost:9292"); - println!("🚀 PID: {}", pid); - println!("🚀 PID file: {}", pid_file_path.display()); - let mut pid_file_contents = String::from("unknown"); - while !pid_file_contents.chars().all(char::is_numeric) { + let mut child = child_cmd + .spawn() + .map_err(|_| "Failed to start Pact Broker".to_string())?; + let pid = child.id(); + println!("🚀 Pact Broker is running on http://localhost:9292"); + println!("🚀 PID: {}", pid); + println!("🚀 PID file: {}", pid_file_path.display()); + let mut pid_file_contents = String::from("unknown"); + while !pid_file_contents.chars().all(char::is_numeric) { + std::thread::sleep(std::time::Duration::from_secs(1)); + pid_file_contents = + fs::read_to_string(&pid_file_path).unwrap_or_else(|_| String::from("unknown")); + } + println!("Traveling Broker PID: {}", pid_file_contents); + + let detach = args.get_flag("detach"); + if detach { + println!("🚀 Running in the background"); + return Ok(()); + } else { + while child.try_wait().unwrap().is_none() { std::thread::sleep(std::time::Duration::from_secs(1)); - pid_file_contents = fs::read_to_string(&pid_file_path) - .unwrap_or_else(|_| String::from("unknown")); } - println!("Traveling Broker PID: {}", pid_file_contents); - - // we should support a detach flag to run the broker in the background - let detach = args.get_flag("detach"); - if detach { - println!("🚀 Running in the background"); - std::process::exit(0); - } else { - while child.try_wait().unwrap().is_none() { - std::thread::sleep(std::time::Duration::from_secs(1)); + let _ = child.kill(); + let pid_file = fs::File::open(&pid_file_path); + match pid_file { + Ok(mut file) => { + let mut pid = String::new(); + file.read_to_string(&mut pid).unwrap(); + let pid = pid.trim().parse::().unwrap(); + println!("🚀 Stopping Pact Broker with PID: {}", pid); + #[cfg(windows)] + Cmd::new("taskkill") + .arg("/F") + .arg("/PID") + .arg(pid.to_string()) + .output() + .expect("Failed to stop the process"); } - let _ = child.kill(); - let pid_file = fs::File::open(&pid_file_path); - match pid_file { - Ok(mut file) => { - let mut pid = String::new(); - file.read_to_string(&mut pid).unwrap(); - let pid = pid.trim().parse::().unwrap(); - println!("🚀 Stopping Pact Broker with PID: {}", pid); - #[cfg(windows)] - Cmd::new("taskkill") - .arg("/F") - .arg("/PID") - .arg(pid.to_string()) - .output() - .expect("Failed to stop the process"); - } - Err(_) => { - println!("PID file not found"); - } + Err(_) => { + println!("PID file not found"); } - let _ = fs::remove_file(&pid_file_path); - std::process::exit(0); } - } else { - println!("Failed to start Pact Broker"); - std::process::exit(1); + let _ = fs::remove_file(&pid_file_path); + return Ok(()); } } Some(("stop", _args)) => { - if let Ok(mut file) = fs::File::open(&pid_file_path) { - let mut pid = String::new(); - file.read_to_string(&mut pid).unwrap(); - let pid = pid.trim().parse::().unwrap(); - println!("🚀 Stopping Pact Broker with PID: {}", pid); - #[cfg(windows)] - Cmd::new("taskkill") - .arg("/F") - .arg("/PID") - .arg(pid.to_string()) - .output() - .expect("⚠️ Failed to stop the broker"); - - #[cfg(not(windows))] - Cmd::new("kill") - .arg(pid.to_string()) - .output() - .expect("⚠️ Failed to stop the broker"); - let _ = fs::remove_file(&pid_file_path); - println!("🛑 Pact Broker stopped"); - std::process::exit(0); - } else { - println!("⚠️ Pact Broker is not running"); - std::process::exit(1); - } + let mut file = fs::File::open(&pid_file_path) + .map_err(|_| "⚠️ Pact Broker is not running".to_string())?; + let mut pid = String::new(); + file.read_to_string(&mut pid).unwrap(); + let pid = pid.trim().parse::().unwrap(); + println!("🚀 Stopping Pact Broker with PID: {}", pid); + #[cfg(windows)] + Cmd::new("taskkill") + .arg("/F") + .arg("/PID") + .arg(pid.to_string()) + .output() + .expect("⚠️ Failed to stop the broker"); + + #[cfg(not(windows))] + Cmd::new("kill") + .arg(pid.to_string()) + .output() + .expect("⚠️ Failed to stop the broker"); + let _ = fs::remove_file(&pid_file_path); + println!("🛑 Pact Broker stopped"); + Ok(()) } Some(("remove", _args)) => { + let matches = add_ruby_broker_subcommand().get_matches_from(["ruby", "stop"]); + let _ = run(&matches); if let Ok(metadata) = fs::metadata(&broker_dir) { if metadata.is_dir() { if let Err(err) = fs::remove_dir_all(&broker_dir) { @@ -264,6 +239,7 @@ pub fn run(args: &ArgMatches) { } else { println!("broker_dir {} not found", broker_dir.display()); } + Ok(()) } Some(("info", _args)) => { fn check_directory_exists(directory: &Path) -> bool { @@ -300,9 +276,11 @@ pub fn run(args: &ArgMatches) { let pact_broker_pid_exists = get_pid_from_file(&pid_file_path); println!("Pact broker pid: {:?}", pact_broker_pid_exists); + Ok(()) } _ => { println!("⚠️ No option provided, try running ruby --help"); + Ok(()) } } } From 8322b5e0ce3674728fbb63cc8cd7f176eb742a78 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Sun, 19 Oct 2025 00:04:23 +0100 Subject: [PATCH 2/3] feat: add otel (traces/logging) to stdout / otlp exporters --- Cargo.lock | 216 +++++++++++++++++++++++++++++----- Cargo.toml | 17 ++- README.md | 42 ++++++- examples/stub_example-rust.sh | 2 +- run.sh | 1 + src/bin.rs | 125 +++++++++++++++++--- src/cli.rs | 55 ++++++++- src/cli/otel.rs | 126 ++++++++++++++++++++ 8 files changed, 534 insertions(+), 50 deletions(-) create mode 100644 src/cli/otel.rs diff --git a/Cargo.lock b/Cargo.lock index 03a9bbb..b00c611 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,17 +1611,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.9.0" @@ -2187,6 +2176,109 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.16", + "tracing", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http 1.3.1", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" +dependencies = [ + "http 1.3.1", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost 0.14.1", + "reqwest", + "thiserror 2.0.16", + "tokio", + "tonic 0.14.2", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost 0.14.1", + "tonic 0.14.2", + "tonic-prost", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" + +[[package]] +name = "opentelemetry-stdout" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc8887887e169414f637b18751487cce4e095be787d23fad13c454e2fb1b3811" +dependencies = [ + "chrono", + "opentelemetry", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.2", + "thiserror 2.0.16", +] + [[package]] name = "os_info" version = "3.12.0" @@ -2217,18 +2309,29 @@ dependencies = [ "clap_complete", "home", "indexmap 1.9.3", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-http", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry-stdout", + "opentelemetry_sdk", "pact-broker-cli", "pact-plugin-cli", "pact-stub-server", "pact_mock_server_cli", "pact_verifier_cli", + "tokio", + "tracing", + "tracing-subscriber", "trycmd", ] [[package]] name = "pact-broker-cli" -version = "0.2.1" -source = "git+https://github.com/pact-foundation/pact-broker-cli?branch=fix%2Fpropogate-exit-code#736993fe8775abdae168c81d2ef3764baaa184b5" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e4d02cf56a0f725db45fbbb19b498247ea5da46920669eeb37396447ffa11d" dependencies = [ "anyhow", "base64 0.22.1", @@ -2309,7 +2412,7 @@ dependencies = [ "md5 0.7.0", "os_info", "pact_models", - "prost", + "prost 0.13.5", "prost-types", "regex", "reqwest", @@ -2321,7 +2424,7 @@ dependencies = [ "tar", "tokio", "toml", - "tonic", + "tonic 0.13.1", "tonic-build", "tracing", "tracing-core", @@ -2854,7 +2957,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +dependencies = [ + "bytes", + "prost-derive 0.14.1", ] [[package]] @@ -2870,7 +2983,7 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost", + "prost 0.13.5", "prost-types", "regex", "syn 2.0.106", @@ -2890,13 +3003,26 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "prost-derive" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "prost-types" version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" dependencies = [ - "prost", + "prost 0.13.5", ] [[package]] @@ -4015,29 +4141,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio 1.0.4", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2 0.6.0", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.1", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -4188,7 +4311,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost", + "prost 0.13.5", "socket2 0.5.6", "tokio", "tokio-stream", @@ -4198,6 +4321,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.7.0", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "rustls-native-certs", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tonic-build" version = "0.13.1" @@ -4212,6 +4363,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "tonic-prost" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +dependencies = [ + "bytes", + "prost 0.14.1", + "tonic 0.14.2", +] + [[package]] name = "tower" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index 2605544..a75127c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,12 +31,25 @@ home = "0.5.5" # pact-plugin-cli = { version = "*", path = "/Users/saf/dev/pact-foundation/pact-plugins/cli"} # pact_verifier_cli = { version = "*", path = "/Users/saf/dev/pact-foundation/pact-reference/rust/pact_verifier_cli"} # pact-broker-cli = { version = "*", path = "/Users/saf/dev/pact-foundation/pact-broker-cli"} -pact-broker-cli = { version = "*", git = "https://github.com/pact-foundation/pact-broker-cli", branch = "fix/propogate-exit-code"} +# pact-broker-cli = { version = "*", git = "https://github.com/pact-foundation/pact-broker-cli", branch = "fix/propogate-exit-code"} pact-stub-server = { version = "*", git = "https://github.com/YOU54F/pact-stub-server.git", branch = "feat/cli_as_lib"} pact_mock_server_cli = { version = "2.0.0-beta.1", git = "https://github.com/YOU54F/pact-core-mock-server.git", branch = "feat/add-pact-specification-option-to-cli"} pact-plugin-cli = { version = "*", git = "https://github.com/pact-foundation/pact-plugins", branch = "feat/cli_as_lib"} pact_verifier_cli = { version = "*", git = "https://github.com/YOU54F/pact-reference", branch = "feat/cli_as_lib"} -# pact-broker-cli = { version = "0.2.1" } +pact-broker-cli = { version = "0.2.2" } + +## Otel + +tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "time"] } +opentelemetry = "0.31.0" +opentelemetry_sdk = "0.31.0" +opentelemetry-stdout = { version = "0.31.0", features = ["trace", "logs"] } +opentelemetry-http = "0.31.0" +opentelemetry-otlp = { version = "0.31.0", features = ["trace", "tls", "tls-roots", "grpc-tonic"] } +opentelemetry-semantic-conventions = "0.31.0" +opentelemetry-appender-tracing = "0.31.0" +tracing = { version= "0.1.41", features = ["std"]} +tracing-subscriber = { version = "0.3.20", features = ["env-filter","registry", "std", "fmt"] } [dev-dependencies] trycmd = "0.15.0" diff --git a/README.md b/README.md index 626600f..a1f5f42 100644 --- a/README.md +++ b/README.md @@ -220,8 +220,8 @@ Commands: Options: -y, --yes Automatically answer Yes for all prompts -d, --debug Enable debug level logs - --log-level Set the log level (none, off, error, warn, info, debug, trace) [default: off] [possible values: off, none, error, warn, info, debug, trace] -t, --trace Enable trace level logs + --log-level Set the log level (none, off, error, warn, info, debug, trace) [default: off] [possible values: off, none, error, warn, info, debug, trace] -v, --version Print CLI version -h, --help Print help @@ -246,12 +246,12 @@ Commands: Options: --help Print help and exit - --log-level Set the log level (none, off, error, warn, info, debug, trace) [default: off] [possible values: off, none, error, warn, info, debug, trace] -v, --version Print version information and exit -p, --port port the master mock server runs on (defaults to 8080) -h, --host hostname the master mock server runs on (defaults to localhost) -l, --loglevel Log level for mock servers to write to the log file (defaults to info) [possible values: error, warn, info, debug, trace, none] --no-term-log Turns off using terminal ANSI escape codes + --log-level Set the log level (none, off, error, warn, info, debug, trace) [default: off] [possible values: off, none, error, warn, info, debug, trace] --no-file-log Do not log to an output file ``` @@ -266,8 +266,8 @@ Usage: pact verifier [OPTIONS] Options: --help Print help and exit - --log-level Set the log level (none, off, error, warn, info, debug, trace) [default: off] [possible values: off, none, error, warn, info, debug, trace] -v, --version Print version information and exit + --log-level Set the log level (none, off, error, warn, info, debug, trace) [default: off] [possible values: off, none, error, warn, info, debug, trace] Logging options: -l, --loglevel Log level to emit log events at (defaults to warn) [possible values: error, warn, info, debug, trace, none] @@ -376,8 +376,6 @@ Options: Log level (defaults to info) [default: info] [possible values: error, warn, info, debug, trace, none] -f, --file Pact file to load (can be repeated) - --log-level - Set the log level (none, off, error, warn, info, debug, trace) [default: off] [possible values: off, none, error, warn, info, debug, trace] -d, --dir Directory of pact files to load (can be repeated) -e, --extension @@ -386,6 +384,8 @@ Options: URL of pact file to fetch (can be repeated) -b, --broker-url URL of the pact broker to fetch pacts from [env: PACT_BROKER_BASE_URL=] + --log-level + Set the log level (none, off, error, warn, info, debug, trace) [default: off] [possible values: off, none, error, warn, info, debug, trace] --user User and password to use when fetching pacts from URLS or Pact Broker in user:password form -t, --token @@ -415,6 +415,38 @@ Options: ``` +## Open Telemetry + +The `pact` cli supports native opentelemetry for traces and application logs. + +It is opt-in via `--enable-otel`, and by default will output `traces` to `stdout`. + +Application logs, can optionally be exported with `--enable-otel-logs`, output is controlled by `--log-level`, which currently defaults to `none` + +By default, `--otel-exporter-endpoint` will route to `http://localhost:4318` and will add `/v1/traces` to any provided endpoint.. + +```sh +Options: + --enable-otel + Enable OpenTelemetry tracing + --enable-otel-logs + Enable OpenTelemetry logging + --otel-exporter + The OpenTelemetry exporter to use (stdout, otlp) [default: stdout] [env: OTEL_TRACES_EXPORTER=] [possible values: stdout, otlp] + --otel-exporter-endpoint + The endpoint to use for the OTLP exporter (required if --otel-exporter=otlp) [env: OTEL_EXPORTER_OTLP_ENDPOINT=] + --otel-exporter-protocol + The protocol to use for the OTLP exporter (http/protobuf, grpc) [env: OTEL_EXPORTER_OTLP_PROTOCOL=] [default: http] [possible values: http, http/protobuf, grpc] +``` + +Standard otel environemnt variables are followed + +```sh +export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" +export OTEL_TRACES_EXPORTER="otlp" +export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" +``` + ## Ecosystem The ecosystem is built up of multiple different crates, each which serve a different purpose. diff --git a/examples/stub_example-rust.sh b/examples/stub_example-rust.sh index 8e9b153..4c47273 100755 --- a/examples/stub_example-rust.sh +++ b/examples/stub_example-rust.sh @@ -19,6 +19,6 @@ echo '' # AFTER SUITE stop mock service # this would be invoked by the test framework -kill -2 $pid +kill -9 $pid while [ kill -0 $pid 2> /dev/null ]; do sleep 0.5; done \ No newline at end of file diff --git a/run.sh b/run.sh index ea54a3c..7e72601 100755 --- a/run.sh +++ b/run.sh @@ -77,6 +77,7 @@ ${BIN} broker delete-branch --branch bar --pacticipant foo ${BIN} broker describe-pacticipant --name foo ${BIN} broker generate-uuid +unset PACT_BROKER_BASE_URL ./examples/mock_example-rust.sh ./examples/stub_example-rust.sh ./examples/verifier_example-rust.sh diff --git a/src/bin.rs b/src/bin.rs index 8308b2e..5d34b80 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -5,10 +5,59 @@ use clap_complete::{generate_to, Shell}; use std::{process::ExitCode, str::FromStr}; +use crate::cli::otel::{capture_telemetry, init_tracer}; use crate::cli::pact_broker_docker; use crate::cli::pact_broker_ruby; pub fn main() -> ExitCode { + let app = cli::build_cli(); + let matches = app.clone().try_get_matches(); + + let ( + enable_otel, + enable_otel_logs, + otel_exporter, + otel_exporter_endpoint, + otel_exporter_protocol, + log_level, + ) = match &matches { + Ok(m) => ( + m.get_flag("enable-otel"), + m.get_flag("enable-otel-logs"), + m.get_one::("otel-exporter"), + m.get_one::("otel-exporter-endpoint"), + m.get_one::("otel-exporter-protocol"), + m.get_one::("log-level") + .and_then(|lvl| lvl.parse::().ok()), + ), + Err(_) => (false, false, None, None, None, None), + }; + let otel_config = if enable_otel { + Some(crate::cli::otel::OtelConfig { + exporter: otel_exporter.cloned(), + endpoint: otel_exporter_endpoint.cloned(), + protocol: otel_exporter_protocol.cloned(), + }) + } else { + None + }; + let mut tracer_provider = None; + let mut log_provider = None; + let rt: tokio::runtime::Runtime = + tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); + rt.block_on(async { + tracer_provider = if enable_otel { + Some(init_tracer(otel_config.unwrap())) + } else { + None + }; + log_provider = if enable_otel_logs { + Some(crate::cli::otel::init_logs(log_level)).unwrap() + } else { + None + }; + }); + let app = cli::build_cli(); let res = match app.clone().try_get_matches() { Ok(results) => match results.subcommand() { @@ -19,23 +68,35 @@ pub fn main() -> ExitCode { Some("docker") => { let docker_args = args.subcommand_matches("docker").unwrap(); return match pact_broker_docker::run(docker_args) { - Ok(_) => ExitCode::SUCCESS, - Err(code) => code, + Ok(_) => { + capture_telemetry(&std::env::args().collect::>(), 0, None); + ExitCode::SUCCESS + } + Err(code) => { + capture_telemetry(&std::env::args().collect::>(), 1, None); + code + } }; // return Ok(()); } Some("ruby") => { let standalone_args = args.subcommand_matches("ruby").unwrap(); let res = pact_broker_ruby::run(standalone_args); - match res { - Ok(_) => return ExitCode::SUCCESS, + return match res { + Ok(_) => { + capture_telemetry(&std::env::args().collect::>(), 0, None); + ExitCode::SUCCESS + } Err(err) => { - return { - eprintln!("{}", err); - ExitCode::from(1) - } + println!("{}", err); + capture_telemetry( + &std::env::args().collect::>(), + 1, + Some(err.as_str()), + ); + ExitCode::from(1) } - } + }; } _ => {} } @@ -43,34 +104,51 @@ pub fn main() -> ExitCode { let raw_args: Vec = std::env::args().collect(); let matches_result = Ok(args.clone()); match pact_broker_cli::handle_matches(&matches_result, Some(raw_args)) { - Ok(()) => Ok(()), - Err(e) => Err(e), + Ok(()) => { + capture_telemetry(&std::env::args().collect::>(), 0, None); + Ok(()) + } + Err(e) => { + capture_telemetry(&std::env::args().collect::>(), 1, None); + Err(e) + } } } Some(("pactflow", args)) => { match pact_broker_cli::cli::pactflow_client::run(args, std::env::args().collect()) { - Ok(_) => Ok(()), - Err(error) => Err(ExitCode::from(error as u8)), + Ok(_) => { + capture_telemetry(&std::env::args().collect::>(), 0, None); + Ok(()) + } + Err(error) => { + capture_telemetry(&std::env::args().collect::>(), error, None); + Err(ExitCode::from(error as u8)) + } } } Some(("stub", args)) => { let res = pact_stub_server_cli::process_stub_command(args); + capture_telemetry(&std::env::args().collect::>(), 0, None); res } Some(("completions", args)) => { let res = generate_completions(args); + capture_telemetry(&std::env::args().collect::>(), 0, None); res } Some(("plugin", args)) => { let res = pact_plugin_cli::process_plugin_command(args); + capture_telemetry(&std::env::args().collect::>(), 0, None); res } Some(("mock", args)) => { let res = pact_mock_server_cli::process_mock_command(args); + capture_telemetry(&std::env::args().collect::>(), 0, None); res } Some(("verifier", args)) => { let res = pact_verifier_cli::process_verifier_command(args); + capture_telemetry(&std::env::args().collect::>(), 0, None); res } _ => { @@ -99,9 +177,28 @@ pub fn main() -> ExitCode { } err.exit(); } - _ => err.exit(), + + _ => { + capture_telemetry( + &std::env::args().collect::>(), + err.exit_code(), + Some(&err.to_string()), + ); + + err.exit() + } }, }; + if let Some(tracer_provider) = tracer_provider { + if enable_otel { + let _ = tracer_provider.shutdown(); + } + } + if let Some(log_provider) = log_provider { + if enable_otel_logs { + let _ = log_provider.shutdown(); + } + } match res { Ok(_) => ExitCode::SUCCESS, Err(code) => code, diff --git a/src/cli.rs b/src/cli.rs index e729a35..65b9d09 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,15 +1,17 @@ -use clap::{command, Arg, Command, CommandFactory}; +use clap::{Arg, Command, CommandFactory}; use crate::cli::{ pact_broker_docker::add_docker_broker_subcommand, pact_broker_ruby::add_ruby_broker_subcommand, }; +pub mod otel; pub mod pact_broker_docker; pub mod pact_broker_ruby; pub fn build_cli() -> Command { let app = Command::new("pact") .about("Pact consolidated CLI - pact_core_mock_server, pact_verifier, pact-stub-server, pact-plugin-cli, pact-broker-cli in a single binary") + .args(add_otel_options_args()) .subcommand( pact_broker_cli::cli::pact_broker_client::add_pact_broker_client_command() .name("broker") @@ -46,3 +48,54 @@ fn add_completions_subcommand() -> Command { .value_parser(clap::builder::NonEmptyStringValueParser::new()) .help("The directory to write the shell completions to, default is the current directory")) } + +fn add_otel_options_args() -> Vec { + vec![ + Arg::new("enable-otel") + .long("enable-otel") + .help("Enable OpenTelemetry tracing") + .global(true) + .hide(true) + .action(clap::ArgAction::SetTrue), + Arg::new("enable-otel-logs") + .long("enable-otel-logs") + .help("Enable OpenTelemetry logging") + .global(true) + .hide(true) + .action(clap::ArgAction::SetTrue), + Arg::new("otel-exporter") + .long("otel-exporter") + .help("The OpenTelemetry exporter to use (stdout, otlp)") + .num_args(1) + .global(true) + .hide(true) + .default_value("stdout") + .env("OTEL_TRACES_EXPORTER") + .value_parser(clap::builder::PossibleValuesParser::new(&[ + "stdout", "otlp", + ])), + Arg::new("otel-exporter-endpoint") + .long("otel-exporter-endpoint") + .help("The endpoint to use for the OTLP exporter (required if --otel-exporter=otlp)") + .num_args(1) + .global(true) + .hide(true) + .requires_if("otlp", "otel-exporter") + .env("OTEL_EXPORTER_OTLP_ENDPOINT") + .value_parser(clap::builder::NonEmptyStringValueParser::new()), + Arg::new("otel-exporter-protocol") + .long("otel-exporter-protocol") + .help("The protocol to use for the OTLP exporter (http/protobuf, grpc)") + .num_args(1) + .global(true) + .hide(true) + .default_value("http") + .requires_if("otlp", "otel-exporter") + .env("OTEL_EXPORTER_OTLP_PROTOCOL") + .value_parser(clap::builder::PossibleValuesParser::new(&[ + "http", + "http/protobuf", + "grpc", + ])), + ] +} diff --git a/src/cli/otel.rs b/src/cli/otel.rs new file mode 100644 index 0000000..1b11260 --- /dev/null +++ b/src/cli/otel.rs @@ -0,0 +1,126 @@ +// use clap::Subcommand; +use opentelemetry::{global, trace::Span}; + +use opentelemetry::{trace::Tracer, KeyValue}; +use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::{ + logs::SdkLoggerProvider, propagation::TraceContextPropagator, trace::SdkTracerProvider, +}; +use opentelemetry_stdout::{LogExporter, SpanExporter}; +use tracing::{trace, Level}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +pub struct OtelConfig { + pub exporter: Option, + pub endpoint: Option, + pub protocol: Option, +} + +pub fn init_tracer(otel_config: OtelConfig) -> SdkTracerProvider { + global::set_text_map_propagator(TraceContextPropagator::new()); + let otel_service_config = { + // Here you can set up resource attributes like service name, version, etc. + opentelemetry_sdk::Resource::builder() + .with_attributes(vec![KeyValue::new("service.name", "pact-cli")]) + .build() + }; + let provider = match otel_config.exporter.as_deref() { + Some("otlp") => { + let endpoint = otel_config + .endpoint + .unwrap_or_else(|| "http://localhost:4318".to_string()); + let protocol = otel_config.protocol.unwrap_or_else(|| "http".to_string()); + + let otlp_exporter = { + trace!( + "Initializing OTLP exporter with endpoint: {} and protocol: {}", + endpoint, + protocol + ); + match protocol.as_str() { + "grpc" => opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .with_endpoint(endpoint.to_string()) + .build() + .expect("Failed to configure grpc exporter"), + _ => opentelemetry_otlp::SpanExporter::builder() + .with_http() + .with_endpoint(endpoint.to_string() + "/v1/traces") + .build() + .expect("Failed to configure http exporter"), + } + }; + + SdkTracerProvider::builder() + .with_simple_exporter(otlp_exporter) + .with_resource(otel_service_config) + .build() + } + _ => SdkTracerProvider::builder() + .with_simple_exporter(SpanExporter::default()) + .with_resource(otel_service_config) + .build(), + }; + + global::set_tracer_provider(provider.clone()); + provider +} + +pub fn init_logs(log_level: Option) -> Option { + // Setup logger provider with stdout exporter + let logger_provider = SdkLoggerProvider::builder() + .with_simple_exporter(LogExporter::default()) + .build(); + let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider); + + // Instead of .init(), attach to existing tracing subscribers + if tracing_subscriber::registry() + .with(otel_layer) + .with( + tracing_subscriber::fmt::layer() + .compact() + .with_thread_names(true) + .with_level(true), + ) + .with({ + if let Some(level) = log_level { + Some(tracing_subscriber::filter::LevelFilter::from_level(level)) + } else { + Some(tracing_subscriber::filter::LevelFilter::OFF) + } + }) + .try_init() + .is_ok() + { + Some(logger_provider) + } else { + // Failed to initialize, likely due to dispatcher already set + None + } +} + +pub fn capture_telemetry(args: &[String], exit_code: i32, error_message: Option<&str>) { + let tracer = global::tracer("pact-cli"); + let mut span = tracer.start("invocation"); + + // set the service name and other otlp high level attributes + + if let Some(binary) = args.get(0) { + span.set_attribute(KeyValue::new("binary", binary.clone())); + } + if let Some(command) = args.get(1) { + span.set_attribute(KeyValue::new("command", command.clone())); + } + if let Some(subcommand) = args.get(2) { + span.set_attribute(KeyValue::new("subcommand", subcommand.clone())); + } + if args.len() > 3 { + span.set_attribute(KeyValue::new("args", format!("{:?}", &args[3..]))); + } + span.set_attribute(KeyValue::new("exit_code", exit_code as i64)); + if let Some(message) = error_message { + span.set_attribute(KeyValue::new("error_message", message.to_string())); + } + span.end(); +} From 8e811eb6b30f7b47a4396e7003339718fec01684 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Sun, 19 Oct 2025 00:11:24 +0100 Subject: [PATCH 3/3] ci(test): test otel in unix ci --- .github/workflows/reusable-build.yml | 2 +- run.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index fb38b4d..bc4185f 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -83,7 +83,7 @@ jobs: shell: bash env: PACT_BROKER_BASE_URL: http://localhost:9292 - BIN: target/${{ inputs.target }}/release/pact + BIN: target/${{ inputs.target }}/release/pact --enable-otel - name: test pact-cli with locally running Pact Broker (Windows) if: ${{ runner.os == 'Windows' && inputs.target != 'aarch64-pc-windows-msvc' }} run: ./run.ps1 diff --git a/run.sh b/run.sh index 7e72601..5341fe1 100755 --- a/run.sh +++ b/run.sh @@ -45,7 +45,15 @@ ${BIN} broker create-environment --name name_foo1 ${BIN} broker create-environment --name name_foo2 --display-name display_name_foo ${BIN} broker create-environment --name name_foo3 --display-name display_name_foo --contact-name contact_name_foo ${BIN} broker create-environment --name name_foo4 --display-name display_name_foo --contact-name contact_name_foo --contact-email-address contact.email.address@foo.bar +# remove --enable-otel arg from BIN to test otel functionality +IS_OTEL_ENABLED=$(echo $BIN | grep -- '--enable-otel' || true) +if [ -n "$IS_OTEL_ENABLED" ]; then + BIN=${BIN//--enable-otel/} +fi export ENV_UUID=$(${BIN} broker create-environment --name name_foo5 --output=id) +if [ -n "$IS_OTEL_ENABLED" ]; then + BIN="$BIN --enable-otel" +fi ${BIN} broker describe-environment --uuid $ENV_UUID ${BIN} broker update-environment --uuid $ENV_UUID --name name_foo6 ${BIN} broker update-environment --uuid $ENV_UUID --name name_foo7 --display-name display_name_foo6 @@ -66,7 +74,13 @@ ${BIN} broker create-or-update-pacticipant --name foo --main-branch main --repos ${BIN} broker describe-pacticipant --name foo ${BIN} broker list-pacticipants ${BIN} broker create-webhook https://localhost --request POST --contract-published +if [ -n "$IS_OTEL_ENABLED" ]; then + BIN=${BIN//--enable-otel/} +fi export WEBHOOK_UUID=$(${BIN} broker create-webhook https://localhost --request POST --contract-published | jq .uuid -r) +if [ -n "$IS_OTEL_ENABLED" ]; then + BIN="$BIN --enable-otel" +fi ${BIN} broker create-or-update-webhook https://foo.bar --request POST --uuid $WEBHOOK_UUID --provider-verification-succeeded ${BIN} broker test-webhook --uuid $WEBHOOK_UUID ${BIN} broker create-or-update-version --version foo --pacticipant foo --branch bar --tag baz