Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .gitlab/ssi_gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion docs/understand/scenarios/k8s_lib_injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
64 changes: 64 additions & 0 deletions tests/test_the_test/test_k8s_ci_matrix_labels.py
Original file line number Diff line number Diff line change
@@ -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
94 changes: 49 additions & 45 deletions utils/scripts/ci_orchestrators/gitlab_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ def print_ssi_gitlab_pipeline(language: str, matrix_data: dict[str, dict], ci_en
print("Pipeline file generated: ", output_file)


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


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)


def get_k8s_scenario_components(
scenario: str, all_system_tests_scenarios: list[Scenario], language: str
) -> dict[str, str | list[str]]:
Expand Down Expand Up @@ -153,7 +172,6 @@ def get_k8s_scenario_components(
- 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)
Expand All @@ -162,111 +180,97 @@ def get_k8s_scenario_components(
- 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 GitLab CI matrix
Dictionary with component version assignments for the GitLab CI matrix

"""
# Load all valid scenarios from the scenarios module
all_valid_scenarios = {s.name for s in all_system_tests_scenarios}

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")
if is_system_tests_scheduled
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")
if is_system_tests_scheduled
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(
Expand Down
Loading