From 165446b4edd1a301ecd96931ae92b12eafca5f36 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Wed, 25 Mar 2026 15:07:36 +0100 Subject: [PATCH 1/7] Fix Go tracer version selection for orchestrion --- install_test_visibility.sh | 409 +++++++++++++++++++++++++++++++++---- 1 file changed, 373 insertions(+), 36 deletions(-) diff --git a/install_test_visibility.sh b/install_test_visibility.sh index 1521e40..3c6a13e 100755 --- a/install_test_visibility.sh +++ b/install_test_visibility.sh @@ -396,44 +396,65 @@ install_ruby_tracer() { echo "DD_TRACER_VERSION_RUBY=$(datadog_ci_gem_version)" } -# Function to get the Go version from the go.mod file of a release -get_orchestrion_go_version() { +resolve_orchestrion_tag() { local input_tag="$1" - local tag="" - - # If "latest" is provided, fetch the latest release tag from GitHub API - if [ "$input_tag" == "latest" ]; then - # Use curl with -sSf to ensure errors are caught and not output to stdout - # Use grep and sed to extract the tag_name from the JSON response - tag=$(curl -sSf -A "github-action" https://api.github.com/repos/datadog/orchestrion/releases/latest \ - | grep -o '"tag_name": *"[^"]*"' \ - | head -n 1 \ - | sed 's/"tag_name": *"\([^"]*\)"/\1/') - if [ -z "$tag" ]; then - echo "Error: Could not retrieve the latest tag." >&2 - return 1 - fi - else - tag="$input_tag" + + # Reuse explicitly requested tags as-is. + if [ "$input_tag" != "latest" ]; then + echo "$input_tag" + return 0 + fi + + # Resolve "latest" once through the GitHub releases API so the rest of the + # installation flow works with a single concrete orchestrion version. + local tag + tag=$(curl -sSf -A "github-action" https://api.github.com/repos/datadog/orchestrion/releases/latest \ + | grep -o '"tag_name": *"[^"]*"' \ + | head -n 1 \ + | sed 's/"tag_name": *"\([^"]*\)"/\1/') + if [ -z "$tag" ]; then + echo "Error: Could not retrieve the latest tag." >&2 + return 1 fi - # Determine the URL to fetch the go.mod file + echo "$tag" +} + +fetch_orchestrion_go_mod() { + local tag="$1" + local modfile="${2:-go.mod}" local url="" - # If tag looks like a commit SHA (7 to 40 hexadecimal characters) + + # Support both released tags and direct commit SHAs so the script can keep + # working with the same kinds of inputs accepted by `go install`. if [[ "$tag" =~ ^[0-9a-f]{7,40}$ ]]; then - url="https://raw.githubusercontent.com/DataDog/orchestrion/${tag}/go.mod" + url="https://raw.githubusercontent.com/DataDog/orchestrion/${tag}/${modfile}" else - url="https://raw.githubusercontent.com/DataDog/orchestrion/refs/tags/${tag}/go.mod" + url="https://raw.githubusercontent.com/DataDog/orchestrion/refs/tags/${tag}/${modfile}" fi - # Fetch the go.mod file content using curl with -sSf + # Read the upstream go.mod file directly from GitHub so we can reuse the + # versions that shipped with the selected orchestrion release. local go_mod go_mod=$(curl -sSf -A "github-action" "$url" || true) if [ -z "$go_mod" ]; then - echo "Error: Could not retrieve go.mod from ${url}" >&2 + echo "Error: Could not retrieve ${modfile} from ${url}" >&2 return 1 fi + echo "$go_mod" +} + +# Function to get the Go version from the go.mod file of a release +get_orchestrion_go_version() { + local input_tag="$1" + + local tag + tag=$(resolve_orchestrion_tag "$input_tag") || return 1 + + local go_mod + go_mod=$(fetch_orchestrion_go_mod "$tag") || return 1 + # Extract the Go version by searching for the line starting with "go " local go_version go_version=$(echo "$go_mod" | grep -m 1 '^go ' | awk '{print $2}') @@ -445,13 +466,160 @@ get_orchestrion_go_version() { echo "$go_version" } +get_orchestrion_module_version() { + local input_tag="$1" + local module_path="$2" + local modfile="${3:-go.mod}" + + # Resolve the orchestrion tag first so every lookup in this run points to + # the same upstream revision. + local tag + tag=$(resolve_orchestrion_tag "$input_tag") || return 1 + + local go_mod + go_mod=$(fetch_orchestrion_go_mod "$tag" "$modfile") || return 1 + + # Extract the version from the relevant require line in the selected go.mod. + local module_version + module_version=$(echo "$go_mod" | awk -v module_path="$module_path" '$1 == module_path { print $2; exit }') + if [ -z "$module_version" ]; then + echo "Error: Could not extract ${module_path} version from ${modfile}" >&2 + return 1 + fi + + echo "$module_version" +} + +get_current_project_go_version() { + local go_mod_path="${1:-go.mod}" + + if [ ! -f "$go_mod_path" ]; then + echo "Error: Could not find ${go_mod_path} in the current directory." >&2 + return 1 + fi + + # Read the Go directive from the target project so later version selection + # can stay within the Go level the project already declares. + local go_version + go_version=$(grep -m 1 '^go ' "$go_mod_path" | awk '{print $2}') + if [ -z "$go_version" ]; then + echo "Error: Could not extract the Go version from ${go_mod_path}" >&2 + return 1 + fi + + echo "$go_version" +} + +resolve_go_module_directory() { + local configured_module_dir="${DD_CIVISIBILITY_GO_MODULE_DIR:-}" + + # An explicit override is user intent, so validate it strictly instead of + # silently ignoring it. + if [ -n "$configured_module_dir" ]; then + if [ ! -d "$configured_module_dir" ]; then + echo "Error: DD_CIVISIBILITY_GO_MODULE_DIR points to a directory that does not exist: $configured_module_dir" >&2 + return 1 + fi + + local absolute_configured_module_dir + absolute_configured_module_dir=$(cd "$configured_module_dir" && pwd) + if [ ! -f "$absolute_configured_module_dir/go.mod" ]; then + echo "Error: DD_CIVISIBILITY_GO_MODULE_DIR does not contain a go.mod file: $absolute_configured_module_dir" >&2 + return 1 + fi + + echo "$absolute_configured_module_dir" + return 0 + fi + + # When the script already runs in the module root, keep using the current + # directory and avoid extra filesystem scanning. + if [ -f "go.mod" ]; then + pwd + return 0 + fi + + # For repository roots that do not contain go.mod directly, auto-detect a + # single nested module. If there is more than one candidate, do not guess. + local -a go_mod_candidates=() + mapfile -t go_mod_candidates < <( + find . \ + \( -path '*/.git' -o -path '*/vendor' -o -path '*/node_modules' \) -prune -o \ + -type f -name go.mod -print \ + | sort + ) + + if [ ${#go_mod_candidates[@]} -eq 1 ]; then + local detected_module_dir + detected_module_dir=$(dirname "${go_mod_candidates[0]}") + (cd "$detected_module_dir" && pwd) + return 0 + fi + + if [ ${#go_mod_candidates[@]} -eq 0 ]; then + return 2 + fi + + return 3 +} + +list_stable_dd_trace_go_versions() { + # Query the published v2 module versions and keep only stable x.y.z tags. + # This intentionally skips rc/dev builds so the script selects the newest + # supported released tracer version instead of a pre-release. + go list -m -versions github.com/DataDog/dd-trace-go/v2 2>/dev/null \ + | awk '{for (i = 2; i <= NF; i++) print $i}' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' +} + +fetch_dd_trace_go_orchestrion_all_go_mod() { + local version="$1" + local url="https://raw.githubusercontent.com/DataDog/dd-trace-go/${version}/orchestrion/all/go.mod" + + # Read the integration module metadata directly from GitHub because + # `orchestrion pin` ultimately adds this module to go.mod. + local go_mod + go_mod=$(curl -sSf -A "github-action" "$url" || true) + if [ -z "$go_mod" ]; then + echo "Error: Could not retrieve orchestrion/all/go.mod from ${url}" >&2 + return 1 + fi + + echo "$go_mod" +} + +get_dd_trace_go_orchestrion_all_go_version() { + local version="$1" + + local go_mod + go_mod=$(fetch_dd_trace_go_orchestrion_all_go_mod "$version") || return 1 + + # Extract the Go directive that tells us whether this tracer release can be + # used without requiring a newer Go version than the project or runner has. + local go_version + go_version=$(echo "$go_mod" | grep -m 1 '^go ' | awk '{print $2}') + if [ -z "$go_version" ]; then + echo "Error: Could not extract the Go version from orchestrion/all/go.mod for ${version}" >&2 + return 1 + fi + + echo "$go_version" +} + # Helper function to compare two semantic version numbers. # It returns 0 (true) if the first version ($1) is greater than or equal to the second ($2), # and returns 1 (false) otherwise. version_ge() { + local normalized_version_1="${1#v}" + local normalized_version_2="${2#v}" + normalized_version_1="${normalized_version_1%%-*}" + normalized_version_2="${normalized_version_2%%-*}" + normalized_version_1="${normalized_version_1%%+*}" + normalized_version_2="${normalized_version_2%%+*}" + # Split version numbers into arrays based on the dot separator - IFS='.' read -r -a ver1 <<< "$1" - IFS='.' read -r -a ver2 <<< "$2" + IFS='.' read -r -a ver1 <<< "$normalized_version_1" + IFS='.' read -r -a ver2 <<< "$normalized_version_2" # Determine the maximum length of both version arrays local len=${#ver1[@]} @@ -474,6 +642,61 @@ version_ge() { return 0 } +version_min() { + if version_ge "$1" "$2"; then + echo "$2" + else + echo "$1" + fi +} + +select_dd_trace_go_version_for_project() { + local minimum_version="$1" + local max_supported_go_version="$2" + + local -a available_versions=() + mapfile -t available_versions < <(list_stable_dd_trace_go_versions) + if [ ${#available_versions[@]} -eq 0 ]; then + echo "Error: Could not retrieve the list of stable dd-trace-go versions." >&2 + return 1 + fi + + # Selection algorithm: + # - Start from the newest stable dd-trace-go release. + # - Reject anything older than the orchestrion-shipped baseline. + # - For each remaining candidate, read orchestrion/all/go.mod and keep the + # first release whose Go requirement fits within the effective Go ceiling. + # The first match is the newest stable tracer that is both safe for the + # selected orchestrion release and compatible with this project + runner. + # Walk the stable releases from newest to oldest and pick the first one + # that satisfies both constraints: + # 1. It is not older than the version shipped with the selected orchestrion. + # 2. Its orchestrion/all module does not require a newer Go version than + # the project and runner can support together. + local candidate_version + local candidate_go_version + local index + for (( index=${#available_versions[@]}-1; index>=0; index-- )); do + candidate_version="${available_versions[index]}" + + if ! version_ge "$candidate_version" "$minimum_version"; then + continue + fi + + candidate_go_version=$(get_dd_trace_go_orchestrion_all_go_version "$candidate_version" 2>/dev/null || true) + if [ -z "$candidate_go_version" ]; then + continue + fi + + if version_ge "$max_supported_go_version" "$candidate_go_version"; then + echo "$candidate_version" + return 0 + fi + done + + return 1 +} + # Function to check if the installed Go version meets the requirement. # It calls get_go_version with a provided parameter (tag name or "latest"), # extracts the installed version from `go version`, and compares both. @@ -513,11 +736,20 @@ install_go_tracer() { DD_SET_TRACER_VERSION_GO=latest fi + # Resolve the input to a concrete orchestrion tag once so "latest" does not + # drift between the different network calls below. + local resolved_orchestrion_tag + resolved_orchestrion_tag=$(resolve_orchestrion_tag "$DD_SET_TRACER_VERSION_GO" || true) + if [ $? -ne 0 ] || [ -z "$resolved_orchestrion_tag" ]; then + echo "Error: Could not resolve the orchestrion tag for $DD_SET_TRACER_VERSION_GO." >&2 + return 1 + fi + # Try to retrieve the required Go version from orchestrion's go.mod file using the specified tag. local orchestrion_go_version - orchestrion_go_version=$(get_orchestrion_go_version "$DD_SET_TRACER_VERSION_GO" || true) + orchestrion_go_version=$(get_orchestrion_go_version "$resolved_orchestrion_tag" || true) if [ $? -ne 0 ] || [ -z "$orchestrion_go_version" ]; then - echo "Error: Could not retrieve the required Go version for orchestrion (tag: $DD_SET_TRACER_VERSION_GO)." >&2 + echo "Error: Could not retrieve the required Go version for orchestrion (tag: $resolved_orchestrion_tag)." >&2 echo "Skipping orchestrion installation." >&2 return 0 fi @@ -528,26 +760,131 @@ install_go_tracer() { # Compare the installed version with the required version. if ! version_ge "$installed_go_version" "$orchestrion_go_version"; then - echo "The installed Go version ($installed_go_version) does not meet the required version ($orchestrion_go_version) for orchestrion (tag: $DD_SET_TRACER_VERSION_GO)." >&2 + echo "The installed Go version ($installed_go_version) does not meet the required version ($orchestrion_go_version) for orchestrion (tag: $resolved_orchestrion_tag)." >&2 echo "Skipping orchestrion installation." >&2 return 0 fi - # Install orchestrion using go install - if ! go install github.com/DataDog/orchestrion@$DD_SET_TRACER_VERSION_GO >&2; then + # Resolve the Go module directory before touching go.mod. The script first + # honors an explicit override, then tries the current directory, then falls + # back to single-module auto-detection for repository roots that only contain + # a nested Go project. + local go_module_dir + local module_resolution_status + if go_module_dir=$(resolve_go_module_directory); then + module_resolution_status=0 + else + module_resolution_status=$? + fi + if [ $module_resolution_status -eq 1 ]; then + return 1 + fi + if [ $module_resolution_status -ne 0 ] || [ -z "$go_module_dir" ]; then + if [ $module_resolution_status -eq 2 ]; then + >&2 echo "Could not find a go.mod file in the current directory or any nested directory." + else + >&2 echo "Could not determine a single Go module directory automatically." + >&2 echo "Set DD_CIVISIBILITY_GO_MODULE_DIR to the Go module root if this repository contains multiple Go modules." + fi + >&2 echo "Skipping orchestrion installation." + return 0 + fi + + # The selected orchestrion release defines the minimum tracer version we can + # use safely. We never choose anything older than this baseline. + local minimum_dd_trace_go_version + minimum_dd_trace_go_version=$(get_orchestrion_module_version "$resolved_orchestrion_tag" "github.com/DataDog/dd-trace-go/v2" || true) + if [ $? -ne 0 ] || [ -z "$minimum_dd_trace_go_version" ]; then + >&2 echo "Error: Could not retrieve the dd-trace-go version for orchestrion (tag: $resolved_orchestrion_tag)." + return 1 + fi + + # The shipped minimum tracer version can itself require a newer Go version + # through its orchestrion/all module. We use this requirement as the hard + # lower bound for deciding whether a project can be instrumented at all. + local minimum_dd_trace_go_required_go_version + minimum_dd_trace_go_required_go_version=$(get_dd_trace_go_orchestrion_all_go_version "$minimum_dd_trace_go_version" || true) + if [ $? -ne 0 ] || [ -z "$minimum_dd_trace_go_required_go_version" ]; then + >&2 echo "Error: Could not retrieve the Go requirement for dd-trace-go $minimum_dd_trace_go_version." + return 1 + fi + + # The runner also needs to satisfy the minimum tracer requirement. This check + # is stricter than the earlier orchestrion root go.mod check and protects the + # fallback path if the tracer bundle starts requiring a newer Go version than + # orchestrion's root module declares. + if ! version_ge "$installed_go_version" "$minimum_dd_trace_go_required_go_version"; then + >&2 echo "The installed Go version ($installed_go_version) is lower than the minimum Go version required by dd-trace-go $minimum_dd_trace_go_version ($minimum_dd_trace_go_required_go_version)." + >&2 echo "Skipping orchestrion installation." + return 0 + fi + + # Use the lower of the project's Go directive and the runner's installed Go + # version as the compatibility ceiling. This keeps the selected tracer inside + # the Go version already declared by the project and also avoids picking a + # module that the current runner cannot build. + local selected_dd_trace_go_version + selected_dd_trace_go_version="$minimum_dd_trace_go_version" + + # Prefer the newest compatible tracer when we can read the project's Go + # version from the selected module root. If that information is unavailable + # or does not lead to a compatible release, fall back to the minimum tracer + # version that shipped with orchestrion so the script still avoids floating to + # an unsupported `dd-trace-go@latest`. + local project_go_version + if project_go_version=$(get_current_project_go_version "$go_module_dir/go.mod"); then + # If the project itself declares a Go version below the minimum required by + # the shipped tracer, do not fall back to that tracer. Skipping here avoids + # letting `orchestrion pin` silently move the project to a newer Go level. + if ! version_ge "$project_go_version" "$minimum_dd_trace_go_required_go_version"; then + >&2 echo "The project Go version ($project_go_version) is lower than the minimum Go version required by dd-trace-go $minimum_dd_trace_go_version ($minimum_dd_trace_go_required_go_version)." + >&2 echo "Skipping orchestrion installation." + return 0 + fi + + local max_supported_go_version + max_supported_go_version=$(version_min "$project_go_version" "$installed_go_version") + + # Choose the newest stable tracer release that satisfies the two + # boundaries: it must be at least the version shipped with orchestrion, + # and its orchestrion/all module must support the effective Go ceiling + # computed above. + local compatible_dd_trace_go_version + if compatible_dd_trace_go_version=$(select_dd_trace_go_version_for_project "$minimum_dd_trace_go_version" "$max_supported_go_version"); then + selected_dd_trace_go_version="$compatible_dd_trace_go_version" + else + >&2 echo "Could not find a project-compatible stable dd-trace-go release for Go $max_supported_go_version." + >&2 echo "Falling back to the minimum dd-trace-go version shipped with orchestrion: $minimum_dd_trace_go_version." + fi + else + >&2 echo "Could not read the project Go version from $go_module_dir/go.mod." + >&2 echo "Falling back to the minimum dd-trace-go version shipped with orchestrion: $minimum_dd_trace_go_version." + fi + + if ! (cd "$go_module_dir" && go mod edit -require=github.com/DataDog/dd-trace-go/v2@$selected_dd_trace_go_version) >&2; then + >&2 echo "Error: Could not pin dd-trace-go for Go to version $selected_dd_trace_go_version." + return 1 + fi + + # Install the requested orchestrion CLI version in GOPATH/bin so the later + # `orchestrion pin` command runs with the same release we just resolved. + if ! go install github.com/DataDog/orchestrion@$resolved_orchestrion_tag >&2; then >&2 echo "Error: Could not install orchestrion for Go." return 1 fi - # Pin orchestrion - if ! orchestrion pin >&2; then + # Generate/update orchestrion.tool.go and the project dependencies. At this + # point dd-trace-go is already pinned in go.mod, so orchestrion will reuse + # that version instead of upgrading to the latest tracer release. + if ! (cd "$go_module_dir" && orchestrion pin) >&2; then >&2 echo "Error: Orchestrion pin failed." return 1 fi - # Run go get to update dependencies - if ! go get github.com/DataDog/orchestrion >&2; then - >&2 echo "Error: go get github.com/DataDog/orchestrion failed." + # Update the module graph with the selected orchestrion dependency while + # keeping the version fixed to the same concrete tag used above. + if ! (cd "$go_module_dir" && go get github.com/DataDog/orchestrion@$resolved_orchestrion_tag) >&2; then + >&2 echo "Error: go get github.com/DataDog/orchestrion@$resolved_orchestrion_tag failed." return 1 fi From f3d507f3a9440583c53ad8379b2fc7de0ef74ae2 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Wed, 25 Mar 2026 15:25:21 +0100 Subject: [PATCH 2/7] Restore Bash 3.2 compatibility in Go installer --- install_test_visibility.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/install_test_visibility.sh b/install_test_visibility.sh index 3c6a13e..bfd685e 100755 --- a/install_test_visibility.sh +++ b/install_test_visibility.sh @@ -542,7 +542,11 @@ resolve_go_module_directory() { # For repository roots that do not contain go.mod directly, auto-detect a # single nested module. If there is more than one candidate, do not guess. local -a go_mod_candidates=() - mapfile -t go_mod_candidates < <( + # Use a plain read loop instead of `mapfile` so this still works on the + # Bash 3.2 shell shipped on macOS GitHub runners. + while IFS= read -r go_mod_candidate; do + go_mod_candidates+=("$go_mod_candidate") + done < <( find . \ \( -path '*/.git' -o -path '*/vendor' -o -path '*/node_modules' \) -prune -o \ -type f -name go.mod -print \ @@ -655,7 +659,11 @@ select_dd_trace_go_version_for_project() { local max_supported_go_version="$2" local -a available_versions=() - mapfile -t available_versions < <(list_stable_dd_trace_go_versions) + # Use a plain read loop instead of `mapfile` so this still works on the + # Bash 3.2 shell shipped on macOS GitHub runners. + while IFS= read -r available_version; do + available_versions+=("$available_version") + done < <(list_stable_dd_trace_go_versions) if [ ${#available_versions[@]} -eq 0 ]; then echo "Error: Could not retrieve the list of stable dd-trace-go versions." >&2 return 1 From 5d0b0c1aaf5074478b8895f984c7ee078e8e61b3 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Wed, 25 Mar 2026 15:27:33 +0100 Subject: [PATCH 3/7] Clarify Bash 3.2 compatibility comments --- install_test_visibility.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/install_test_visibility.sh b/install_test_visibility.sh index bfd685e..d9b1a45 100755 --- a/install_test_visibility.sh +++ b/install_test_visibility.sh @@ -542,8 +542,8 @@ resolve_go_module_directory() { # For repository roots that do not contain go.mod directly, auto-detect a # single nested module. If there is more than one candidate, do not guess. local -a go_mod_candidates=() - # Use a plain read loop instead of `mapfile` so this still works on the - # Bash 3.2 shell shipped on macOS GitHub runners. + # Collect the detected go.mod paths into an array using syntax that works + # on the Bash 3.2 shell shipped on macOS GitHub runners. while IFS= read -r go_mod_candidate; do go_mod_candidates+=("$go_mod_candidate") done < <( @@ -659,8 +659,8 @@ select_dd_trace_go_version_for_project() { local max_supported_go_version="$2" local -a available_versions=() - # Use a plain read loop instead of `mapfile` so this still works on the - # Bash 3.2 shell shipped on macOS GitHub runners. + # Collect the published stable tracer versions into an array using syntax + # that works on the Bash 3.2 shell shipped on macOS GitHub runners. while IFS= read -r available_version; do available_versions+=("$available_version") done < <(list_stable_dd_trace_go_versions) From 4126d00937a6aecfd4f944306075eb662dec3ad1 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Wed, 25 Mar 2026 15:33:44 +0100 Subject: [PATCH 4/7] Document Go installer helper functions --- install_test_visibility.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/install_test_visibility.sh b/install_test_visibility.sh index d9b1a45..bfbc653 100755 --- a/install_test_visibility.sh +++ b/install_test_visibility.sh @@ -396,6 +396,9 @@ install_ruby_tracer() { echo "DD_TRACER_VERSION_RUBY=$(datadog_ci_gem_version)" } +# +# Resolve the user-provided orchestrion selector to a single concrete tag so +# the rest of the installation flow uses one stable orchestrion version. resolve_orchestrion_tag() { local input_tag="$1" @@ -420,6 +423,9 @@ resolve_orchestrion_tag() { echo "$tag" } +# +# Download the requested orchestrion go.mod file from GitHub so later helpers +# can read the dependency versions that shipped with that release. fetch_orchestrion_go_mod() { local tag="$1" local modfile="${2:-go.mod}" @@ -490,6 +496,9 @@ get_orchestrion_module_version() { echo "$module_version" } +# +# Read the target project's Go directive from go.mod so tracer selection can +# stay within the Go version the project already declares. get_current_project_go_version() { local go_mod_path="${1:-go.mod}" @@ -567,6 +576,9 @@ resolve_go_module_directory() { return 3 } +# +# List the published stable dd-trace-go/v2 releases so the installer can pick +# the newest compatible tracer without considering prerelease tags. list_stable_dd_trace_go_versions() { # Query the published v2 module versions and keep only stable x.y.z tags. # This intentionally skips rc/dev builds so the script selects the newest @@ -592,6 +604,9 @@ fetch_dd_trace_go_orchestrion_all_go_mod() { echo "$go_mod" } +# +# Read the Go directive from dd-trace-go's orchestrion/all module for a given +# tracer version so we can check whether that tracer is compatible. get_dd_trace_go_orchestrion_all_go_version() { local version="$1" @@ -646,6 +661,9 @@ version_ge() { return 0 } +# +# Return the lower of two semantic versions so tracer selection can use the +# stricter compatibility ceiling between the project and the runner. version_min() { if version_ge "$1" "$2"; then echo "$2" @@ -654,6 +672,10 @@ version_min() { fi } +# +# Pick the newest stable dd-trace-go release that is not older than the +# orchestrion-shipped baseline and whose orchestrion/all module still supports +# the effective Go ceiling for this project and runner. select_dd_trace_go_version_for_project() { local minimum_version="$1" local max_supported_go_version="$2" From 68bca99818d114a85c175342730aede78eae24c7 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Wed, 25 Mar 2026 15:37:33 +0100 Subject: [PATCH 5/7] Document Go module directory override --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d45f29..4722669 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=... DD_API_KEY=... ./install_test_visi The script parameters are -- `DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES`: (required) List of languages to be instrumented. Can be either `all` or any of `java`, `js`, `python`, `dotnet`, `ruby` (multiple languages can be specified as a space-separated list). +- `DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES`: (required) List of languages to be instrumented. Can be either `all` or any of `java`, `js`, `python`, `dotnet`, `ruby`, `go` (multiple languages can be specified as a space-separated list). - `DD_API_KEY`: (required for .NET tracer installation) Datadog API key. Can be found at https://app.datadoghq.com/organization-settings/api-keys - `DD_TRACER_FOLDER`: (optional) The folder where the tracing libraries will be installed, defaults to `./.datadog` - `DD_SITE`: (optional) Datadog site, defaults to US1. See https://docs.datadoghq.com/getting_started/site for more information about sites. @@ -30,6 +30,7 @@ The script parameters are - `DD_SET_TRACER_VERSION_PYTHON`: (optional) Version of the Python tracer to install. If not provided, the latest version is installed. - `DD_SET_TRACER_VERSION_RUBY`: (optional) Version of the Ruby datadog-ci gem to install. If not provided, the latest version is installed. - `DD_SET_TRACER_VERSION_GO`: (optional) Version of Orchestrion to install. If not provided, the latest version is installed. +- `DD_CIVISIBILITY_GO_MODULE_DIR`: (optional) Directory that contains the Go project's `go.mod` file. Use this when the Go module is not at the repository root or when the repository contains multiple Go modules. - `DD_INSTRUMENTATION_BUILD_SYSTEM_JAVA`: (optional) A hint for Java instrumentation to instrument a specific build system. Allowed values are `maven`, `gradle`, `sbt`, `ant`, and `all`. If not specified, every Maven, Gradle, SBT, and Ant build will be instrumented. `all` is a special value that allows instrumenting _every JVM process_. The script will install the libraries and print the list of environment variables that should be set in order to enable Test Optimization. Example output: From 4e76a910fec459b280d24e58096b9f577ae40740 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Wed, 25 Mar 2026 16:19:13 +0100 Subject: [PATCH 6/7] Add Go matrix coverage for nested module selection --- install_test_visibility.sh | 28 +- tests/run_go_matrix.sh | 647 +++++++++++++++++++++++++++++++++++++ 2 files changed, 665 insertions(+), 10 deletions(-) create mode 100755 tests/run_go_matrix.sh diff --git a/install_test_visibility.sh b/install_test_visibility.sh index bfbc653..6d797b8 100755 --- a/install_test_visibility.sh +++ b/install_test_visibility.sh @@ -580,12 +580,17 @@ resolve_go_module_directory() { # List the published stable dd-trace-go/v2 releases so the installer can pick # the newest compatible tracer without considering prerelease tags. list_stable_dd_trace_go_versions() { + local module_dir="${1:-.}" + # Query the published v2 module versions and keep only stable x.y.z tags. # This intentionally skips rc/dev builds so the script selects the newest # supported released tracer version instead of a pre-release. - go list -m -versions github.com/DataDog/dd-trace-go/v2 2>/dev/null \ - | awk '{for (i = 2; i <= NF; i++) print $i}' \ - | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' + ( + cd "$module_dir" && + go list -m -versions github.com/DataDog/dd-trace-go/v2 2>/dev/null \ + | awk '{for (i = 2; i <= NF; i++) print $i}' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' + ) } fetch_dd_trace_go_orchestrion_all_go_mod() { @@ -679,13 +684,14 @@ version_min() { select_dd_trace_go_version_for_project() { local minimum_version="$1" local max_supported_go_version="$2" + local module_dir="${3:-.}" local -a available_versions=() # Collect the published stable tracer versions into an array using syntax # that works on the Bash 3.2 shell shipped on macOS GitHub runners. while IFS= read -r available_version; do available_versions+=("$available_version") - done < <(list_stable_dd_trace_go_versions) + done < <(list_stable_dd_trace_go_versions "$module_dir") if [ ${#available_versions[@]} -eq 0 ]; then echo "Error: Could not retrieve the list of stable dd-trace-go versions." >&2 return 1 @@ -880,7 +886,7 @@ install_go_tracer() { # and its orchestrion/all module must support the effective Go ceiling # computed above. local compatible_dd_trace_go_version - if compatible_dd_trace_go_version=$(select_dd_trace_go_version_for_project "$minimum_dd_trace_go_version" "$max_supported_go_version"); then + if compatible_dd_trace_go_version=$(select_dd_trace_go_version_for_project "$minimum_dd_trace_go_version" "$max_supported_go_version" "$go_module_dir"); then selected_dd_trace_go_version="$compatible_dd_trace_go_version" else >&2 echo "Could not find a project-compatible stable dd-trace-go release for Go $max_supported_go_version." @@ -891,11 +897,6 @@ install_go_tracer() { >&2 echo "Falling back to the minimum dd-trace-go version shipped with orchestrion: $minimum_dd_trace_go_version." fi - if ! (cd "$go_module_dir" && go mod edit -require=github.com/DataDog/dd-trace-go/v2@$selected_dd_trace_go_version) >&2; then - >&2 echo "Error: Could not pin dd-trace-go for Go to version $selected_dd_trace_go_version." - return 1 - fi - # Install the requested orchestrion CLI version in GOPATH/bin so the later # `orchestrion pin` command runs with the same release we just resolved. if ! go install github.com/DataDog/orchestrion@$resolved_orchestrion_tag >&2; then @@ -903,6 +904,13 @@ install_go_tracer() { return 1 fi + # Pin dd-trace-go only after the orchestrion CLI is available so install + # failures do not leave the customer's go.mod partially updated. + if ! (cd "$go_module_dir" && go mod edit -require=github.com/DataDog/dd-trace-go/v2@$selected_dd_trace_go_version) >&2; then + >&2 echo "Error: Could not pin dd-trace-go for Go to version $selected_dd_trace_go_version." + return 1 + fi + # Generate/update orchestrion.tool.go and the project dependencies. At this # point dd-trace-go is already pinned in go.mod, so orchestrion will reuse # that version instead of upgrading to the latest tracer release. diff --git a/tests/run_go_matrix.sh b/tests/run_go_matrix.sh new file mode 100755 index 0000000..0e160eb --- /dev/null +++ b/tests/run_go_matrix.sh @@ -0,0 +1,647 @@ +#!/bin/bash + +# This harness validates the Go installation decision matrix by running the +# install script against fake go/curl/orchestrion binaries. That keeps the +# tests deterministic while still exercising the full shell control flow. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SCRIPT_UNDER_TEST="$REPO_ROOT/install_test_visibility.sh" +TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dd-tv-go-matrix.XXXXXX")" + +PASS_COUNT=0 +FAIL_COUNT=0 +LAST_EXIT_CODE=0 +LAST_STDOUT="" +LAST_STDERR="" +CURRENT_CASE_DIR="" +CURRENT_WORKSPACE="" + +cleanup() { + rm -rf "$TEST_ROOT" +} +trap cleanup EXIT + +# Print a failure message and stop the current scenario. +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +# Assert that the script exited with the expected status code. +assert_exit_code() { + local expected="$1" + if [ "$LAST_EXIT_CODE" -ne "$expected" ]; then + fail "expected exit code $expected, got $LAST_EXIT_CODE" + fi +} + +# Assert that a file contains the expected text. +assert_file_contains() { + local file_path="$1" + local expected_text="$2" + if ! grep -Fq "$expected_text" "$file_path"; then + echo "Expected to find '$expected_text' in $file_path" >&2 + echo "----- $file_path -----" >&2 + cat "$file_path" >&2 + fail "missing expected text" + fi +} + +# Assert that a file does not contain the given text. +assert_file_not_contains() { + local file_path="$1" + local unexpected_text="$2" + if grep -Fq "$unexpected_text" "$file_path"; then + echo "Did not expect to find '$unexpected_text' in $file_path" >&2 + echo "----- $file_path -----" >&2 + cat "$file_path" >&2 + fail "found unexpected text" + fi +} + +# Assert that a file exists. +assert_file_exists() { + local file_path="$1" + if [ ! -f "$file_path" ]; then + fail "expected file to exist: $file_path" + fi +} + +# Assert that a file does not exist. +assert_file_missing() { + local file_path="$1" + if [ -e "$file_path" ]; then + fail "expected file to be absent: $file_path" + fi +} + +# Assert that two files are byte-for-byte identical. +assert_files_equal() { + local expected_file="$1" + local actual_file="$2" + if ! cmp -s "$expected_file" "$actual_file"; then + echo "Files differ:" >&2 + echo "----- expected: $expected_file -----" >&2 + cat "$expected_file" >&2 + echo "----- actual: $actual_file -----" >&2 + cat "$actual_file" >&2 + fail "files differ" + fi +} + +# Write a simple go.mod file that includes a go directive. +write_go_mod() { + local file_path="$1" + local module_name="$2" + local go_version="$3" + cat > "$file_path" < "$file_path" < "$CURRENT_CASE_DIR/bin/curl" <<'EOF' +#!/bin/bash +set -euo pipefail + +url="${@: -1}" + +if [[ "$url" == *"/repos/datadog/orchestrion/releases/latest" ]]; then + counter_file="$FAKE_LOG_DIR/latest_requests.count" + count=0 + if [ -f "$counter_file" ]; then + count="$(cat "$counter_file")" + fi + count=$((count + 1)) + printf '%s\n' "$count" > "$counter_file" + printf '{"tag_name":"%s"}\n' "${FAKE_LATEST_ORCHESTRION_TAG:-v1.8.0}" + exit 0 +fi + +if [[ "$url" == *"raw.githubusercontent.com/DataDog/orchestrion/"*"/go.mod" ]]; then + cat <&2 +exit 1 +EOF + chmod +x "$CURRENT_CASE_DIR/bin/curl" + + cat > "$CURRENT_CASE_DIR/bin/go" <<'EOF' +#!/bin/bash +set -euo pipefail + +append_or_replace_requirement() { + local go_mod_path="$1" + local module_path="$2" + local module_version="$3" + local temp_path="${go_mod_path}.tmp" + + awk -v module_path="$module_path" ' + ($1 == module_path) || ($1 == "require" && $2 == module_path) { next } + { print } + ' "$go_mod_path" > "$temp_path" + mv "$temp_path" "$go_mod_path" + printf 'require %s %s\n' "$module_path" "$module_version" >> "$go_mod_path" +} + +log_file="$FAKE_LOG_DIR/go.log" +command_name="${1:-}" +if [ $# -gt 0 ]; then + shift +fi + +case "$command_name" in + version) + printf 'go version go%s darwin/arm64\n' "${FAKE_GO_VERSION:-1.26.1}" + ;; + list) + if [ "${1:-}" = "-m" ] && [ "${2:-}" = "-versions" ] && [ "${3:-}" = "github.com/DataDog/dd-trace-go/v2" ]; then + if [ "${FAKE_FAIL_STABLE_LIST:-0}" = "1" ]; then + exit 1 + fi + printf 'github.com/DataDog/dd-trace-go/v2 %s\n' "${FAKE_DD_TRACE_GO_VERSIONS:-v2.6.0 v2.7.0 v2.8.0}" + else + echo "Unsupported fake go list invocation: go list $*" >&2 + exit 98 + fi + ;; + install) + printf 'install %s\n' "$*" >> "$log_file" + if [ "${FAKE_FAIL_GO_INSTALL:-0}" = "1" ]; then + exit 1 + fi + ;; + mod) + if [ "${1:-}" = "edit" ]; then + printf 'mod %s\n' "$*" >> "$log_file" + requirement="" + for argument in "$@"; do + case "$argument" in + -require=*) + requirement="${argument#-require=}" + ;; + esac + done + if [ -z "$requirement" ]; then + echo "Unsupported fake go mod edit invocation: go mod $*" >&2 + exit 97 + fi + append_or_replace_requirement "$(pwd)/go.mod" "${requirement%@*}" "${requirement##*@}" + else + echo "Unsupported fake go mod invocation: go mod $*" >&2 + exit 96 + fi + ;; + get) + printf 'get %s\n' "$*" >> "$log_file" + append_or_replace_requirement "$(pwd)/go.mod" "${1%@*}" "${1##*@}" + ;; + *) + echo "Unsupported fake go command: go $command_name $*" >&2 + exit 95 + ;; +esac +EOF + chmod +x "$CURRENT_CASE_DIR/bin/go" + + cat > "$CURRENT_CASE_DIR/bin/orchestrion" <<'EOF' +#!/bin/bash +set -euo pipefail + +append_or_replace_requirement() { + local go_mod_path="$1" + local module_path="$2" + local module_version="$3" + local temp_path="${go_mod_path}.tmp" + + awk -v module_path="$module_path" ' + ($1 == module_path) || ($1 == "require" && $2 == module_path) { next } + { print } + ' "$go_mod_path" > "$temp_path" + mv "$temp_path" "$go_mod_path" + printf 'require %s %s\n' "$module_path" "$module_version" >> "$go_mod_path" +} + +command_name="${1:-}" +case "$command_name" in + pin) + if [ "${FAKE_FAIL_PIN:-0}" = "1" ]; then + exit 1 + fi + tracer_version="$(awk ' + ($1 == "github.com/DataDog/dd-trace-go/v2") { version = $2 } + ($1 == "require" && $2 == "github.com/DataDog/dd-trace-go/v2") { version = $3 } + END { print version } + ' go.mod)" + if [ -z "$tracer_version" ]; then + tracer_version="${FAKE_ORCHESTRION_DD_TRACE_VERSION:-v2.6.0}" + fi + append_or_replace_requirement "$(pwd)/go.mod" "github.com/DataDog/dd-trace-go/orchestrion/all/v2" "$tracer_version" + cat > orchestrion.tool.go <&2 + exit 94 + ;; +esac +EOF + chmod +x "$CURRENT_CASE_DIR/bin/orchestrion" +} + +# Create a clean per-scenario workspace and fake toolchain. +prepare_case() { + local case_name="$1" + CURRENT_CASE_DIR="$TEST_ROOT/$case_name" + CURRENT_WORKSPACE="$CURRENT_CASE_DIR/workspace" + mkdir -p "$CURRENT_WORKSPACE" + create_fake_toolchain +} + +# Run the install script inside the current scenario workspace. +run_install_script() { + LAST_STDOUT="$CURRENT_CASE_DIR/stdout.txt" + LAST_STDERR="$CURRENT_CASE_DIR/stderr.txt" + + set +e + ( + cd "$CURRENT_WORKSPACE" && + env \ + PATH="$CURRENT_CASE_DIR/bin:$PATH" \ + FAKE_LOG_DIR="$CURRENT_CASE_DIR/logs" \ + "$@" \ + bash "$SCRIPT_UNDER_TEST" > "$LAST_STDOUT" 2> "$LAST_STDERR" + ) + LAST_EXIT_CODE=$? + set -e +} + +# Execute one scenario in isolation and keep the matrix running on failures. +run_case() { + local case_name="$1" + shift + + if ( "$@" ); then + PASS_COUNT=$((PASS_COUNT + 1)) + printf 'PASS %s\n' "$case_name" + else + FAIL_COUNT=$((FAIL_COUNT + 1)) + printf 'FAIL %s\n' "$case_name" + fi +} + +# Validate the root-module happy path for a Go 1.24 project. +scenario_root_module_go_124() { + prepare_case "root_module_go_124" + write_go_mod "$CURRENT_WORKSPACE/go.mod" "example.com/root" "1.24" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_contains "$LAST_STDOUT" "GOFLAGS=" + assert_file_contains "$LAST_STDOUT" "DD_TRACER_VERSION_GO=v1.8.0" + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/dd-trace-go/orchestrion/all/v2 v2.6.0" + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/dd-trace-go/v2 v2.6.0" + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/orchestrion v1.8.0" + assert_file_exists "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate the root-module upgrade path for a Go 1.25 project. +scenario_root_module_go_125() { + prepare_case "root_module_go_125" + write_go_mod "$CURRENT_WORKSPACE/go.mod" "example.com/root" "1.25" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/dd-trace-go/orchestrion/all/v2 v2.7.0" + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/dd-trace-go/v2 v2.7.0" + assert_file_exists "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate that too-old projects are skipped without mutating go.mod. +scenario_root_module_go_122_skips() { + prepare_case "root_module_go_122_skips" + write_go_mod "$CURRENT_WORKSPACE/go.mod" "example.com/root" "1.22" + cp "$CURRENT_WORKSPACE/go.mod" "$CURRENT_CASE_DIR/initial.go.mod" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_contains "$LAST_STDERR" "Skipping orchestrion installation." + assert_file_not_contains "$LAST_STDOUT" "GOFLAGS=" + assert_files_equal "$CURRENT_CASE_DIR/initial.go.mod" "$CURRENT_WORKSPACE/go.mod" + assert_file_missing "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate the runner lower-bound check from orchestrion's own go.mod. +scenario_runner_below_orchestrion_minimum() { + prepare_case "runner_below_orchestrion_minimum" + write_go_mod "$CURRENT_WORKSPACE/go.mod" "example.com/root" "1.24" + cp "$CURRENT_WORKSPACE/go.mod" "$CURRENT_CASE_DIR/initial.go.mod" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 \ + FAKE_ORCHESTRION_GO_VERSION=1.27.0 + + assert_exit_code 0 + assert_file_contains "$LAST_STDERR" "does not meet the required version" + assert_file_not_contains "$LAST_STDOUT" "GOFLAGS=" + assert_files_equal "$CURRENT_CASE_DIR/initial.go.mod" "$CURRENT_WORKSPACE/go.mod" + assert_file_missing "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate the runner lower-bound check for the shipped tracer bundle. +scenario_runner_below_shipped_tracer_minimum() { + prepare_case "runner_below_shipped_tracer_minimum" + write_go_mod "$CURRENT_WORKSPACE/go.mod" "example.com/root" "1.25" + cp "$CURRENT_WORKSPACE/go.mod" "$CURRENT_CASE_DIR/initial.go.mod" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.24.0 \ + FAKE_ORCHESTRION_GO_VERSION=1.24.0 \ + FAKE_ORCHESTRION_DD_TRACE_VERSION=v2.7.0 + + assert_exit_code 0 + assert_file_contains "$LAST_STDERR" "lower than the minimum Go version required by dd-trace-go v2.7.0" + assert_file_not_contains "$LAST_STDOUT" "GOFLAGS=" + assert_files_equal "$CURRENT_CASE_DIR/initial.go.mod" "$CURRENT_WORKSPACE/go.mod" + assert_file_missing "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate nested-module auto-detection for a Go 1.24 project. +scenario_single_nested_module_go_124() { + prepare_case "single_nested_module_go_124" + mkdir -p "$CURRENT_WORKSPACE/app" + write_go_mod "$CURRENT_WORKSPACE/app/go.mod" "example.com/app" "1.24" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_contains "$CURRENT_WORKSPACE/app/go.mod" "github.com/DataDog/dd-trace-go/orchestrion/all/v2 v2.6.0" + assert_file_contains "$CURRENT_WORKSPACE/app/go.mod" "github.com/DataDog/dd-trace-go/v2 v2.6.0" + assert_file_exists "$CURRENT_WORKSPACE/app/orchestrion.tool.go" + assert_file_missing "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate nested-module auto-detection for a Go 1.25 project. +scenario_single_nested_module_go_125() { + prepare_case "single_nested_module_go_125" + mkdir -p "$CURRENT_WORKSPACE/app" + write_go_mod "$CURRENT_WORKSPACE/app/go.mod" "example.com/app" "1.25" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_contains "$CURRENT_WORKSPACE/app/go.mod" "github.com/DataDog/dd-trace-go/orchestrion/all/v2 v2.7.0" + assert_file_contains "$CURRENT_WORKSPACE/app/go.mod" "github.com/DataDog/dd-trace-go/v2 v2.7.0" + assert_file_exists "$CURRENT_WORKSPACE/app/orchestrion.tool.go" + assert_file_missing "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate the clean skip path for multiple nested modules without an override. +scenario_multiple_modules_without_override() { + prepare_case "multiple_modules_without_override" + mkdir -p "$CURRENT_WORKSPACE/api" "$CURRENT_WORKSPACE/worker" + write_go_mod "$CURRENT_WORKSPACE/api/go.mod" "example.com/api" "1.24" + write_go_mod "$CURRENT_WORKSPACE/worker/go.mod" "example.com/worker" "1.24" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_contains "$LAST_STDERR" "Set DD_CIVISIBILITY_GO_MODULE_DIR" + assert_file_missing "$CURRENT_WORKSPACE/api/orchestrion.tool.go" + assert_file_missing "$CURRENT_WORKSPACE/worker/orchestrion.tool.go" +} + +# Validate explicit module selection in a multi-module repository. +scenario_multiple_modules_with_override() { + prepare_case "multiple_modules_with_override" + mkdir -p "$CURRENT_WORKSPACE/api" "$CURRENT_WORKSPACE/worker" + write_go_mod "$CURRENT_WORKSPACE/api/go.mod" "example.com/api" "1.24" + write_go_mod "$CURRENT_WORKSPACE/worker/go.mod" "example.com/worker" "1.25" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + DD_CIVISIBILITY_GO_MODULE_DIR=worker \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_missing "$CURRENT_WORKSPACE/api/orchestrion.tool.go" + assert_file_exists "$CURRENT_WORKSPACE/worker/orchestrion.tool.go" + assert_file_contains "$CURRENT_WORKSPACE/worker/go.mod" "github.com/DataDog/dd-trace-go/orchestrion/all/v2 v2.7.0" + assert_file_not_contains "$CURRENT_WORKSPACE/api/go.mod" "github.com/DataDog/orchestrion" +} + +# Validate the explicit missing-directory failure path. +scenario_override_missing_directory() { + prepare_case "override_missing_directory" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + DD_CIVISIBILITY_GO_MODULE_DIR=missing \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 1 + assert_file_contains "$LAST_STDERR" "DD_CIVISIBILITY_GO_MODULE_DIR points to a directory that does not exist" + assert_file_not_contains "$LAST_STDOUT" "GOFLAGS=" +} + +# Validate the explicit "directory exists but has no go.mod" failure path. +scenario_override_without_go_mod() { + prepare_case "override_without_go_mod" + mkdir -p "$CURRENT_WORKSPACE/empty" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + DD_CIVISIBILITY_GO_MODULE_DIR=empty \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 1 + assert_file_contains "$LAST_STDERR" "DD_CIVISIBILITY_GO_MODULE_DIR does not contain a go.mod file" + assert_file_not_contains "$LAST_STDOUT" "GOFLAGS=" +} + +# Validate the clean skip path when no Go module exists anywhere. +scenario_no_go_mod_anywhere() { + prepare_case "no_go_mod_anywhere" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_contains "$LAST_STDERR" "Could not find a go.mod file" + assert_file_not_contains "$LAST_STDOUT" "GOFLAGS=" + assert_file_missing "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate the fallback path for projects without a go directive. +scenario_missing_go_directive_falls_back() { + prepare_case "missing_go_directive_falls_back" + write_go_mod_without_go_directive "$CURRENT_WORKSPACE/go.mod" "example.com/root" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 + + assert_exit_code 0 + assert_file_contains "$LAST_STDERR" "Could not read the project Go version" + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/dd-trace-go/orchestrion/all/v2 v2.6.0" + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/dd-trace-go/v2 v2.6.0" + assert_file_exists "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +# Validate the fallback path when the stable tracer list cannot be retrieved. +scenario_missing_stable_tracer_list_falls_back() { + prepare_case "missing_stable_tracer_list_falls_back" + write_go_mod "$CURRENT_WORKSPACE/go.mod" "example.com/root" "1.25" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 \ + FAKE_FAIL_STABLE_LIST=1 + + assert_exit_code 0 + assert_file_contains "$LAST_STDERR" "Could not retrieve the list of stable dd-trace-go versions." + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/dd-trace-go/orchestrion/all/v2 v2.6.0" + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/dd-trace-go/v2 v2.6.0" +} + +# Validate that "latest" is resolved once and reused consistently. +scenario_latest_resolved_once() { + prepare_case "latest_resolved_once" + write_go_mod "$CURRENT_WORKSPACE/go.mod" "example.com/root" "1.24" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=latest \ + FAKE_GO_VERSION=1.26.1 \ + FAKE_LATEST_ORCHESTRION_TAG=v1.8.0 + + assert_exit_code 0 + assert_file_contains "$CURRENT_CASE_DIR/logs/latest_requests.count" "1" + assert_file_contains "$CURRENT_CASE_DIR/logs/go.log" "install github.com/DataDog/orchestrion@v1.8.0" + assert_file_contains "$CURRENT_CASE_DIR/logs/go.log" "get github.com/DataDog/orchestrion@v1.8.0" + assert_file_contains "$CURRENT_WORKSPACE/go.mod" "github.com/DataDog/orchestrion v1.8.0" +} + +# Validate that a failed orchestrion install does not dirty go.mod. +scenario_failed_go_install_keeps_go_mod_clean() { + prepare_case "failed_go_install_keeps_go_mod_clean" + write_go_mod "$CURRENT_WORKSPACE/go.mod" "example.com/root" "1.24" + cp "$CURRENT_WORKSPACE/go.mod" "$CURRENT_CASE_DIR/initial.go.mod" + + run_install_script \ + DD_CIVISIBILITY_INSTRUMENTATION_LANGUAGES=go \ + DD_SET_TRACER_VERSION_GO=v1.8.0 \ + FAKE_GO_VERSION=1.26.1 \ + FAKE_FAIL_GO_INSTALL=1 + + assert_exit_code 1 + assert_file_contains "$LAST_STDERR" "Error: Could not install orchestrion for Go." + assert_files_equal "$CURRENT_CASE_DIR/initial.go.mod" "$CURRENT_WORKSPACE/go.mod" + assert_file_not_contains "$CURRENT_CASE_DIR/logs/go.log" "mod edit" + assert_file_missing "$CURRENT_WORKSPACE/orchestrion.tool.go" +} + +main() { + run_case "root_module_go_124" scenario_root_module_go_124 + run_case "root_module_go_125" scenario_root_module_go_125 + run_case "root_module_go_122_skips" scenario_root_module_go_122_skips + run_case "runner_below_orchestrion_minimum" scenario_runner_below_orchestrion_minimum + run_case "runner_below_shipped_tracer_minimum" scenario_runner_below_shipped_tracer_minimum + run_case "single_nested_module_go_124" scenario_single_nested_module_go_124 + run_case "single_nested_module_go_125" scenario_single_nested_module_go_125 + run_case "multiple_modules_without_override" scenario_multiple_modules_without_override + run_case "multiple_modules_with_override" scenario_multiple_modules_with_override + run_case "override_missing_directory" scenario_override_missing_directory + run_case "override_without_go_mod" scenario_override_without_go_mod + run_case "no_go_mod_anywhere" scenario_no_go_mod_anywhere + run_case "missing_go_directive_falls_back" scenario_missing_go_directive_falls_back + run_case "missing_stable_tracer_list_falls_back" scenario_missing_stable_tracer_list_falls_back + run_case "latest_resolved_once" scenario_latest_resolved_once + run_case "failed_go_install_keeps_go_mod_clean" scenario_failed_go_install_keeps_go_mod_clean + + printf '\nGo matrix: %s passed, %s failed\n' "$PASS_COUNT" "$FAIL_COUNT" + if [ "$FAIL_COUNT" -ne 0 ]; then + exit 1 + fi +} + +main "$@" From e94065a74e1343e7afbf678f02e75fc9df918e19 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Wed, 25 Mar 2026 16:24:01 +0100 Subject: [PATCH 7/7] Add GitHub Actions workflow for Go matrix --- .github/workflows/go-test-matrix.yml | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/go-test-matrix.yml diff --git a/.github/workflows/go-test-matrix.yml b/.github/workflows/go-test-matrix.yml new file mode 100644 index 0000000..8d0a061 --- /dev/null +++ b/.github/workflows/go-test-matrix.yml @@ -0,0 +1,41 @@ +name: Go Test Matrix + +on: + pull_request: + paths: + - install_test_visibility.sh + - tests/run_go_matrix.sh + - .github/workflows/go-test-matrix.yml + push: + branches: + - main + - codex/** + paths: + - install_test_visibility.sh + - tests/run_go_matrix.sh + - .github/workflows/go-test-matrix.yml + workflow_dispatch: + +jobs: + go-test-matrix: + name: go matrix (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Verify shell syntax + run: | + bash -n install_test_visibility.sh + bash -n tests/run_go_matrix.sh + /bin/bash -n tests/run_go_matrix.sh + + - name: Run Go test matrix + run: tests/run_go_matrix.sh