diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000..898078e3 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,9 @@ +# Self-hosted runner labels available to this repository, so actionlint does not +# report them as unknown. These are ARC scale sets in the `public` runner group +# (StackVista/argocd-apps, cluster_definitions/tooling-main/apps/github-runner-*); +# the `-public` tier is the one a PUBLIC repository is allowed to schedule on. +self-hosted-runner: + labels: + - docker-public + - xlarge-public + - arm64-xlarge-public diff --git a/.github/scripts/select-checks.sh b/.github/scripts/select-checks.sh new file mode 100755 index 00000000..e0683e8f --- /dev/null +++ b/.github/scripts/select-checks.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# +# Selects which integration check suites the test matrix should run, reproducing +# the `changes:` rules that gated each `test_` job in .gitlab-ci.yml +# (GitLab -> GitHub migration, STAC-25463). +# +# GitLab evaluated a per-job `changes:` list; GitHub has no job-level path filter, +# so the equivalent is computed once here and fanned out as a matrix. This is done +# in plain git rather than a path-filter action: StackVista enforces a strict +# third-party action allowlist, and `git diff` against the merge base is exactly +# what the GitLab rule meant. +# +# Selection rules, ported from .gitlab-ci.yml: +# * A change to a shared library, the setup scripts, or this CI wiring runs +# EVERY suite (GitLab: the `base_changes` anchor). +# * Otherwise only the suites whose own directory changed run. +# * GitLab's `splunk_base_build_rule` -- a change to splunk_base also runs the +# other three splunk suites, which import its test helpers -- is not ported +# here because no splunk suite runs yet. It lands with them in phase 2. +# * push / workflow_dispatch run everything (GitLab: `master_branch`, +# `release_branch`). +# +# Writes `checks=` to $GITHUB_OUTPUT for `fromJson()` in a matrix. + +set -euo pipefail + +# Suites currently running on GitHub Actions. Phase 1 is the 15 pure-Python +# suites: they need no Docker daemon, so they are the low-risk half of the port. +# +# Deliberately NOT here yet (phase 2, needs the DinD story exercised first): +# splunk_base, splunk_health, splunk_metric, splunk_topology +# -- each drives a real Splunk container via docker-compose. +# stackstate_checks_dev +# -- its tests exercise the toolkit's own Docker helpers. +# Both public ARC runners provide a DinD sidecar, so this is a matter of proving +# it rather than provisioning anything. +# +# Deliberately dropped, not pending: +# postgres -- .gitlab-ci.yml carried a `test_postgres` job for a check that does +# not exist in this repository. It is dead config, not a gap. +CHECKS=( + agent_integration_sample + agent_v2_integration_sample + agent_v2_integration_stateful_sample + agent_v2_integration_transactional_sample + dynatrace_base + dynatrace_health + dynatrace_topology + kubelet + openmetrics + servicenow + stackstate_checks_base + static_health + static_topology + vsphere + zabbix +) + +# A change anywhere here invalidates every suite: the base classes and the test +# helpers are imported by all of them, and the setup scripts build the venv the +# suites run in. +SHARED_PATHS=( + stackstate_checks_base/ + stackstate_checks_dev/ + stackstate_checks_tests_helper/ + .setup-scripts/ + .github/workflows/checks-tests.yml + .github/scripts/select-checks.sh +) + +emit() { + local -a selected=("$@") + local json + if [ "${#selected[@]}" -eq 0 ]; then + json="[]" + else + json=$(printf '%s\n' "${selected[@]}" | sort -u | jq -R . | jq -c -s .) + fi + echo "checks=${json}" >>"${GITHUB_OUTPUT}" + echo "Selected suites: ${json}" +} + +# Anything that is not a pull request is a full run. On the release branch the +# whole matrix is the point (it is what Cerberus reports on), and a manual +# dispatch is an explicit request for everything. +if [ "${EVENT_NAME}" != "pull_request" ]; then + echo "Event '${EVENT_NAME}' is not a pull request: running every suite." + emit "${CHECKS[@]}" + exit 0 +fi + +# Diffing against the merge base keeps a stale base branch from dragging +# unrelated commits into the change set. +MERGE_BASE=$(git merge-base "origin/${BASE_REF}" HEAD) +mapfile -t CHANGED < <(git diff --name-only "${MERGE_BASE}" HEAD) + +echo "Changed files (${#CHANGED[@]}) against ${BASE_REF} @ ${MERGE_BASE}:" +printf ' %s\n' "${CHANGED[@]}" + +matches_prefix() { + local file=$1 prefix + shift + for prefix in "$@"; do + case "${file}" in + "${prefix}"*) return 0 ;; + esac + done + return 1 +} + +for file in "${CHANGED[@]}"; do + if matches_prefix "${file}" "${SHARED_PATHS[@]}"; then + echo "'${file}' is shared CI or library code: running every suite." + emit "${CHECKS[@]}" + exit 0 + fi +done + +SELECTED=() +for file in "${CHANGED[@]}"; do + for check in "${CHECKS[@]}"; do + if [ "${file#"${check}"/}" != "${file}" ]; then + SELECTED+=("${check}") + fi + done +done + +emit "${SELECTED[@]+"${SELECTED[@]}"}" diff --git a/.github/workflows/checks-tests.yml b/.github/workflows/checks-tests.yml new file mode 100644 index 00000000..17ee86c6 --- /dev/null +++ b/.github/workflows/checks-tests.yml @@ -0,0 +1,249 @@ +name: Check tests + +# Ported from .gitlab-ci.yml as part of the GitLab -> GitHub migration +# (STAC-25142 / STAC-25463), phase 1: the pure-Python check suites. +# +# WHAT MOVED +# linux_deps + the `test_` job family -> the `check-tests` matrix below. +# The per-job `changes:` rules -> .github/scripts/select-checks.sh. +# The validate suite that rode along inside `test_stackstate_checks_base` +# -> its own `validate` job, so a metadata +# failure is legible as its own PR check +# instead of hiding inside a test job. +# +# WHAT IS DELIBERATELY NOT HERE +# splunk_{base,health,metric,topology} and stackstate_checks_dev +# The only five suites that need a Docker daemon (the four splunk suites +# drive a real Splunk container via docker-compose; checks_dev tests the +# toolkit's own Docker helpers). Both public ARC runners ship a DinD +# sidecar, so this is about exercising that path rather than provisioning +# anything -- phase 2. That phase also brings across +# .setup-scripts/setup_artifactory_docker.sh (the registry docker login) +# and COMPOSE_HTTP_TIMEOUT, which only those suites need. +# test_postgres +# Dead config: .gitlab-ci.yml tests a `postgres` check that does not exist +# in this repository. Dropped, not pending. +# print_env +# A bare `printenv`. This repository is PUBLIC, so that job publishes every +# CI credential in scope to a world-readable log. Not ported at any phase; +# `secrets: inherit` is likewise never used here. +# The Windows lane +# There is none to port. `.gitlab-ci.yml` defines a `.windows_env` anchor +# but no job has ever referenced it, and Windows is not a supported target, +# so the orphaned .setup-scripts/conda_env.ps1 + windows_*.cmd helpers can +# be retired with the GitLab pipeline (STAC-25464). +# publish-checks-dev / the runner-image `docker` job +# Publishing needs write credentials this repo does not hold; pulumi-infra +# schedules integrations' publishing role for its section 7.4. Until then +# releases keep running from GitLab, so the GitLab pipeline stays live. +# A Cerberus failure notification +# Unlike stackstate-agent, this pipeline has never had one -- there is no +# notify job in .gitlab-ci.yml and no .cerberus directory -- so adding it +# would be new capability, not a port. It also needs CERBERUS_LAMBDA_URL, +# which is a private-visibility org secret and so unreadable from this +# PUBLIC repo without a pulumi-infra grant. Tracked as a follow-up. +# +# CREDENTIALS +# Pulling the runner image needs vars.REGISTRY_HOST / vars.REGISTRY_USER and +# secrets.REGISTRY_PASSWORD, which are org-level visibility=all (STAC-25350) +# and therefore already reach this PUBLIC repo. Note it composes +# vars.REGISTRY_HOST + /docker rather than using vars.REGISTRY_DOCKER_URL: that +# variable is private-visibility and so is NOT readable here. Org secrets are +# never exposed to fork PRs, hence the same-repo guard on every job that pulls +# the image. +# +# Resolving check requirements additionally needs the private PyPI index, for +# the handful of pins that are not on public PyPI. That is +# vars.GITLAB_PACKAGE_REGISTRY_PYPI_SIMPLE_URL and +# secrets.GITLAB_PACKAGE_REGISTRY_USER, granted to this repo in pulumi-infra +# (StackVista/pulumi-infra#263), alongside the already-org-wide +# secrets.GITLAB_PACKAGE_REGISTRY_READONLY_PASSWORD. Pull-only: this repo's +# *publishing* role is still deferred, per the note above. + +on: + pull_request: + # Mirrors GitLab's `master_branch` rule, which hardcoded the release branch the + # same way: the full matrix runs there regardless of what a given commit + # touched, so the branch always has a complete verdict. + push: + branches: + - stackstate-7.78.2 + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +env: + # Tag of the prebuilt test image. Kept in lockstep with RUNNER_IMAGE_TAG in + # .gitlab-ci.yml until the GitLab pipeline is retired; the image is still built + # and published by that pipeline's `docker` job. + RUNNER_IMAGE_TAG: 20260625-py313 + +jobs: + select-checks: + name: Select check suites to run + # Not strictly credential-bound, but there is nothing to select for a fork PR + # whose downstream jobs cannot pull the image anyway. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: docker-public + timeout-minutes: 10 + outputs: + checks: ${{ steps.select.outputs.checks }} + runner_image: ${{ steps.image.outputs.ref }} + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + # Full history: the selector diffs against the merge base with the base + # branch, which a shallow clone cannot resolve. + fetch-depth: 0 + persist-credentials: false + + - name: Resolve the runner image reference + id: image + env: + REGISTRY_HOST: ${{ vars.REGISTRY_HOST }} + run: | + set -euo pipefail + # Same image the GitLab pipeline runs on, via the same read-only proxy: + # GitLab's ${REGISTRY_DOCKER_URL} is registry.tooling.stackstate.io/docker. + echo "ref=${REGISTRY_HOST}/docker/stackstate/stackstate-agent-integrations-runner:${RUNNER_IMAGE_TAG}" >>"${GITHUB_OUTPUT}" + + - name: Select check suites + id: select + env: + EVENT_NAME: ${{ github.event_name }} + BASE_REF: ${{ github.base_ref }} + run: | + set -euo pipefail + # The ARC work volume is owned by the runner uid; mark it safe so the + # selector's git calls are not rejected as "dubious ownership". + git config --global --add safe.directory '*' + bash .github/scripts/select-checks.sh + + validate: + name: Check metadata validation (checksdev validate) + # Ported from the `checksdev validate *` commands that opened + # test_stackstate_checks_base. Cheap, repo-wide, and independent of the + # matrix, so it runs on every change rather than per suite. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + needs: select-checks + runs-on: docker-public + timeout-minutes: 30 + container: + # Tag-pinned rather than digest-pinned: the tag is produced by the GitLab + # `docker` job and updated in lockstep with .gitlab-ci.yml, so pinning a + # digest here would silently drift from the pipeline that builds it. + image: ${{ needs.select-checks.outputs.runner_image }} # zizmor: ignore[unpinned-images] + credentials: + username: ${{ vars.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Build the toolchain virtualenv + run: | + set -eo pipefail + git config --global --add safe.directory '*' + # Creates venv/ and installs checksdev; the GitLab `linux_deps` job did + # this once and shipped venv/ as an artifact. Here each job builds its + # own: the matrix legs run in parallel, so repeating it costs runner + # time but no wall-clock, and it avoids relocating a venv through the + # artifact store. Worth revisiting with real timings, the way the + # agent's cache image was justified (STAC-25429). + source .setup-scripts/setup_env.sh + + - name: checksdev validate + run: | + set -eo pipefail + source venv/bin/activate + checksdev validate config + checksdev validate dep + checksdev validate manifest --include-extras + checksdev validate metadata + checksdev validate service-checks + + check-tests: + name: Check tests (${{ matrix.check }}) + if: >- + ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) + && needs.select-checks.outputs.checks != '[]' }} + needs: select-checks + runs-on: docker-public + timeout-minutes: 45 + strategy: + # One suite's failure should not mask the state of the other fourteen. + fail-fast: false + matrix: + check: ${{ fromJson(needs.select-checks.outputs.checks) }} + container: + image: ${{ needs.select-checks.outputs.runner_image }} # zizmor: ignore[unpinned-images] + credentials: + username: ${{ vars.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Build the toolchain virtualenv + run: | + set -eo pipefail + git config --global --add safe.directory '*' + source .setup-scripts/setup_env.sh + + - name: Configure the private PyPI index (GitLab Package Registry) + env: + GITLAB_PACKAGE_REGISTRY_PYPI_SIMPLE_URL: ${{ vars.GITLAB_PACKAGE_REGISTRY_PYPI_SIMPLE_URL }} + GITLAB_PACKAGE_REGISTRY_USER: ${{ secrets.GITLAB_PACKAGE_REGISTRY_USER }} + GITLAB_PACKAGE_REGISTRY_READONLY_PASSWORD: ${{ secrets.GITLAB_PACKAGE_REGISTRY_READONLY_PASSWORD }} + # A few checks pin wheels that are not on public PyPI and are served from the + # central registry instead (currently only vsphere, which pins + # vsphere-automation-sdk). This mirrors what the GitLab `.linux_test` anchor did + # immediately before `checksdev test`. The script writes ~/.pip/pip.conf and a + # 0600 ~/.netrc, keeping credentials out of the index URL. + # + # Guarded rather than unconditional: only one suite actually needs the private + # index, so a missing credential should not fail the fourteen that resolve + # everything from public PyPI. When it is missing we say so loudly, and the + # affected suite still fails visibly on its own unresolvable pin. + run: | + set -eo pipefail + if [ -z "${GITLAB_PACKAGE_REGISTRY_PYPI_SIMPLE_URL}" ] \ + || [ -z "${GITLAB_PACKAGE_REGISTRY_USER}" ] \ + || [ -z "${GITLAB_PACKAGE_REGISTRY_READONLY_PASSWORD}" ]; then + echo "::warning title=Private PyPI index not configured::GitLab Package Registry credentials are not available to this job. Checks that pin wheels absent from public PyPI (currently 'vsphere') will fail to resolve their requirements." + exit 0 + fi + .setup-scripts/setup_artifact_registry.sh + + - name: checksdev test ${{ matrix.check }} + env: + CHECK: ${{ matrix.check }} + run: | + set -eo pipefail + source venv/bin/activate + checksdev test --cov "${CHECK}" + + - name: checksdev benchmarks ${{ matrix.check }} + env: + CHECK: ${{ matrix.check }} + # Non-blocking, matching GitLab's `|| true`: benchmarks are reported for + # information and have never gated a merge. + continue-on-error: true + run: | + set -eo pipefail + source venv/bin/activate + checksdev test "${CHECK}" --bench