Skip to content
Open
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
61 changes: 51 additions & 10 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
image: ${REGISTRY_DOCKER_URL}/stackstate/stackstate-agent-integrations-runner:20241120-py311
image: ${REGISTRY_DOCKER_URL}/stackstate/stackstate-agent-integrations-runner:${RUNNER_IMAGE_TAG}
stages:
- build
- test
- publish

variables:
CONDA_ENV: "stackstate-agent-integrations-py-3"
PYTHON_VERSION: 3.13.13
PYTHON_VERSION: 3.13.14
RUNNER_IMAGE_TAG: "20260625-py313"

.rules:
- &pull_requests
Expand All @@ -24,7 +25,7 @@ variables:
- $CHECK/*
- $CHECK/**/*
- &master_branch
if: '$CI_COMMIT_BRANCH == "stackstate-7.71.2"'
if: '$CI_COMMIT_BRANCH == "stackstate-7.78.2"'
- &release_branch
if: $CI_COMMIT_TAG
- &base_manual_changes
Expand All @@ -48,7 +49,7 @@ variables:
- splunk_base/*
- splunk_base/**/*
- &master_manual_branch
if: '$CI_COMMIT_BRANCH == "stackstate-7.71.2"'
if: '$CI_COMMIT_BRANCH == "stackstate-7.78.2"'
when: manual
allow_failure: true
- &release_manual_branch
Expand Down Expand Up @@ -120,18 +121,41 @@ print_env:
interruptible: true

docker:
<<: *manual_job_rules
image: ${REGISTRY_DOCKER_URL}/library/docker:20-git
stage: build
rules:
- if: $CI_COMMIT_MESSAGE =~ /Build new docker image/i
when: on_success
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- .setup-scripts/image/**/*
- .setup-scripts/setup_env.sh
when: manual
allow_failure: true
- if: $CI_EXTERNAL_PULL_REQUEST_IID
when: manual
allow_failure: true
- if: '$CI_COMMIT_BRANCH == "stackstate-7.78.2"'
when: manual
allow_failure: true
- if: $CI_COMMIT_TAG
when: manual
allow_failure: true
script:
- echo "${docker_password}" | docker login --username=${docker_user} --password-stdin docker.io
- echo "${quay_password}" | docker login --username=${quay_user} --password-stdin quay.io
- echo "${REGISTRY_PASSWORD}" | docker login --username=${REGISTRY_USER} --password-stdin ${REGISTRY_HOST}
- apk add make
- cd .setup-scripts/image
- make build
- make push
- if [ "${CI_COMMIT_REF_NAME}" = "stackstate-7.71.2" ]; then make tag_latest; make push_latest; fi
# Pull base from docker.io during build; mirror may not have 3.13.14-bookworm yet.
- make build push PYTHON_BASE_IMAGE=python:3.13.14-bookworm
- RUNNER_TAG=$(make -s print_tag)
- echo "Published ${REGISTRY_DOCKER_URL}/stackstate/stackstate-agent-integrations-runner:${RUNNER_TAG}"
- echo "Set RUNNER_IMAGE_TAG to ${RUNNER_TAG} (setup_env.sh/conda_env.ps1 already target python3.13/3.13.14), then re-run the pipeline."
- |
if [ "${CI_COMMIT_REF_NAME}" = "stackstate-7.78.2" ]; then
make tag_latest push_latest
fi
services:
- alias: docker
command:
Expand Down Expand Up @@ -386,12 +410,29 @@ test_static_health:
DOCKER_DRIVER: overlay2

