diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..d5e4120 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,59 @@ +# +-------------------------------------------------------------------------+ +# | Copyright (C) 2004-2026 The Cacti Group | +# +-------------------------------------------------------------------------+ +# | Cacti: The Complete RRDtool-based Graphing Solution | +# +-------------------------------------------------------------------------+ +# | http://www.cacti.net/ | +# +-------------------------------------------------------------------------+ +# +# CodeQL has no PHP analysis, so this covers js/GPSMaps.js only. The PHP is +# covered by the syntax and integration jobs in plugin-ci-workflow.yml. + +name: "CodeQL" + +on: + push: + branches: [main, develop] + paths-ignore: + - "**/*.md" + pull_request: + branches: [main, develop] + paths-ignore: + - "**/*.md" + schedule: + - cron: "30 1 * * 1" + workflow_dispatch: + +concurrency: + group: codeql-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + language: ["javascript-typescript"] + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Initialize CodeQL + uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3 + with: + languages: ${{ matrix.language }} + - name: Autobuild + uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/plugin-ci-workflow.yml b/.github/workflows/plugin-ci-workflow.yml new file mode 100644 index 0000000..845b778 --- /dev/null +++ b/.github/workflows/plugin-ci-workflow.yml @@ -0,0 +1,335 @@ +# +-------------------------------------------------------------------------+ +# | Copyright (C) 2004-2026 The Cacti Group | +# +-------------------------------------------------------------------------+ +# | Cacti: The Complete RRDtool-based Graphing Solution | +# +-------------------------------------------------------------------------+ +# | http://www.cacti.net/ | +# +-------------------------------------------------------------------------+ + +name: Plugin Integration Tests + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + +permissions: + contents: read + +jobs: + # The INFO compat value implies a Cacti 1.2.x install, which requires PHP + # 8.0. Linting at that floor catches syntax the declared minimum cannot run, + # which the integration matrix below would miss. + syntax-floor: + name: PHP 8.0 syntax floor + runs-on: ubuntu-latest + steps: + - name: Checkout gpsmap Plugin + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Install PHP 8.0 + uses: shivammathur/setup-php@cf4cade2721270509d5b1c766ab3549210a39a2a # v2 + with: + php-version: '8.0' + + - name: Lint every PHP file + run: | + if find . -path ./vendor -prune -o -name '*.php' -print \ + | xargs -n1 php -l 2>&1 | grep -iv 'no syntax errors detected'; then + echo "Syntax errors found at the declared PHP floor" + exit 1 + fi + + unit-test: + name: Standalone test suite + runs-on: ubuntu-latest + steps: + - name: Checkout gpsmap Plugin + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Install PHP + uses: shivammathur/setup-php@cf4cade2721270509d5b1c766ab3549210a39a2a # v2 + with: + php-version: '8.1' + coverage: xdebug + + # Guarded so this workflow can land ahead of the suite it runs. + - name: Run the suite + run: | + if [ -f tests/run.php ]; then + php tests/run.php + else + echo "tests/run.php not present on this branch, skipping" + fi + + - name: Enforce the coverage gate + env: + XDEBUG_MODE: coverage + run: | + if [ -f tests/coverage.php ]; then + php tests/coverage.php + else + echo "tests/coverage.php not present on this branch, skipping" + fi + + integration-test: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + php: ['8.1', '8.2', '8.3', '8.4'] + os: [ubuntu-latest] + cacti: ['develop'] + + services: + mysql: + image: mariadb:10.6 + env: + MYSQL_ROOT_PASSWORD: cactiroot + MYSQL_DATABASE: cacti + MYSQL_USER: cactiuser + MYSQL_PASSWORD: cactiuser + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + name: PHP ${{ matrix.php }} Integration Test on ${{ matrix.os }} against ${{ matrix.cacti }} + + steps: + - name: Checkout Cacti + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + repository: Cacti/cacti + ref: ${{ matrix.cacti }} + path: cacti + + - name: Checkout gpsmap Plugin + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + path: cacti/plugins/gpsmap + fetch-depth: 0 + + - name: Install PHP ${{ matrix.php }} + uses: shivammathur/setup-php@cf4cade2721270509d5b1c766ab3549210a39a2a # v2 + with: + php-version: ${{ matrix.php }} + 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" + + - name: Check PHP version + run: | + php -v + echo "PHP_BINARY=$(command -v php)" >> "$GITHUB_ENV" + + - name: Run apt-get update + run: sudo apt-get update + + - name: Install System Dependencies + run: | + sudo apt-get install --yes --no-install-recommends apache2 snmp snmpd rrdtool fping + + - name: Start SNMPD Agent and Test + run: | + sudo systemctl start snmpd + sudo snmpwalk -c public -v2c -On localhost .1.3.6.1.2.1.1 + + - name: Setup Permissions + run: | + sudo chown -R www-data:runner ${{ github.workspace }}/cacti + sudo find ${{ github.workspace }}/cacti -type d -exec chmod 775 {} \; + sudo find ${{ github.workspace }}/cacti -type f -exec chmod 664 {} \; + sudo chmod +x ${{ github.workspace }}/cacti/cmd.php + sudo chmod +x ${{ github.workspace }}/cacti/poller.php + + - name: Create MySQL Config + run: | + echo -e "[client]\nuser = root\npassword = cactiroot\nhost = 127.0.0.1\n" > ~/.my.cnf + + - name: Initialize Cacti Database + env: + MYSQL_AUTH_USR: '--defaults-file=~/.my.cnf' + run: | + mysql $MYSQL_AUTH_USR -e 'CREATE DATABASE IF NOT EXISTS cacti;' + mysql $MYSQL_AUTH_USR -e "CREATE USER IF NOT EXISTS 'cactiuser'@'localhost' IDENTIFIED BY 'cactiuser';" + mysql $MYSQL_AUTH_USR -e "GRANT ALL PRIVILEGES ON cacti.* TO 'cactiuser'@'localhost';" + mysql $MYSQL_AUTH_USR -e "GRANT SELECT ON mysql.time_zone_name TO 'cactiuser'@'localhost';" + mysql $MYSQL_AUTH_USR -e "FLUSH PRIVILEGES;" + mysql $MYSQL_AUTH_USR cacti < ${{ github.workspace }}/cacti/cacti.sql + mysql $MYSQL_AUTH_USR -e "INSERT INTO settings (name, value) VALUES ('path_php_binary', '/usr/bin/php')" cacti + + - name: Install Composer Dependencies + run: | + cd ${{ github.workspace }}/cacti + if [ -f composer.json ]; then + sudo composer install --prefer-dist --no-progress + fi + + - name: Create Cacti config.php + run: | + cat ${{ github.workspace }}/cacti/include/config.php.dist | \ + sed -r "s/localhost/127.0.0.1/g" | \ + sed -r "s/'cacti'/'cacti'/g" | \ + sed -r "s/'cactiuser'/'cactiuser'/g" | \ + sed -r "s/'cactiuser'/'cactiuser'/g" > ${{ github.workspace }}/cacti/include/config.php + sudo chmod 664 ${{ github.workspace }}/cacti/include/config.php + + - name: Configure Apache + run: | + cat << 'EOF' | sed 's#GITHUB_WORKSPACE#${{ github.workspace }}#g' > /tmp/cacti.conf + + ServerAdmin webmaster@localhost + DocumentRoot GITHUB_WORKSPACE/cacti + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + EOF + sudo cp /tmp/cacti.conf /etc/apache2/sites-available/000-default.conf + sudo systemctl restart apache2 + + - name: Install Cacti via CLI + run: | + cd ${{ github.workspace }}/cacti + sudo php cli/install_cacti.php --accept-eula --install --force + + - name: Install gpsmap Plugin + run: | + cd ${{ github.workspace }}/cacti + sudo php cli/plugin_manage.php --plugin=gpsmap --install --enable + + - name: Check PHP Syntax for Plugin + run: | + cd ${{ github.workspace }}/cacti/plugins/gpsmap + if find . -name '*.php' -exec php -l {} 2>&1 \; | grep -iv 'no syntax errors detected'; then + echo "Syntax errors found!" + exit 1 + fi + + - name: Remove the plugins directory exclusion from the .phpstan.neon + run: | + if [ -f .phpstan.neon ]; then + sed -i '/plugins/d' .phpstan.neon + fi + working-directory: ${{ github.workspace }}/cacti + + - name: Mark composer scripts executable + run: | + if [ -d "${{ github.workspace }}/cacti/include/vendor/bin" ]; then + sudo find "${{ github.workspace }}/cacti/include/vendor/bin" -maxdepth 1 -type f -exec chmod +x {} + + fi + + - name: Run Linter on base code + run: | + if composer --no-ansi run-script --list | grep -qE '^[[:space:]]+lint([[:space:]]|$)'; then + composer run-script lint ${{ github.workspace }}/cacti/plugins/gpsmap + else + echo 'Composer lint script is not defined; skipping.' + fi + working-directory: ${{ github.workspace }}/cacti + + - name: Check coding standards on changed plugin PHP + env: + BASE_SHA: ${{ github.event.before }} + run: | + if ! composer --no-ansi run-script --list | grep -qE '^[[:space:]]+phpcsfixer([[:space:]]|$)'; then + echo 'Composer phpcsfixer script is not defined; skipping.' + exit 0 + fi + + if [ "$GITHUB_EVENT_NAME" = 'pull_request' ]; then + BASE_SHA=$(git -C plugins/gpsmap rev-parse HEAD^1) + elif ! git -C plugins/gpsmap cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then + BASE_SHA=$(git -C plugins/gpsmap rev-parse HEAD^) + fi + + mapfile -d '' changed_php < <( + git -C plugins/gpsmap diff --name-only --diff-filter=ACMR -z \ + "$BASE_SHA" HEAD -- '*.php' + ) + + if [ "${#changed_php[@]}" -eq 0 ]; then + echo 'No PHP files changed; skipping coding standards.' + exit 0 + fi + + php_files=() + for file in "${changed_php[@]}"; do + php_files+=("${{ github.workspace }}/cacti/plugins/gpsmap/$file") + done + + composer run-script phpcsfixer "${php_files[@]}" + working-directory: ${{ github.workspace }}/cacti + + # gpsmap adds columns to the core host table, so a device with coordinates + # is what makes the poller hook produce anything at all. + - name: Add a mapped Device + run: | + cd ${{ github.workspace }}/cacti + # A bare develop install may contain no templates. Seed the smallest + # valid template fixture so add_device.php can exercise the real API. + TPL=$(mysql -u cactiuser -p'cactiuser' -h 127.0.0.1 cacti -sN \ + -e "SELECT id FROM host_template ORDER BY id LIMIT 1;") + if [ -z "$TPL" ]; then + mysql -u cactiuser -p'cactiuser' -h 127.0.0.1 cacti \ + -e "INSERT INTO host_template (name) VALUES ('CI Device');" + TPL=$(mysql -u cactiuser -p'cactiuser' -h 127.0.0.1 cacti -sN \ + -e "SELECT id FROM host_template WHERE name = 'CI Device' ORDER BY id DESC LIMIT 1;") + fi + echo "Using host_template id $TPL" + sudo php cli/add_device.php --description=mapped --ip=127.0.0.1 --template="$TPL" + mysql -u cactiuser -p'cactiuser' -h 127.0.0.1 cacti \ + -e "UPDATE host SET latitude='51.5074', longitude='-0.1278' WHERE description='mapped';" + mysql -u cactiuser -p'cactiuser' -h 127.0.0.1 cacti \ + -e "INSERT INTO gpsmap_templates (templateID, templateName, upimage, recoverimage, downimage, AP) + SELECT host_template_id, 'ci', 'Green.png', 'Orange.png', 'Red.png', 0 + FROM host WHERE description='mapped' LIMIT 1;" + + - name: Run Cacti Poller + run: | + cd ${{ github.workspace }}/cacti + sudo php poller.php --poller=1 --force --debug + if ! grep -q "SYSTEM STATS" log/cacti.log; then + echo "Cacti poller did not finish successfully" + cat log/cacti.log + exit 1 + fi + + - name: Verify the poller wrote its map artefacts + run: | + cd ${{ github.workspace }}/cacti/plugins/gpsmap/XML + ls -la + for f in all.xml all.kml all-top.html; do + if [ ! -s "$f" ]; then + echo "Expected $f to exist and be non-empty" + exit 1 + fi + done + if ! grep -q '