From c4352ca2ed52b6e0d7d28b3a1285212950206f14 Mon Sep 17 00:00:00 2001 From: Roberto Montero Date: Tue, 27 Oct 2026 11:31:46 +0100 Subject: [PATCH 1/2] Shorten K8s lib injection GitLab job names Keep the image path in the CI matrix and restore the registry host in the job, so job names no longer contain the full ECR URL. Co-authored-by: Cursor --- .gitlab/ssi_gitlab-ci.yml | 14 ++ .../understand/scenarios/k8s_lib_injection.md | 2 +- .../test_k8s_ci_matrix_labels.py | 64 +++++++++ .../ci_orchestrators/gitlab_exporter.py | 126 ++++++------------ 4 files changed, 122 insertions(+), 84 deletions(-) create mode 100644 tests/test_the_test/test_k8s_ci_matrix_labels.py diff --git a/.gitlab/ssi_gitlab-ci.yml b/.gitlab/ssi_gitlab-ci.yml index 0c66ced2557..e454766889d 100644 --- a/.gitlab/ssi_gitlab-ci.yml +++ b/.gitlab/ssi_gitlab-ci.yml @@ -252,6 +252,20 @@ ssi_tests: # Run the tests and capture stdout+stderr so the after_script can # re-print the run summary (pipefail keeps run.sh's real exit code). set -o pipefail + # Catalog entries are stored as ssi/... or ssi-dev/.... Put the registry back. + # Any other value is a custom image reference and must be used as given. + expand_k8s_ref() { + case "$1" in + ssi/*|ssi-dev/*) printf '%s/%s' "${PRIVATE_DOCKER_REGISTRY}" "$1" ;; + *) printf '%s' "$1" ;; + esac + } + if [ -z "${K8S_WEBLOG_IMG:-}" ]; then + K8S_WEBLOG_IMG="${PRIVATE_DOCKER_REGISTRY}/system-tests/${K8S_WEBLOG}" + fi + K8S_LIB_INIT_IMG=$(expand_k8s_ref "${K8S_LIB_INIT_IMG}") + K8S_INJECTOR_IMG=$(expand_k8s_ref "${K8S_INJECTOR_IMG:-}") + K8S_CLUSTER_IMG=$(expand_k8s_ref "${K8S_CLUSTER_IMG:-}") ./run.sh ${K8S_SCENARIO} --k8s-library ${TEST_LIBRARY} --k8s-weblog ${K8S_WEBLOG} --k8s-weblog-img ${K8S_WEBLOG_IMG}:${TAG_NAME} --k8s-lib-init-img ${K8S_LIB_INIT_IMG} --k8s-injector-img ${K8S_INJECTOR_IMG:-None} --k8s-cluster-img ${K8S_CLUSTER_IMG:-None} --report-run-url $CI_JOB_URL --report-environment ${REPORT_ENVIRONMENT} 2>&1 | tee "${CI_PROJECT_DIR}/run_output.log" after_script: | diff --git a/docs/understand/scenarios/k8s_lib_injection.md b/docs/understand/scenarios/k8s_lib_injection.md index 131e8b0498a..9632e991243 100644 --- a/docs/understand/scenarios/k8s_lib_injection.md +++ b/docs/understand/scenarios/k8s_lib_injection.md @@ -417,7 +417,7 @@ export K8S_LIB_INIT_IMG="my-registry/my-lib-init:my-tag" ### Implementation Details - **Parser**: [`utils/k8s/k8s_components_parser.py`](../../utils/k8s/k8s_components_parser.py) - Singleton that reads `k8s_components.json` -- **CI Matrix Builder**: [`utils/scripts/ci_orchestrators/gitlab_exporter.py`](../../utils/scripts/ci_orchestrators/gitlab_exporter.py) - Generates test matrix for GitLab CI +- **CI Matrix Builder**: [`utils/scripts/ci_orchestrators/gitlab_exporter.py`](../../utils/scripts/ci_orchestrators/gitlab_exporter.py) - Generates test matrix for GitLab CI. Image values in the job name are the path without the registry host (`ssi/cluster-agent:7.84.3`). The job prepends `PRIVATE_DOCKER_REGISTRY` before `./run.sh`. - **Scenario Configuration**: [`utils/_context/_scenarios/k8s_lib_injection.py`](../../utils/_context/_scenarios/k8s_lib_injection.py) - Uses parser to load default versions diff --git a/tests/test_the_test/test_k8s_ci_matrix_labels.py b/tests/test_the_test/test_k8s_ci_matrix_labels.py new file mode 100644 index 00000000000..ca819dc691c --- /dev/null +++ b/tests/test_the_test/test_k8s_ci_matrix_labels.py @@ -0,0 +1,64 @@ +"""GitLab K8s matrix values omit the ECR host. The job puts the registry back.""" + +from utils import pytest, scenarios +from utils._context._scenarios import get_all_scenarios +from utils.k8s.k8s_components_parser import K8sComponentsParser +from utils.scripts.ci_orchestrators.gitlab_exporter import ( + _short_image_ref, + get_k8s_scenario_components, + print_k8s_gitlab_pipeline, +) + +_IMAGE_ENV = ("K8S_INJECTOR_IMG", "K8S_LIB_INIT_IMG", "K8S_WEBLOG_IMG") + + +def _clear_image_env(monkeypatch: pytest.MonkeyPatch) -> None: + for name in _IMAGE_ENV: + monkeypatch.delenv(name, raising=False) + + +@scenarios.test_the_test +def test_short_image_ref_drops_only_the_ecr_host(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("PRIVATE_DOCKER_REGISTRY", raising=False) + host = "235494822917.dkr.ecr.us-east-1.amazonaws.com" + assert _short_image_ref(f"{host}/ssi/cluster-agent:7.84.3") == "ssi/cluster-agent:7.84.3" + assert _short_image_ref(f"{host}/ssi-dev/cluster-agent-dev:master") == "ssi-dev/cluster-agent-dev:master" + assert _short_image_ref("3.257.1") == "3.257.1" + custom = "my-registry/my-lib-init:my-tag" + assert _short_image_ref(custom) == custom + other_account = "486234852809.dkr.ecr.us-east-1.amazonaws.com/ssi/apm-inject:abc" + assert _short_image_ref(other_account) == other_account + other_path = f"{host}/team/custom:tag" + assert _short_image_ref(other_path) == other_path + + +@scenarios.test_the_test +def test_matrix_job_name_uses_the_image_path(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("CI_PROJECT_NAME", "system-tests") + monkeypatch.setenv("CI_PIPELINE_SOURCE", "push") + monkeypatch.delenv("PRIVATE_DOCKER_REGISTRY", raising=False) + _clear_image_env(monkeypatch) + + matrix = get_k8s_scenario_components("K8S_LIB_INJECTION_PROFILING_ENABLED", get_all_scenarios(), "php") + parser = K8sComponentsParser() + cluster = parser.get_default_component_version("cluster_agent") + helm = parser.get_default_component_version("helm_chart") + + assert matrix["K8S_CLUSTER_IMG"] == cluster.split("/", 1)[1] + assert matrix["K8S_HELM_CHART"] == helm + assert matrix["K8S_INJECTOR_IMG"] == [ + item.split("/", 1)[1] for item in parser.get_all_component_versions("injector") + ] + assert "235494822917" not in str(matrix) + assert "${PRIVATE_DOCKER_REGISTRY}" not in str(matrix) + + pipeline: dict = {"stages": []} + print_k8s_gitlab_pipeline( + "php", + {"K8S_LIB_INJECTION_PROFILING_ENABLED": ["dd-lib-php-init-test-app"]}, + "dev", + pipeline, + ) + entry = pipeline["K8S_LIB_INJECTION_PROFILING_ENABLED"]["parallel"]["matrix"][0] + assert entry["K8S_WEBLOG"] == "dd-lib-php-init-test-app" + assert "K8S_WEBLOG_IMG" not in entry diff --git a/utils/scripts/ci_orchestrators/gitlab_exporter.py b/utils/scripts/ci_orchestrators/gitlab_exporter.py index 273cf14cef7..daf370a8248 100644 --- a/utils/scripts/ci_orchestrators/gitlab_exporter.py +++ b/utils/scripts/ci_orchestrators/gitlab_exporter.py @@ -126,80 +126,50 @@ def print_ssi_gitlab_pipeline(language: str, matrix_data: dict[str, dict], ci_en print("Pipeline file generated: ", output_file) -def get_k8s_scenario_components( - scenario: str, all_system_tests_scenarios: list[Scenario], language: str -) -> dict[str, str | list[str]]: - """Determine K8s component versions for a given scenario and CI context. - - This method selects the appropriate component versions based on: - - The scenario requirements (operator, cluster agent) - - The CI pipeline context (system-tests (scheduled or pr/commit) or tracer repo or injector repo) - - Custom image overrides from environment variables - - Version Selection Logic: - ======================== - - For each component, versions are selected based on pipeline context: - - 1. **Custom Image (Highest Priority)** - - If K8S_LIB_INIT_IMG or K8S_INJECTOR_IMG env vars are set - - Uses the custom image directly (injector/tracer repo testing) - - The rest of the components are the pinned/default versions - - 2. **System-Tests Scheduled Pipeline** - - Tests ALL available versions (prod, dev, pinned) - - 3. **System-Tests PR/Commit** - - Tests ALL available versions (for injector and lib init) - - Tests only the pinned versions for the rest of the components +def _short_image_ref(value: str) -> str: + """Drop our registry host from catalog images so the job name keeps the path. + Only ``ssi/`` and ``ssi-dev/`` images on ``PRIVATE_DOCKER_REGISTRY`` are shortened. + The job adds that same host back. A custom image on any other host stays intact. + """ + host, slash, path = value.partition("/") + registry = os.getenv("PRIVATE_DOCKER_REGISTRY", "235494822917.dkr.ecr.us-east-1.amazonaws.com") + if slash and host == registry and path.startswith(("ssi/", "ssi-dev/")): + return path + return value - Component Versions Returned: - ============================ - - K8S_HELM_CHART_OPERATOR: Datadog operator helm chart version (if with_datadog_operator) - - K8S_CLUSTER_IMG: Cluster agent image (if with_cluster_agent) - - K8S_INJECTOR_IMG: APM injector image (if with_cluster_agent) - - K8S_HELM_CHART: Datadog helm chart version (if cluster agent without operator) - - K8S_LIB_INIT_IMG: Library init container image (always included) - - Args: - scenario: Scenario name (must exist in utils/_context/_scenarios/__init__.py) - all_system_tests_scenarios: List of all available scenarios - language: Programming language (java, python, nodejs, etc.) - Returns: - Dictionary with component version assignments for GitLab CI matrix +def _as_job_name(value: str | list[str]) -> str | list[str]: + if isinstance(value, list): + return [_short_image_ref(item) for item in value] + return _short_image_ref(value) - """ - # Load all valid scenarios from the scenarios module - all_valid_scenarios = {s.name for s in all_system_tests_scenarios} +def get_k8s_scenario_components( + scenario: str, all_system_tests_scenarios: list[Scenario], language: str +) -> dict[str, str | list[str]]: + """Component versions for one scenario. Image values are registry paths, not full URLs.""" + all_valid_scenarios = {item.name for item in all_system_tests_scenarios} if scenario not in all_valid_scenarios: raise ValueError( f"Scenario '{scenario}' not found in utils/_context/_scenarios/__init__.py. " - f"Please ensure the scenario is properly defined." + "Please ensure the scenario is properly defined." ) - # Extract environment variables for CI context ci_project_name = os.getenv("CI_PROJECT_NAME") ci_pipeline_source = os.getenv("CI_PIPELINE_SOURCE") k8s_injector_img = os.getenv("K8S_INJECTOR_IMG") k8s_lib_init_img = os.getenv("K8S_LIB_INIT_IMG") - - # Determine CI context is_system_tests = ci_project_name == "system-tests" is_system_tests_scheduled = is_system_tests and ci_pipeline_source == "schedule" - # Get scenario configuration - scenario_obj = next((s for s in all_system_tests_scenarios if s.name == scenario), None) - with_datadog_operator = getattr(scenario_obj, "with_datadog_operator", False) - with_cluster_agent = getattr(scenario_obj, "with_cluster_agent", False) + scenario_obj = next(item for item in all_system_tests_scenarios if item.name == scenario) + with_datadog_operator = bool(getattr(scenario_obj, "with_datadog_operator", False)) + with_cluster_agent = bool(getattr(scenario_obj, "with_cluster_agent", False)) - # Initialize component parser and result parser = K8sComponentsParser() - components = {} + components: dict[str, str | list[str]] = {} - # Datadog Operator scenario: requires operator helm chart if with_datadog_operator: components["K8S_HELM_CHART_OPERATOR"] = ( parser.get_all_component_versions("helm_chart_operator") @@ -207,21 +177,18 @@ def get_k8s_scenario_components( else parser.get_default_component_version("helm_chart_operator") ) - # Cluster Agent scenarios: require cluster agent and injector if with_cluster_agent: components["K8S_CLUSTER_IMG"] = ( parser.get_all_component_versions("cluster_agent") if is_system_tests_scheduled else parser.get_default_component_version("cluster_agent") ) - components["K8S_INJECTOR_IMG"] = k8s_injector_img or ( parser.get_all_component_versions("injector") if is_system_tests else parser.get_default_component_version("injector") ) - # Non-operator cluster scenarios: require standard helm chart if with_cluster_agent and not with_datadog_operator: components["K8S_HELM_CHART"] = ( parser.get_all_component_versions("helm_chart") @@ -229,44 +196,37 @@ def get_k8s_scenario_components( else parser.get_default_component_version("helm_chart") ) - # Library init: required for all scenarios components["K8S_LIB_INIT_IMG"] = k8s_lib_init_img or ( parser.get_all_component_versions("lib_init", language) if is_system_tests else parser.get_default_component_version("lib_init", language) ) - return components + return {key: _as_job_name(value) for key, value in components.items()} def print_k8s_gitlab_pipeline( - language: str, k8s_matrix: dict[str, dict], ci_environment: str, result_pipeline: dict + language: str, k8s_matrix: dict[str, list[str]], ci_environment: str, result_pipeline: dict ) -> None: result_pipeline["stages"].append("K8S_LIB_INJECTION") - all_system_tests_scenarios = get_all_scenarios() - # Create the jobs by scenario. + scenarios = get_all_scenarios() + # Same image for every weblog when the caller sets it. Kept off the matrix so the + # registry host is not part of the job name. Otherwise the job builds it from K8S_WEBLOG. + weblog_image = os.getenv("K8S_WEBLOG_IMG") for scenario, weblogs in k8s_matrix.items(): - job = scenario - result_pipeline[job] = {} - result_pipeline[job]["extends"] = ".k8s_lib_injection_base" - # Job variables - result_pipeline[job]["variables"] = {} - result_pipeline[job]["variables"]["TEST_LIBRARY"] = language - result_pipeline[job]["variables"]["K8S_SCENARIO"] = scenario - result_pipeline[job]["variables"]["REPORT_ENVIRONMENT"] = ci_environment - - result_pipeline[job]["parallel"] = {"matrix": []} - for weblog_name in weblogs: - k8s_weblog_img = os.getenv("K8S_WEBLOG_IMG", "${PRIVATE_DOCKER_REGISTRY}" + f"/system-tests/{weblog_name}") - - # Build matrix entry - only include components that have values - matrix_entry = { - "K8S_WEBLOG": weblog_name, - "K8S_WEBLOG_IMG": k8s_weblog_img, - } - matrix_entry.update(get_k8s_scenario_components(scenario, all_system_tests_scenarios, language)) - - result_pipeline[job]["parallel"]["matrix"].append(matrix_entry) + variables = { + "TEST_LIBRARY": language, + "K8S_SCENARIO": scenario, + "REPORT_ENVIRONMENT": ci_environment, + } + if weblog_image: + variables["K8S_WEBLOG_IMG"] = weblog_image + components = get_k8s_scenario_components(scenario, scenarios, language) + result_pipeline[scenario] = { + "extends": ".k8s_lib_injection_base", + "variables": variables, + "parallel": {"matrix": [{"K8S_WEBLOG": weblog, **components} for weblog in weblogs]}, + } def print_docker_ssi_gitlab_pipeline( From 4dc7d6bd2bd65c9d773d3bcffa63c541803b8668 Mon Sep 17 00:00:00 2001 From: Roberto Montero Date: Tue, 27 Oct 2026 11:34:37 +0100 Subject: [PATCH 2/2] Restore the K8s matrix version-selection notes Keep the explanation of which component versions each pipeline tests. Co-authored-by: Cursor --- .../ci_orchestrators/gitlab_exporter.py | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/utils/scripts/ci_orchestrators/gitlab_exporter.py b/utils/scripts/ci_orchestrators/gitlab_exporter.py index daf370a8248..df2c75ad72f 100644 --- a/utils/scripts/ci_orchestrators/gitlab_exporter.py +++ b/utils/scripts/ci_orchestrators/gitlab_exporter.py @@ -148,7 +148,51 @@ def _as_job_name(value: str | list[str]) -> str | list[str]: def get_k8s_scenario_components( scenario: str, all_system_tests_scenarios: list[Scenario], language: str ) -> dict[str, str | list[str]]: - """Component versions for one scenario. Image values are registry paths, not full URLs.""" + """Determine K8s component versions for a given scenario and CI context. + + This method selects the appropriate component versions based on: + - The scenario requirements (operator, cluster agent) + - The CI pipeline context (system-tests (scheduled or pr/commit) or tracer repo or injector repo) + - Custom image overrides from environment variables + + Version Selection Logic: + ======================== + + For each component, versions are selected based on pipeline context: + + 1. **Custom Image (Highest Priority)** + - If K8S_LIB_INIT_IMG or K8S_INJECTOR_IMG env vars are set + - Uses the custom image directly (injector/tracer repo testing) + - The rest of the components are the pinned/default versions + + 2. **System-Tests Scheduled Pipeline** + - Tests ALL available versions (prod, dev, pinned) + + 3. **System-Tests PR/Commit** + - Tests ALL available versions (for injector and lib init) + - Tests only the pinned versions for the rest of the components + + Component Versions Returned: + ============================ + - K8S_HELM_CHART_OPERATOR: Datadog operator helm chart version (if with_datadog_operator) + - K8S_CLUSTER_IMG: Cluster agent image (if with_cluster_agent) + - K8S_INJECTOR_IMG: APM injector image (if with_cluster_agent) + - K8S_HELM_CHART: Datadog helm chart version (if cluster agent without operator) + - K8S_LIB_INIT_IMG: Library init container image (always included) + + Catalog images on PRIVATE_DOCKER_REGISTRY under ssi/ or ssi-dev/ are returned + without the registry host (for example ``ssi/cluster-agent:7.84.3``). The GitLab + job adds the host back before ./run.sh. Custom images on another host are unchanged. + + Args: + scenario: Scenario name (must exist in utils/_context/_scenarios/__init__.py) + all_system_tests_scenarios: List of all available scenarios + language: Programming language (java, python, nodejs, etc.) + + Returns: + Dictionary with component version assignments for the GitLab CI matrix + + """ all_valid_scenarios = {item.name for item in all_system_tests_scenarios} if scenario not in all_valid_scenarios: raise ValueError(