Files
Thomas Vincent b430f5b1ec fix(tests): make hooks, CI helpers and the i18n audit fail closed (#8457)
* fix(hooks): check staged blobs and fail closed in the changelog tools

The pre-commit hooks read working-tree files while claiming to check staged content, check-changelog.py passed when its base diff failed, and fold-changelog.py deleted fragments it had not validated.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(tests): make CI helper scripts fail when they check nothing

check_all_pages.sh passed without logging in or crawling, the sink inventory accepted ripgrep errors, install_packages.sh and copyright_year.sh lost their failures, and the LDAP spec asserted before navigation finished.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(i18n): audit only changed .po entries and keep contributor badges separate

audit_po.py failed every translation PR on pre-existing errors and matched non-printf text as conversions; update-contributors.py leaked badges between aliased contributors.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs(changelog): add fragment for #8457

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(e2e): move the pointer off the Deepness multiselect button

Deepness sets a zero body margin, so the unhovered #ms-button sits at the viewport origin and headless Chromium on Linux intermittently applies :hover to it.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* ci: pull pinned integration database from authenticated mirror

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-23 08:17:12 -04:00

557 lines
20 KiB
YAML

# +-------------------------------------------------------------------------+
# | Copyright (C) 2004-2026 The Cacti Group |
# | |
# | This program is free software; you can redistribute it and/or |
# | modify it under the terms of the GNU General Public License |
# | as published by the Free Software Foundation; either version 2 |
# | of the License, or (at your option) any later version. |
# | |
# | This program is distributed in the hope that it will be useful, |
# | but WITHOUT ANY WARRANTY; without even the implied warranty of |
# | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
# | GNU General Public License for more details. |
# +-------------------------------------------------------------------------+
# | Cacti: The Complete RRDtool-based Graphing Solution |
# +-------------------------------------------------------------------------+
# | This code is designed, written, and maintained by the Cacti Group. See |
# | about.php and/or the AUTHORS file for specific developer information. |
# +-------------------------------------------------------------------------+
# | http://www.cacti.net/ |
# +-------------------------------------------------------------------------+
name: Cacti Commit Audit
on:
push:
branches: [develop, '1.2.x']
pull_request:
branches: [develop, '1.2.x']
merge_group:
permissions:
contents: read
concurrency:
group: build-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
"COMPOSER_ALLOW_SUPERUSER": 1
jobs:
javascript-contracts:
name: JavaScript contracts
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- name: Install Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
with:
node-version: '22'
- name: Install managed JavaScript and test contracts
run: |
set -euo pipefail
npm ci --ignore-scripts
npm run postinstall --ignore-scripts
npm test
npm run test:coverage
# spelling:
# name: Spell Check
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v7.0.0
# - name: Install codespell
# run: pip install codespell==2.4.1
# - name: Run codespell
# run: codespell --config .codespell.cfg
runtime-compatibility:
name: PHP ${{ matrix.php }} locked runtime
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
php: ['8.3', '8.4', '8.5']
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- name: Setup locked PHP dependencies
uses: ./.github/actions/setup-php-composer
with:
php-version: ${{ matrix.php }}
extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring
coverage: none
dependency-mode: locked-runtime
install-args: --no-dev --prefer-dist --no-progress --no-interaction
- name: Verify actual platform requirements
run: composer check-platform-reqs --no-dev
- name: Smoke-test the production autoloader
run: php -r "require 'include/vendor/autoload.php';"
native-dependencies:
name: PHP ${{ matrix.php }} native dependencies
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
php: ['8.3', '8.4', '8.5']
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- name: Setup Node
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
with:
node-version: '22'
cache: npm
- name: Build managed JavaScript
run: |
set -euo pipefail
npm ci --ignore-scripts
npm run postinstall --ignore-scripts
- name: Setup native PHP dependencies
uses: ./.github/actions/setup-php-composer
with:
php-version: ${{ matrix.php }}
extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring
coverage: none
dependency-mode: native
cache-dependency-path: composer.json
- name: Create native compatibility definition
run: |
set -euo pipefail
cp composer.json composer.compat.json
COMPOSER=composer.compat.json composer config platform.php "${{ matrix.php }}"
- name: Resolve native dependencies
env:
COMPOSER: composer.compat.json
run: |
set -euo pipefail
composer update --no-install --no-scripts --no-plugins --prefer-dist --no-progress --no-interaction
composer install --no-scripts --prefer-dist --no-progress --no-interaction
- name: Run unit tests
run: include/vendor/bin/pest --configuration=tests/phpunit.xml --display-warnings --testsuite=Unit
check:
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
contents: read
packages: read
services:
mysql:
image: ghcr.io/cacti/ci-images/mariadb@sha256:2fdc84794931c0abde9ba6a8c9cb5a4635a02d851a103f0301a8b606dfc5cd98
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
env:
MYSQL_DATABASE: cacti
MYSQL_HOST: 127.0.0.1
MYSQL_USER: cactiuser
MYSQL_PASSWORD: cactiuser
MYSQL_ROOT_PASSWORD: cactiroot
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
name: PHP 8.3 locked integration
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
with:
node-version: '22'
cache: npm
- name: Build managed JavaScript
run: |
set -euo pipefail
npm ci --ignore-scripts
npm run postinstall --ignore-scripts
- name: Setup locked PHP 8.3 dependencies
uses: ./.github/actions/setup-php-composer
with:
php-version: '8.3'
extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring
ini-values: post_max_size=256M, max_execution_time=60, date.timezone=America/New_York
coverage: none
dependency-mode: locked-quality
install-args: --prefer-dist --no-progress --no-interaction
- name: Check PHP version
run: php -v
- name: Prime fontconfig's cache
run: sudo fc-cache
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Verify actual platform requirements
run: composer check-platform-reqs
- name: Run Linter on base code
run: composer run-script lint
working-directory: .
- name: Run hardening pattern guard
if: github.event_name == 'pull_request'
run: composer run-script hardening-patterns -- --base="${{ github.event.pull_request.base.sha }}" --head=HEAD
working-directory: .
- name: Reject committed Composer dependencies
run: composer run-script vendor-policy
working-directory: .
- name: Checking coding standards on base code
run: composer run-script phpcsfixer
working-directory: .
- name: Run PHPStan at Level 6 on base code
run: composer run-script phpstan
working-directory: .
- name: Run Pest Unit Tests
run: composer run-script test
working-directory: .
- name: Checkout Thold Plugin
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: Cacti/plugin_thold
ref: 34c4a69b752b8eb0d73e90f1cd1c2a9b317a9d1b
path: plugins/thold
persist-credentials: false
- name: Checkout Monitor Plugin
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: Cacti/plugin_monitor
ref: 11c7b6c6422d7ffaba9c2dcac25b729d9cb6ff12
path: plugins/monitor
persist-credentials: false
- name: Checkout MacTrack Plugin
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: Cacti/plugin_mactrack
ref: 408adb447cbb7b4ef4360bade646b8d926f7b293
path: plugins/mactrack
persist-credentials: false
- name: Checkout Intropage Plugin
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: Cacti/plugin_intropage
ref: f3a00ff7fb4a7f0c26b8910cd32c5c98e45da1e9
path: plugins/intropage
persist-credentials: false
- name: Checkout FlowView Plugin
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: Cacti/plugin_flowview
ref: ee37c8938c99bc49f6a2933f373865c65f51fba0
path: plugins/flowview
persist-credentials: false
- name: Checkout Audit Plugin
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: Cacti/plugin_audit
ref: 1d7be4f422a3831dcaa150747ad7314168b60605
path: plugins/audit
persist-credentials: false
- name: Checkout Syslog Plugin
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: Cacti/plugin_syslog
ref: 82df21ba2acf38ec4052aa862d2b53c67287f311
path: plugins/syslog
persist-credentials: false
- name: Enable Syslog batch installation
run: |
set -euo pipefail
# With syslog we have to enable batch installs using the lines below
cd ${{ github.workspace }}/plugins/syslog
cp -p config.php.dist config.php
# shellcheck disable=SC2016
sed -i 's/\/\/\$syslog_install_options/\$syslog_install_options/g' config.php
- name: Move plugin config files
run: |
set -euo pipefail
cp -p ${{ github.workspace }}/plugins/flowview/config.php.dist ${{ github.workspace }}/plugins/flowview/config.php
cp -p ${{ github.workspace }}/plugins/syslog/config.php.dist ${{ github.workspace }}/plugins/syslog/config.php
sed -i 's/\/\/\$/\$/g' ${{ github.workspace }}/plugins/syslog/config.php
- name: Run apt-get update
run: sudo apt-get -y update
- name: Install Apache and Tools
run: sudo apt-get install apache2 snmp snmpd rrdtool fping libapache2-mod-php
- name: Start SNMPD Agent and Test Agent
run: |
set -euo pipefail
sudo systemctl start snmpd
sudo snmpwalk -c public -v2c -On localhost .1.3.6.1.2.1.1
- name: Link Web Root and Grant the Web Group Access
run: |
set -euo pipefail
sudo ln -s /home/runner/work/cacti/cacti /var/www/html
sudo chgrp -R www-data "${{ github.workspace }}"
sudo find "${{ github.workspace }}" -type d -exec chmod g+rwx {} +
sudo find "${{ github.workspace }}" -type f -exec chmod g+rw {} +
sudo chmod o+x /home
sudo chmod o+x /home/runner
sudo chmod o+x /home/runner/work
sudo chmod o+x /home/runner/work/cacti
sudo chmod o+x /home/runner/work/cacti/cacti
- name: Configure Apache Site
run: |
set -euo pipefail
cp ${{ github.workspace }}/tests/tools/cacti.conf /tmp/cacti.conf
sed -i "s#GITHUB_WORKSPACE#${{ github.workspace }}#g" /tmp/cacti.conf
sudo cp /tmp/cacti.conf /etc/apache2/sites-available/000-default.conf
- name: Restart Apache2
run: sudo systemctl restart apache2
- name: Create and Initialize Database
run: |
set -euo pipefail
mysql_config="$RUNNER_TEMP/mysql-client.cnf"
install -m 0600 /dev/null "$mysql_config"
printf '%s\n' \
'[client]' \
'user = root' \
'password = cactiroot' \
'host = 127.0.0.1' \
'max_allowed_packet = 500000000' > "$mysql_config"
mysql_args=(--defaults-extra-file="$mysql_config")
mysql "${mysql_args[@]}" -e "CREATE DATABASE IF NOT EXISTS cacti;"
mysql "${mysql_args[@]}" -e "CREATE USER 'cactiuser'@'localhost' IDENTIFIED BY 'cactiuser';"
mysql "${mysql_args[@]}" -e "GRANT ALL PRIVILEGES ON cacti.* TO 'cactiuser'@'localhost';"
mysql "${mysql_args[@]}" -e "FLUSH PRIVILEGES;"
mysql "${mysql_args[@]}" -e "SET GLOBAL max_allowed_packet=500000000;"
mysql "${mysql_args[@]}" cacti < "${{ github.workspace }}/cacti.sql"
mysql "${mysql_args[@]}" -e "INSERT INTO settings (name, value) VALUES ('path_php_binary', '/bin/php')" cacti
- name: Prime Cacti config.php File
run: |
set -euo pipefail
sudo install -o runner -g www-data -m 0660 \
${{ github.workspace }}/include/config.php.dist \
${{ github.workspace }}/include/config.php
sudo sed -i "s/localhost/127.0.0.1/g" ${{ github.workspace }}/include/config.php
- name: Setup Cacti Daemon
run: |
set -euo pipefail
sudo cp ${{ github.workspace }}/tests/tools/cactid.service /etc/systemd/system/cactid.service
sudo sed -i 's/apache/www-data/g' /etc/systemd/system/cactid.service
sudo systemctl enable cactid
sudo systemctl start cactid
for _ in {1..15}; do
if systemctl is-active --quiet cactid; then
break
fi
sleep 1
done
if ! systemctl is-active --quiet cactid; then
sudo systemctl status --no-pager cactid || true
sudo journalctl --no-pager -xeu cactid.service || true
echo "Cacti daemon failed to become active"
exit 1
fi
- name: Check install code for validity
run: php -q tests/tools/check_install_code.php
- name: Check All Resource XML File Syntax
run: php tests/tools/check_xml_syntax.php
- name: Check Poller Script Syntax after Package Import
run: find scripts -name '*.php' -print0 | xargs -0 -n1 php -l
- name: Check that all CLI scripts have a valid --version flag
run: sudo -u www-data -- bash tests/tools/check_cli_version.sh
- name: Install Cacti through the CLI
run: sudo -u www-data -- php cli/install_cacti.php --accept-eula --install --force
- name: Verify Installer Postconditions
run: |
set -euo pipefail
cd ${{ github.workspace }}
mysql_config="$RUNNER_TEMP/mysql-client.cnf"
mysql_args=(--defaults-extra-file="$mysql_config")
expected_tables=$(grep -c '^CREATE TABLE' cacti.sql)
actual_tables=$(mysql "${mysql_args[@]}" cacti --batch --skip-column-names -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()")
installed_version=$(mysql "${mysql_args[@]}" cacti --batch --skip-column-names -e "SELECT cacti FROM version")
host_templates=$(mysql "${mysql_args[@]}" cacti --batch --skip-column-names -e "SELECT COUNT(*) FROM host_template")
test "$actual_tables" -eq "$expected_tables"
test "$installed_version" != "new_install"
test "$host_templates" -gt 0
- name: Install Cacti Packages through CLI
run: sudo -u www-data -- bash tests/tools/install_packages.sh
- name: Check All Resource XML File Syntax Post Package Install
run: php tests/tools/check_xml_syntax.php
- name: Check all perl scripts for syntax errors
run: find scripts -name '*.pl' -print0 | xargs -0 -n1 perl -c
- name: Audit the Installation Schema
run: sudo -u www-data -- php cli/audit_database.php --report
- name: Install and Enable Plugins
run: sudo -u www-data -- php cli/plugin_manage.php --plugin=thold --plugin=intropage --plugin=monitor --plugin=mactrack --plugin=audit --plugin=flowview --plugin=syslog --install --enable --allperms
- name: Add Device for Moc Test, Check CLI scripts for Errors
run: |
set -euo pipefail
sudo -u www-data -- php cli/add_device.php --description=test_device --ip=127.0.0.1 --template=25
sudo -u www-data -- php cli/rebuild_poller_cache.php --debug
sudo -u www-data -- php cli/poller_reindex_hosts.php --id=all --debug
- name: Run Cacti poller
run: |
set -euo pipefail
sudo -u www-data -- php poller.php --poller=1 --force --debug
grep -q "SYSTEM STATS" log/cacti.log
# The recursive crawl walks the whole authenticated UI one request at a
# time and is the single most expensive step in CI. Nothing it exercises can
# change unless server-rendered code, assets or the schema changed, so skip
# it when a pull request touches only tests, workflows or documentation.
- name: Decide whether the page crawl can affect this change
id: crawl_scope
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" != 'pull_request' ]]; then
echo 'required=true' >> "$GITHUB_OUTPUT"
exit 0
fi
base='${{ github.event.pull_request.base.sha }}'
# Fail open: if the diff cannot be read, run the crawl.
if ! changed=$(git diff --name-only "$base" HEAD 2>/dev/null); then
echo 'required=true' >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ -z "$changed" ]]; then
echo 'required=true' >> "$GITHUB_OUTPUT"
exit 0
fi
if printf '%s\n' "$changed" | grep -qvE '^(tests/|\.github/|docs/|locales/|CHANGELOG|changelog\.d/|.*\.md$)'; then
echo 'required=true' >> "$GITHUB_OUTPUT"
else
echo 'required=false' >> "$GITHUB_OUTPUT"
echo "Only tests, workflows or documentation changed; skipping the page crawl."
fi
- name: Check All Pages Using Recursive wget
if: steps.crawl_scope.outputs.required == 'true'
run: |
set -euo pipefail
install -m 0600 /dev/null .my.cnf
printf '%s\n' \
'[client]' \
'user = root' \
'password = cactiroot' \
'host = 127.0.0.1' > .my.cnf
sudo tests/tools/check_all_pages.sh -vmstat 10 -ps 10
- name: View Cacti log on failure
if: failure()
run: |
set -euo pipefail
log_file=${{ github.workspace }}/log/cacti.log
if [[ -f "$log_file" ]]; then
cat "$log_file"
else
echo "Cacti log was not created"
fi
changelog:
name: CHANGELOG format
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Validate CHANGELOG ordering and entry format
run: python3 .github/scripts/check-changelog.py
required:
name: CI / required
if: always()
needs: [runtime-compatibility, native-dependencies, check, changelog]
runs-on: ubuntu-slim
timeout-minutes: 5
steps:
- name: Verify required jobs
env:
RUNTIME_RESULT: ${{ needs.runtime-compatibility.result }}
NATIVE_RESULT: ${{ needs.native-dependencies.result }}
INTEGRATION_RESULT: ${{ needs.check.result }}
CHANGELOG_RESULT: ${{ needs.changelog.result }}
run: |
set -euo pipefail
for result in \
"$RUNTIME_RESULT" \
"$NATIVE_RESULT" \
"$INTEGRATION_RESULT" \
"$CHANGELOG_RESULT"; do
test "$result" = success
done