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
9 changes: 9 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -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
210 changes: 210 additions & 0 deletions .github/scripts/select-checks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#!/usr/bin/env bash
#
# Selects which integration check suites the test matrix should run, reproducing
# the `changes:` rules that gated each `test_<check>` 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
# (STAC-25531).
# * push / workflow_dispatch run everything (GitLab: `master_branch`,
# `release_branch`).
#
# Writes three arrays to $GITHUB_OUTPUT for `fromJson()` in a matrix:
# checks -- suites that need no credentials
# private_checks -- suites that install from the private GitLab PyPI
# index, and are cleared to run on this event
# deferred_private_checks -- private-index suites withheld from this event
# (always empty outside pull requests)
#
# The split is a security boundary, not a convenience. The credential-free suites
# run with no secrets in scope at all. The private-index suites need a registry
# password, so they are kept in a separate job -- and, on pull requests, are not
# run at all (STAC-25540, second review pass).
#
# That last part is the whole point, so it is worth stating plainly: a
# `pull_request` run executes the pull request's own copy of the workflow and of
# every script it calls. Hardening the job cannot keep a determined pull request
# away from a secret the run is holding -- it can always edit the thing that holds
# it. The only run that cannot leak the credential is a run that never receives
# it, so these suites are deferred to push, tag and workflow_dispatch events,
# whose contents are reviewed before they reach the release branch.

set -euo pipefail

# Suites currently running on GitHub Actions. Phase 1 is the 15 suites that need
# no Docker daemon.
#
# Deliberately NOT here yet (phase 2, STAC-25531 -- needs a docker client in the
# job image):
# 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.
# ubuntu-latest already provides a working Docker daemon, so this is a matter of
# giving the job a docker client rather than provisioning a runner.
#
# 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
)

# Suites whose requirements resolve only against the private GitLab PyPI index.
# `vsphere` pins vsphere-automation-sdk, which VMware never published to public
# PyPI (the name is squatted there by an unrelated 0.0.1 placeholder), so it is
# mirrored into the StackVista package registry and needs authentication.
#
# Everything not listed here is credential-free and must stay that way: adding a
# suite to this list stops it running on pull requests altogether, and removing
# the need for the private index is always the better fix. For vsphere that fix
# looks reachable -- VMware now publishes the SDK to public PyPI under renamed
# packages (vmware-vapi-runtime, vmware-vapi-common-client, pyvmomi) and ships
# the NSX/VMC wheels from its own public index -- so this list should shrink to
# nothing once the pin is modernised.
PRIVATE_INDEX_CHECKS=(
vsphere
)

# 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
)

to_json() {
if [ "$#" -eq 0 ]; then
echo "[]"
else
printf '%s\n' "$@" | sort -u | jq -R . | jq -c -s .
fi
}

is_private_index() {
local candidate=$1 check
for check in "${PRIVATE_INDEX_CHECKS[@]}"; do
[ "${candidate}" = "${check}" ] && return 0
done
return 1
}

emit() {
local -a selected=("$@")
local -a public=() private=() deferred=()
local check
for check in ${selected[@]+"${selected[@]}"}; do
if is_private_index "${check}"; then
private+=("${check}")
else
public+=("${check}")
fi
done

# Pull requests do not run the private-index suites at all (STAC-25540, second
# review pass). See the security-boundary note at the top of this file: a
# `pull_request` run executes the pull request's own copy of the workflow and
# scripts, so the credential can only be protected by withholding it. These
# suites run on the release branch instead, where the code has been reviewed.
if [ "${EVENT_NAME}" = "pull_request" ] && [ "${#private[@]}" -gt 0 ]; then
deferred=("${private[@]}")
private=()
fi

local public_json private_json deferred_json
public_json=$(to_json ${public[@]+"${public[@]}"})
private_json=$(to_json ${private[@]+"${private[@]}"})
deferred_json=$(to_json ${deferred[@]+"${deferred[@]}"})

{
echo "checks=${public_json}"
echo "private_checks=${private_json}"
echo "deferred_private_checks=${deferred_json}"
} >>"${GITHUB_OUTPUT}"

echo "Selected credential-free suites: ${public_json}"
echo "Selected private-index suites: ${private_json}"
if [ "${deferred_json}" != "[]" ]; then
echo "Deferred private-index suites: ${deferred_json}"
echo "::notice title=Private-index suites do not run on pull requests::${deferred_json} resolve only against the private package registry. Pull requests are deliberately given no credential to reach it, so these suites run on ${BASE_REF:-the release branch} after merge."
fi
}

# Anything that is not a pull request is a full run. On the release branch the
# whole matrix is the point -- the branch should always carry a complete verdict,
# regardless of what a given commit touched -- 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[@]}"}"
Loading
Loading