publish-checks-dev:
<<: *linux_env
stage: publish
interruptible: true
needs:
- job: linux_deps
artifacts: true
script:
- .setup-scripts/setup_artifact_registry.sh
- .setup-scripts/setup_artifact_publishing.sh
- export VERSION=`./.setup-scripts/version.sh`
- echo "__version__ = \"$VERSION\"" > stackstate_checks_dev/stackstate_checks/dev/__about__.py
- cd stackstate_checks_dev && python setup.py sdist bdist_wheel upload -r gitlab
<<: *manual_job_rules
- pip install twine
- cd stackstate_checks_dev && python setup.py sdist bdist_wheel && twine upload --repository gitlab dist/*
rules:
- if: '$CI_COMMIT_BRANCH =~ /^STAC-/'
when: manual
allow_failure: true
- <<: *pull_requests
when: manual
allow_failure: true
- <<: *master_branch
when: manual
allow_failure: true
- <<: *release_branch
when: manual
allow_failure: true
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.13.13
3.13.14
25 changes: 18 additions & 7 deletions .setup-scripts/conda_env.ps1
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
$envName = $args[0]
$pythonVersion = $args[1]
if ($pythonVersion -eq '3') {
$pythonVersion = '3.11'
$pythonVersion = '3.13.14'
}
$env_name = conda env list | grep $envName | awk '{print $1}'
if (($env_name -ne $null) -and ($env_name -eq $envName)) {

$versionsFile = Join-Path $PSScriptRoot 'python_tool_versions.env'
if (Test-Path $versionsFile) {
Get-Content $versionsFile | ForEach-Object {
if ($_ -match '^\s*([A-Z_]+)\s*=\s*(.+?)\s*$') {
Set-Variable -Name $matches[1] -Value $matches[2] -Scope Script
}
}
}
if (-not $PIP_VERSION) { $PIP_VERSION = '24.3.1' }
if (-not $SETUPTOOLS_VERSION) { $SETUPTOOLS_VERSION = '75.8.2' }

$envExists = conda env list | Select-String -Pattern "^\s*$([regex]::Escape($envName))\s"
if ($envExists) {
Write-Output "Virtual Environment '$envName' already exists"
return
}
$DD_PIP_VERSION = '20.3.4'
$DD_SETUPTOOLS_VERSION = '44.1.1'

conda create -n $envName python python=$pythonVersion -y
conda activate $envName
pip install --user -i https://pypi.python.org/simple pip==$DD_PIP_VERSION
pip install --ignore-installed setuptools==$DD_SETUPTOOLS_VERSION
pip install --user -i https://pypi.python.org/simple "pip==$PIP_VERSION"
pip install --ignore-installed "setuptools==$SETUPTOOLS_VERSION"
17 changes: 12 additions & 5 deletions .setup-scripts/image/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
FROM registry.tooling.stackstate.io/docker/python:3.11.14-bullseye
# Match agent embedded CPython 3.13.14. Docker Hub publishes 3.13.14 on bookworm, not bullseye.
# CI `docker` job passes PYTHON_BASE_IMAGE=python:3.13.14-bookworm (docker.io is logged in there).
ARG PYTHON_BASE_IMAGE=registry.tooling.stackstate.io/docker/python:3.13.14-bookworm
FROM ${PYTHON_BASE_IMAGE}
ARG PIP_VERSION=24.3.1
ARG SETUPTOOLS_VERSION=75.8.2
RUN apt-get update && \
apt-get install -y virtualenv apt-transport-https ca-certificates curl gnupg2 software-properties-common && \
apt-get install -y apt-transport-https ca-certificates curl gnupg2 lsb-release && \
apt-get clean && \
pip3 install -U pip setuptools codecov wheel
pip3 install "pip==${PIP_VERSION}" "setuptools==${SETUPTOOLS_VERSION}" virtualenv codecov wheel

RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
RUN add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" && \
RUN install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
chmod a+r /etc/apt/keyrings/docker.asc && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \
apt-get update && \
apt-get install -y docker-ce docker-ce-cli containerd.io && \
apt-get clean
Expand Down
22 changes: 18 additions & 4 deletions .setup-scripts/image/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
RUNTIMETAG := $(shell date +%Y%m%d)
PYTHON_SUFFIX := py313
IMAGE_REPO ?= stackstate/stackstate-agent-integrations-runner
# CI `docker` job overrides with python:3.13.14-bookworm (docker.io login); local default is the mirror.
PYTHON_BASE_IMAGE ?= registry.tooling.stackstate.io/docker/python:3.13.14-bookworm
include ../python_tool_versions.env
export

#build:
# docker build -t stackstate/stackstate-agent-integrations-runner:$(RUNTIMETAG) .
Expand All @@ -16,12 +22,20 @@ RUNTIMETAG := $(shell date +%Y%m%d)
# vvv New runner

build:
docker build -t stackstate/stackstate-agent-integrations-runner:$(RUNTIMETAG)-py311 .
docker build --build-arg PYTHON_BASE_IMAGE=$(PYTHON_BASE_IMAGE) \
--build-arg PIP_VERSION=$(PIP_VERSION) \
--build-arg SETUPTOOLS_VERSION=$(SETUPTOOLS_VERSION) \
-t $(IMAGE_REPO):$(RUNTIMETAG)-$(PYTHON_SUFFIX) .

push:
docker push stackstate/stackstate-agent-integrations-runner:$(RUNTIMETAG)-py311
docker push $(IMAGE_REPO):$(RUNTIMETAG)-$(PYTHON_SUFFIX)

tag_latest:
docker tag stackstate/stackstate-agent-integrations-runner:$(RUNTIMETAG)-py311 stackstate/stackstate-agent-integrations-runner:latest-py311
docker tag $(IMAGE_REPO):$(RUNTIMETAG)-$(PYTHON_SUFFIX) $(IMAGE_REPO):latest-$(PYTHON_SUFFIX)

push_latest:
docker push stackstate/stackstate-agent-integrations-runner:latest-py311
docker push $(IMAGE_REPO):latest-$(PYTHON_SUFFIX)

# Print the tag CI should set as RUNNER_IMAGE_TAG after a successful publish.
print_tag:
@echo $(RUNTIMETAG)-$(PYTHON_SUFFIX)
3 changes: 3 additions & 0 deletions .setup-scripts/python_tool_versions.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Python 3.13-compatible tool pins shared by CI setup, runner image, and tox.
PIP_VERSION=24.3.1
SETUPTOOLS_VERSION=75.8.2
11 changes: 8 additions & 3 deletions .setup-scripts/setup_artifact_publishing.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ if [ -n "$missing" ]; then
fi

echo "→ Configuring .pypirc for publishing to GitLab Package Registry..."
echo "GitLab PyPI URL: $GITLAB_PACKAGE_REGISTRY_PYPI_URL"

PYPI_REPOSITORY_URL="${GITLAB_PACKAGE_REGISTRY_PYPI_URL}"
if [[ "${PYPI_REPOSITORY_URL}" != http://* && "${PYPI_REPOSITORY_URL}" != https://* ]]; then
PYPI_REPOSITORY_URL="https://${PYPI_REPOSITORY_URL}"
fi
echo "GitLab PyPI URL: ${PYPI_REPOSITORY_URL}"

# setup .pypirc
cat > ~/.pypirc <<EOF
Expand All @@ -22,9 +27,9 @@ index-servers =
gitlab

[gitlab]
repository = https://GITLAB_PACKAGE_REGISTRY_PYPI_URL
repository = ${PYPI_REPOSITORY_URL}
username = $GITLAB_PACKAGE_REGISTRY_USER
password = GITLAB_PACKAGE_REGISTRY_PASSWORD
password = $GITLAB_PACKAGE_REGISTRY_PASSWORD
EOF

echo "✔ GitLab PyPI registry has been configured for publishing."
10 changes: 6 additions & 4 deletions .setup-scripts/setup_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@ set -x
# This happens when using the ./run_gitlab_local.sh script

export INTEGRATIONS_DIR_TMP=${CI_PROJECT_DIR:-"."}
SETUP_SCRIPTS_DIR="${INTEGRATIONS_DIR_TMP}/.setup-scripts"
# shellcheck source=python_tool_versions.env
source "${SETUP_SCRIPTS_DIR}/python_tool_versions.env"

VENV_PATH=$INTEGRATIONS_DIR_TMP/venv

if [ ! -d $VENV_PATH ]; then
echo "$VENV_PATH doesn't exist, create the venv and loading deps"
virtualenv --python=python3.11 --pip=23.3.1 --setuptools=44.1.1 $INTEGRATIONS_DIR_TMP/venv
python3.13 -m venv $INTEGRATIONS_DIR_TMP/venv
source $INTEGRATIONS_DIR_TMP/venv/bin/activate
pip install "pip==${PIP_VERSION}" "setuptools==${SETUPTOOLS_VERSION}" wheel
pip install pylint==2.17.2
pip install docker==6.1.3
pip install --upgrade pip setuptools
pip install 'cython<3.0.0'
pip install "pyyaml==6.0.1" --no-build-isolation
pip install --upgrade wheel
source $INTEGRATIONS_DIR_TMP/.setup-scripts/load_deps.sh
else
echo "$VENV_PATH already exists, only activating the venv"
ls $INTEGRATIONS_DIR_TMP/venv/bin || echo 'no bin'
ls $INTEGRATIONS_DIR_TMP/venv/lib/python3.11/site-packages || echo 'no site-packages'
ls $INTEGRATIONS_DIR_TMP/venv/lib/python3.13/site-packages || echo 'no site-packages'
source $INTEGRATIONS_DIR_TMP/venv/bin/activate
pip install pylint==2.17.2
pip install docker==6.1.3
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,29 @@ You can optionally pass a log level parameter, if not passed logging is disabled

## CI image

The CI image is built from `.setup-scripts/image`.
The CI image is built from `.setup-scripts/image` and published to
`registry.tooling.stackstate.io` (referenced by `RUNNER_IMAGE_TAG` in
`.gitlab-ci.yml`). **Do not build or push it locally** except for ad-hoc
validation — use the GitLab CI `docker` job (build stage, manual).

Its Python **must match the CPython that the StackState Agent embeds** — the
agent's `omnibus/config/software/python3.rb` `default_version`, mirrored here in
`.python-version`. Bumping it is part of the **agent upstream-merge process**,
not ad-hoc work: see the agent repo's `UPSTREAM_MERGE.md`, section "Integrations
repo: CI runner image (embedded Python bump)".

After merging Dockerfile / Makefile changes:

1. Run the manual **`docker`** job on the pipeline (it builds from
`python:3.13.14-bookworm` and pushes `YYYYMMDD-py313` to the registry).
2. In a follow-up commit, set `RUNNER_IMAGE_TAG` in `.gitlab-ci.yml` to the tag
the job prints, and ensure `setup_env.sh` uses `python3.13` and
`conda_env.ps1` defaults to `3.13.14` (must land together with the runner switch).
3. Re-run the pipeline so test jobs use the new runner.

Keeping runner Python in sync ensures CI tests run on the same interpreter the
agent ships, so version-specific bugs (e.g. pydantic validation differences) are
caught before release rather than in production.

## Dynatrace feeder
We also expose a script to feed some fake data into Dynatrace. It reads logs and metrics from inside a provided directory and shoots them to the Dynatrace API to have a reproducible setup.
Expand Down
2 changes: 1 addition & 1 deletion agent_integration_sample/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ envlist =
flake8

[testenv]
pip_version = pip==23.3.1
pip_version = pip==24.3.1
usedevelop = true
platform = linux|darwin|win32
deps =
Expand Down
2 changes: 1 addition & 1 deletion agent_v2_integration_sample/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ envlist =
flake8

[testenv]
pip_version = pip==23.3.1
pip_version = pip==24.3.1
usedevelop = true
platform = linux|darwin|win32
deps =
Expand Down
2 changes: 1 addition & 1 deletion agent_v2_integration_stateful_sample/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ envlist =
flake8

[testenv]
pip_version = pip==23.3.1
pip_version = pip==24.3.1
usedevelop = true
platform = linux|darwin|win32
deps =
Expand Down
2 changes: 1 addition & 1 deletion agent_v2_integration_transactional_sample/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ envlist =
flake8

[testenv]
pip_version = pip==23.3.1
pip_version = pip==24.3.1
usedevelop = true
platform = linux|darwin|win32
deps =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,23 @@ class HostProperties(ForgivingBaseModel):
cloudType: Optional[str] = None
conditionalName: Optional[str] = None
cpuCores: Optional[int] = None
customHostMetadata: Dict[str, Any] = field(default_factory=dict)
customHostMetadata: Union[Dict[str, Any], List[Any], str] = field(default_factory=dict)

@field_validator('customHostMetadata', mode='before')
@classmethod
def coerce_custom_host_metadata(cls, v):
"""Parse customHostMetadata tolerantly: as a dict, else a list, else a string.

Dynatrace returns this field inconsistently across environments: as a
``{key: value}`` dict in some, and as a list of ``{'key':.., 'value':..}``
objects in others. Accept either shape as-is, falling back to a string
representation for any other (non-null) type, so a single type mismatch does
not cause the whole host entity to be dropped from topology (STAC-25137).
"""
if v is None or isinstance(v, (dict, list, str)):
return v
return str(v)

customizedName: Optional[str] = None
detectedName: Optional[str] = None
dnsNames: List[str] = field(default_factory=list)
Expand Down
Loading