From 741f7b8140ef5ac28ca791e33bcf424554289c41 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Tue, 10 Mar 2026 23:02:58 -0400 Subject: [PATCH 1/3] Support multiple [lang@branch] overrides in PR title Previously, specifying a tracer branch override in the PR title (e.g. [java@my-branch]) only supported a single override at a time. This change allows multiple overrides like [java@branch1][dotnet@branch2] so that different libraries can each use their own dev branch. The get_target_branch action now outputs a JSON map of library to branch, and the compute-workflow-parameters workflow extracts the correct branch for each library before passing it to load-binary.sh. Backward compatibility with plain branch strings is preserved. Co-Authored-By: Claude Opus 4.6 --- .github/actions/get_target_branch/action.yaml | 35 ++++++++++++++++--- .github/workflows/ci.yml | 2 +- .../workflows/compute-workflow-parameters.yml | 26 +++++++++++++- .../compute_libraries_and_scenarios.yml | 6 +++- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/.github/actions/get_target_branch/action.yaml b/.github/actions/get_target_branch/action.yaml index 1fb76cdf266..f679155b5b0 100644 --- a/.github/actions/get_target_branch/action.yaml +++ b/.github/actions/get_target_branch/action.yaml @@ -1,13 +1,16 @@ name: Get_target_branch -description: "Gets target branch from the PR description" +description: "Gets target branch from the PR description. Supports multiple [lang@branch] pairs." inputs: text: description: "Text from which to extract the target branch" required: true outputs: target-branch: - description: "Target branch" + description: "JSON map of library to target branch, e.g. {\"java\":\"my-branch\",\"dotnet\":\"other-branch\"}" value: ${{ steps.extract.outputs.target-branch }} + has-target-branch: + description: "Whether any target branch was found" + value: ${{ steps.extract.outputs.has-target-branch }} runs: using: composite @@ -17,9 +20,33 @@ runs: shell: bash run: | LIBS=$(PYTHONPATH=. python3 utils/const/__main__.py COMPONENT_GROUPS all) - branch=$(echo "${INPUTS_TEXT}" | grep -ioP "\[(?:${LIBS}|agent)@[^]]+(?=\])" | tr -d '[:space:]' || true) + # Match all [lang@branch] pairs from the PR title + matches=$(echo "${INPUTS_TEXT}" | grep -ioP "\[(?:${LIBS}|agent)@[^]]+(?=\])" || true) - echo "target-branch=${branch#*@}" >> $GITHUB_OUTPUT + if [ -z "$matches" ]; then + echo "target-branch=" >> $GITHUB_OUTPUT + echo "has-target-branch=false" >> $GITHUB_OUTPUT + else + # Build a JSON map of {library: branch} from all matches + json="{" + first=true + while IFS= read -r match; do + # match is like "[java@my-branch" (without closing bracket) + match=$(echo "$match" | tr -d '[:space:]') + lib=$(echo "$match" | sed 's/^\[//' | cut -d'@' -f1 | tr '[:upper:]' '[:lower:]') + branch=$(echo "$match" | cut -d'@' -f2-) + if [ "$first" = true ]; then + first=false + else + json="$json," + fi + json="$json\"$lib\":\"$branch\"" + done <<< "$matches" + json="$json}" + + echo "target-branch=$json" >> $GITHUB_OUTPUT + echo "has-target-branch=true" >> $GITHUB_OUTPUT + fi # the preferred approach to handling untrusted input is to set the value of the expression to an intermediate environment variable env: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37f7ec3db49..0bcd61be235 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: name: Fail if target branch is specified needs: - compute_libraries_and_scenarios - if: needs.compute_libraries_and_scenarios.outputs.target-branch != '' + if: needs.compute_libraries_and_scenarios.outputs.has-target-branch == 'true' runs-on: ubuntu-latest steps: - name: Fail if PR title contains a target branch diff --git a/.github/workflows/compute-workflow-parameters.yml b/.github/workflows/compute-workflow-parameters.yml index 93415c7777a..8914485f46e 100644 --- a/.github/workflows/compute-workflow-parameters.yml +++ b/.github/workflows/compute-workflow-parameters.yml @@ -152,13 +152,37 @@ jobs: --system-tests-dev-mode "${{ inputs._system_tests_dev_mode }}" \ --format json | jq + - name: Extract library target branch + if: ${{ inputs._system_tests_dev_mode }} + id: extract_branch + shell: bash + run: | + TARGET_BRANCH_MAP='${{ inputs._system_tests_library_target_branch }}' + LIBRARY='${{ inputs.library }}' + + if [ -z "$TARGET_BRANCH_MAP" ]; then + echo "library_branch=" >> $GITHUB_OUTPUT + echo "agent_branch=" >> $GITHUB_OUTPUT + elif echo "$TARGET_BRANCH_MAP" | jq empty 2>/dev/null; then + # It's valid JSON — extract the branch for this library and agent + library_branch=$(echo "$TARGET_BRANCH_MAP" | jq -r --arg lib "$LIBRARY" '.[$lib] // empty') + agent_branch=$(echo "$TARGET_BRANCH_MAP" | jq -r '.agent // empty') + echo "library_branch=$library_branch" >> $GITHUB_OUTPUT + echo "agent_branch=$agent_branch" >> $GITHUB_OUTPUT + else + # Legacy: plain branch string (backward compatibility) + echo "library_branch=$TARGET_BRANCH_MAP" >> $GITHUB_OUTPUT + echo "agent_branch=" >> $GITHUB_OUTPUT + fi + - name: Get dev artifacts if: ${{ inputs._system_tests_dev_mode }} run: | ./utils/scripts/load-binary.sh agent ./utils/scripts/load-binary.sh ${{ inputs.library }} env: - LIBRARY_TARGET_BRANCH: "${{ inputs._system_tests_library_target_branch }}" + LIBRARY_TARGET_BRANCH: "${{ steps.extract_branch.outputs.library_branch }}" + AGENT_TARGET_BRANCH: "${{ steps.extract_branch.outputs.agent_branch }}" CIRCLECI_TOKEN: ${{ secrets.CIRCLECI_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/compute_libraries_and_scenarios.yml b/.github/workflows/compute_libraries_and_scenarios.yml index f22b6a36117..4f8c439c4cd 100644 --- a/.github/workflows/compute_libraries_and_scenarios.yml +++ b/.github/workflows/compute_libraries_and_scenarios.yml @@ -14,8 +14,11 @@ on: description: "Time in seconds" value: ${{ jobs.main.outputs.desired_execution_time }} target-branch: - description: "dd-trace branch to test" + description: "JSON map of library to target branch" value: ${{ jobs.main.outputs.target-branch }} + has-target-branch: + description: "Whether any target branch was found" + value: ${{ jobs.main.outputs.has-target-branch }} rebuild_lambda_proxy: description: "Should the lambda-proxy image be rebuilt" value: ${{ jobs.main.outputs.rebuild_lambda_proxy }} @@ -35,6 +38,7 @@ jobs: libraries_with_dev: ${{ steps.compute.outputs.libraries_with_dev }} desired_execution_time: ${{ steps.compute.outputs.desired_execution_time }} target-branch: ${{ steps.get-target-branch.outputs.target-branch }} + has-target-branch: ${{ steps.get-target-branch.outputs.has-target-branch }} rebuild_lambda_proxy: ${{ steps.compute.outputs.rebuild_lambda_proxy }} scenarios: ${{ steps.compute.outputs.scenarios }} scenarios_groups: ${{ steps.compute.outputs.scenarios_groups }} From c04ec34d659d2d6299d3aef1c65f9528ac6ad13d Mon Sep 17 00:00:00 2001 From: bm1549 Date: Tue, 10 Mar 2026 23:19:01 -0400 Subject: [PATCH 2/3] docs: update CI documentation for multiple branch overrides Update target branch selection docs to reflect that multiple [lang@branch] pairs can now be specified simultaneously in the PR title. Update the workflow parameter description to note JSON map support. Co-Authored-By: Claude Opus 4.6 --- docs/CI/github-actions.md | 2 +- docs/CI/system-tests-ci.md | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/CI/github-actions.md b/docs/CI/github-actions.md index e20205aa60a..105b2e229fc 100644 --- a/docs/CI/github-actions.md +++ b/docs/CI/github-actions.md @@ -86,7 +86,7 @@ Those parameters are used only by system-tests own CI | Name | Description | Type | Required | Default | | ------------------------------------- | ---------------------------------------------------------------------| ------- | -------- | ---------- | | `_system_tests_dev_mode` | Shall we run system tests in dev mode (library and agent dev binary) | boolean | false | false | -| `_system_tests_library_target_branch` | If system-tests dev mode, the branch to use for the library | string | false | | +| `_system_tests_library_target_branch` | If system-tests dev mode, the branch to use for the library. Accepts a plain branch string (legacy) or a JSON map of library to branch (e.g. `{"java":"b1","dotnet":"b2"}`) | string | false | | | `_build_buddies_images` | Shall we build buddies images | boolean | false | false | | `_build_proxy_image` | Shall we build proxy image | boolean | false | false | | `_build_lambda_proxy_image` | Shall we build the lambda-proxy image | boolean | false | false | diff --git a/docs/CI/system-tests-ci.md b/docs/CI/system-tests-ci.md index 80319a8528d..54f425d3410 100644 --- a/docs/CI/system-tests-ci.md +++ b/docs/CI/system-tests-ci.md @@ -16,9 +16,13 @@ By default, after some basic test/lint jobs, this pipeline build alls weblogs (6 ### Target branch selection `dev` version uses `main` / `master` branch by default but, in the case of some libraries (cpp, agent, nodejs, python, ruby, and dotnet), it is possible to configure the CI to use a target branch. -If the PR's title includes `[target_library@branch_name_to_test]` the workflow will use `branch_name_to_test` instead of `main/master` branch. +If the PR's title includes `[target_library@branch_name_to_test]` the workflow will use `branch_name_to_test` instead of `main/master` branch. Multiple branch overrides can be specified simultaneously for different libraries: -At the moment, it is not possible to run the CI for all libraries in a particular branch but it is limited to the target library indicated in the title. +``` +My PR title [java@my-java-branch][dotnet@my-dotnet-branch][ruby@my-ruby-branch] +``` + +Each library in the CI matrix will use its own specified branch. Libraries without an override continue to use the default branch. As a security measure, the "Fail if target branch is specified" job always fails if a target branch is selected. From 820dfa4c4529e978f535828e8c541be70bc19118 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Thu, 19 Mar 2026 17:23:58 -0400 Subject: [PATCH 3/3] Address PR feedback: Python rewrite, rename outputs, drop legacy - Rewrite bash scripts to Python (shell: python) in get_target_branch and compute-workflow-parameters extract step - Rename target-branch -> target-branch-map across all workflows - Rename _system_tests_library_target_branch -> _system_tests_library_target_branch_map - Remove legacy plain-string backward compatibility path - Add setup-python action for get_target_branch - Update docs accordingly Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/actions/get_target_branch/action.yaml | 62 ++++++++++--------- .github/workflows/ci.yml | 2 +- .../workflows/compute-workflow-parameters.yml | 41 ++++++------ .../compute_libraries_and_scenarios.yml | 8 +-- .github/workflows/system-tests.yml | 6 +- docs/CI/github-actions.md | 2 +- 6 files changed, 63 insertions(+), 58 deletions(-) diff --git a/.github/actions/get_target_branch/action.yaml b/.github/actions/get_target_branch/action.yaml index f679155b5b0..cc6115011d2 100644 --- a/.github/actions/get_target_branch/action.yaml +++ b/.github/actions/get_target_branch/action.yaml @@ -5,9 +5,9 @@ inputs: description: "Text from which to extract the target branch" required: true outputs: - target-branch: + target-branch-map: description: "JSON map of library to target branch, e.g. {\"java\":\"my-branch\",\"dotnet\":\"other-branch\"}" - value: ${{ steps.extract.outputs.target-branch }} + value: ${{ steps.extract.outputs.target-branch-map }} has-target-branch: description: "Whether any target branch was found" value: ${{ steps.extract.outputs.has-target-branch }} @@ -15,39 +15,41 @@ outputs: runs: using: composite steps: + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: '3.12' - name: Extract target branch id: extract - shell: bash + shell: python run: | - LIBS=$(PYTHONPATH=. python3 utils/const/__main__.py COMPONENT_GROUPS all) - # Match all [lang@branch] pairs from the PR title - matches=$(echo "${INPUTS_TEXT}" | grep -ioP "\[(?:${LIBS}|agent)@[^]]+(?=\])" || true) + import json + import os + import re + import subprocess - if [ -z "$matches" ]; then - echo "target-branch=" >> $GITHUB_OUTPUT - echo "has-target-branch=false" >> $GITHUB_OUTPUT - else - # Build a JSON map of {library: branch} from all matches - json="{" - first=true - while IFS= read -r match; do - # match is like "[java@my-branch" (without closing bracket) - match=$(echo "$match" | tr -d '[:space:]') - lib=$(echo "$match" | sed 's/^\[//' | cut -d'@' -f1 | tr '[:upper:]' '[:lower:]') - branch=$(echo "$match" | cut -d'@' -f2-) - if [ "$first" = true ]; then - first=false - else - json="$json," - fi - json="$json\"$lib\":\"$branch\"" - done <<< "$matches" - json="$json}" + result = subprocess.run( + ["python3", "utils/const/__main__.py", "COMPONENT_GROUPS", "all"], + capture_output=True, text=True, env={**os.environ, "PYTHONPATH": "."}, + ) + libs = result.stdout.strip().split("|") + libs.append("agent") - echo "target-branch=$json" >> $GITHUB_OUTPUT - echo "has-target-branch=true" >> $GITHUB_OUTPUT - fi + text = os.environ["INPUTS_TEXT"] + pattern = re.compile(r"\[([^@\]]+)@([^\]]+)\]", re.IGNORECASE) - # the preferred approach to handling untrusted input is to set the value of the expression to an intermediate environment variable + branch_map = {} + for match in pattern.finditer(text): + lib = match.group(1).lower() + branch = match.group(2) + if lib in libs: + branch_map[lib] = branch + + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + if branch_map: + f.write(f"target-branch-map={json.dumps(branch_map)}\n") + f.write("has-target-branch=true\n") + else: + f.write("target-branch-map=\n") + f.write("has-target-branch=false\n") env: INPUTS_TEXT: ${{ inputs.text }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bcd61be235..5ce958431fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,7 +98,7 @@ jobs: _build_python_base_images: ${{ contains(github.event.pull_request.labels.*.name, 'build-python-base-images') }} _enable_replay_scenarios: true _system_tests_dev_mode: ${{ matrix.version == 'dev' }} - _system_tests_library_target_branch: ${{ needs.compute_libraries_and_scenarios.outputs.target-branch }} + _system_tests_library_target_branch_map: ${{ needs.compute_libraries_and_scenarios.outputs.target-branch-map }} push_to_test_optimization: true exotics: diff --git a/.github/workflows/compute-workflow-parameters.yml b/.github/workflows/compute-workflow-parameters.yml index 8914485f46e..3ec57208b3b 100644 --- a/.github/workflows/compute-workflow-parameters.yml +++ b/.github/workflows/compute-workflow-parameters.yml @@ -47,8 +47,8 @@ on: default: false required: false type: boolean - _system_tests_library_target_branch: - description: "If system-tests dev mode, the branch to use for the library" + _system_tests_library_target_branch_map: + description: "If system-tests dev mode, JSON map of library to branch (e.g. {\"java\":\"b1\",\"dotnet\":\"b2\"})" default: '' required: false type: string @@ -155,25 +155,28 @@ jobs: - name: Extract library target branch if: ${{ inputs._system_tests_dev_mode }} id: extract_branch - shell: bash + shell: python run: | - TARGET_BRANCH_MAP='${{ inputs._system_tests_library_target_branch }}' - LIBRARY='${{ inputs.library }}' + import json + import os - if [ -z "$TARGET_BRANCH_MAP" ]; then - echo "library_branch=" >> $GITHUB_OUTPUT - echo "agent_branch=" >> $GITHUB_OUTPUT - elif echo "$TARGET_BRANCH_MAP" | jq empty 2>/dev/null; then - # It's valid JSON — extract the branch for this library and agent - library_branch=$(echo "$TARGET_BRANCH_MAP" | jq -r --arg lib "$LIBRARY" '.[$lib] // empty') - agent_branch=$(echo "$TARGET_BRANCH_MAP" | jq -r '.agent // empty') - echo "library_branch=$library_branch" >> $GITHUB_OUTPUT - echo "agent_branch=$agent_branch" >> $GITHUB_OUTPUT - else - # Legacy: plain branch string (backward compatibility) - echo "library_branch=$TARGET_BRANCH_MAP" >> $GITHUB_OUTPUT - echo "agent_branch=" >> $GITHUB_OUTPUT - fi + branch_map_str = os.environ.get("TARGET_BRANCH_MAP", "") + library = os.environ.get("LIBRARY", "") + + library_branch = "" + agent_branch = "" + + if branch_map_str: + branch_map = json.loads(branch_map_str) + library_branch = branch_map.get(library, "") + agent_branch = branch_map.get("agent", "") + + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + f.write(f"library_branch={library_branch}\n") + f.write(f"agent_branch={agent_branch}\n") + env: + TARGET_BRANCH_MAP: ${{ inputs._system_tests_library_target_branch_map }} + LIBRARY: ${{ inputs.library }} - name: Get dev artifacts if: ${{ inputs._system_tests_dev_mode }} diff --git a/.github/workflows/compute_libraries_and_scenarios.yml b/.github/workflows/compute_libraries_and_scenarios.yml index 4f8c439c4cd..76238c4f832 100644 --- a/.github/workflows/compute_libraries_and_scenarios.yml +++ b/.github/workflows/compute_libraries_and_scenarios.yml @@ -13,9 +13,9 @@ on: desired_execution_time: description: "Time in seconds" value: ${{ jobs.main.outputs.desired_execution_time }} - target-branch: + target-branch-map: description: "JSON map of library to target branch" - value: ${{ jobs.main.outputs.target-branch }} + value: ${{ jobs.main.outputs.target-branch-map }} has-target-branch: description: "Whether any target branch was found" value: ${{ jobs.main.outputs.has-target-branch }} @@ -37,7 +37,7 @@ jobs: library_matrix: ${{ steps.compute.outputs.library_matrix }} libraries_with_dev: ${{ steps.compute.outputs.libraries_with_dev }} desired_execution_time: ${{ steps.compute.outputs.desired_execution_time }} - target-branch: ${{ steps.get-target-branch.outputs.target-branch }} + target-branch-map: ${{ steps.get-target-branch.outputs.target-branch-map }} has-target-branch: ${{ steps.get-target-branch.outputs.has-target-branch }} rebuild_lambda_proxy: ${{ steps.compute.outputs.rebuild_lambda_proxy }} scenarios: ${{ steps.compute.outputs.scenarios }} @@ -86,7 +86,7 @@ jobs: run: | echo 'Impacted libraries: -> ${{ steps.compute.outputs.library_matrix }}' echo 'Desired execution time: -> ${{ steps.compute.outputs.desired_execution_time }}' - echo 'Target branch: -> ${{ steps.get-target-branch.outputs.target-branch }}' + echo 'Target branch map: -> ${{ steps.get-target-branch.outputs.target-branch-map }}' echo 'Rebuild lambda-proxy: -> ${{ steps.compute.outputs.rebuild_lambda_proxy }}' echo 'Scenarios: -> ${{ steps.compute.outputs.scenarios }}' echo 'Scenario Groups: -> ${{ steps.compute.outputs.scenarios_groups }}' diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 82df1f8ec0f..1028feeca4f 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -99,8 +99,8 @@ on: default: false required: false type: boolean - _system_tests_library_target_branch: - description: "If system-tests dev mode, the branch to use for the library" + _system_tests_library_target_branch_map: + description: "If system-tests dev mode, JSON map of library to branch (e.g. {\"java\":\"b1\",\"dotnet\":\"b2\"})" default: '' required: false type: string @@ -162,7 +162,7 @@ jobs: desired_execution_time: ${{ inputs.desired_execution_time }} binaries_artifact: ${{ inputs.binaries_artifact }} _system_tests_dev_mode: ${{ inputs._system_tests_dev_mode }} - _system_tests_library_target_branch: ${{ inputs._system_tests_library_target_branch }} + _system_tests_library_target_branch_map: ${{ inputs._system_tests_library_target_branch_map }} parametric: needs: diff --git a/docs/CI/github-actions.md b/docs/CI/github-actions.md index 105b2e229fc..d59eaae83ff 100644 --- a/docs/CI/github-actions.md +++ b/docs/CI/github-actions.md @@ -86,7 +86,7 @@ Those parameters are used only by system-tests own CI | Name | Description | Type | Required | Default | | ------------------------------------- | ---------------------------------------------------------------------| ------- | -------- | ---------- | | `_system_tests_dev_mode` | Shall we run system tests in dev mode (library and agent dev binary) | boolean | false | false | -| `_system_tests_library_target_branch` | If system-tests dev mode, the branch to use for the library. Accepts a plain branch string (legacy) or a JSON map of library to branch (e.g. `{"java":"b1","dotnet":"b2"}`) | string | false | | +| `_system_tests_library_target_branch_map` | If system-tests dev mode, JSON map of library to branch (e.g. `{"java":"b1","dotnet":"b2"}`) | string | false | | | `_build_buddies_images` | Shall we build buddies images | boolean | false | false | | `_build_proxy_image` | Shall we build proxy image | boolean | false | false | | `_build_lambda_proxy_image` | Shall we build the lambda-proxy image | boolean | false | false |