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
3 changes: 2 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ plugin_syslog/
- **Read:** `syslog_db_fetch_assoc($sql)`, `syslog_db_fetch_cell($sql)`
- **Write:** `syslog_db_execute($sql)`, `syslog_db_execute_prepared($sql, $params)`
- **Connection:** Managed via `$syslog_cnn` global.
- **Schema:** Tables are defined/updated in `setup.php` (`syslog_setup_table_new`).
- **Schema:** Table creation is defined in `includes/schema.php` (`syslog_setup_table_new`); upgrade orchestration is in `setup.php`.
- **New Tables:** Whenever adding a Syslog table, you MUST update `cli/syslog_schema.php` in the same change. Include the table in its required-table list with the correct main/remote collector scope so `--audit` detects its absence, and ensure `--repair` and `--upgrade` can create it. Update the CLI schema regression checks accordingly.

### Cacti Integration Patterns
- **Hooks:** Register hooks in `plugin_syslog_install()` in `setup.php`, but
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/plugin-ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ jobs:
include/vendor/bin/pest --configuration=plugins/syslog/phpunit.xml \
--coverage-clover=plugins/syslog/coverage/clover.xml

- name: Run Syslog schema CLI regression checks
run: php tests/regression/cli_schema_upgrade_test.php
working-directory: ${{ github.workspace }}/cacti/plugins/syslog

# Whole-file coverage is meaningless here: most of the plugin only runs
# inside a live Cacti. What is enforceable is that a change covers the
# lines it adds.
Expand Down
173 changes: 173 additions & 0 deletions cli/syslog_schema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php
/*
+-------------------------------------------------------------------------+
| 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/ |
+-------------------------------------------------------------------------+
*/

include(__DIR__ . '/../../../include/cli_check.php');
require_once(dirname(__DIR__) . '/setup.php');
require_once(dirname(__DIR__) . '/includes/schema.php');
require_once(dirname(__DIR__) . '/includes/settings.php');

/* process calling arguments */
$parms = $_SERVER['argv'];
array_shift($parms);

$audit = false;
$repair = false;
$upgrade = false;

if (!cacti_sizeof($parms)) {
fwrite(STDERR, 'ERROR: Specify --audit, --repair or --upgrade. Use --help for usage.' . PHP_EOL);
exit(1);
}

foreach ($parms as $arg) {
switch ($arg) {
case '--audit':
$audit = true;
break;
case '--repair':
$repair = true;
break;
case '--upgrade':
$upgrade = true;
break;
case '--help':
case '-H':
case '-h':
syslog_schema_display_help();
exit(0);
default:
fwrite(STDERR, "ERROR: Unknown argument: $arg" . PHP_EOL);
exit(1);
}
}

if ($audit && ($repair || $upgrade)) {
fwrite(STDERR, 'ERROR: --audit cannot be combined with --repair or --upgrade.' . PHP_EOL);
exit(1);
}

syslog_determine_config();
if (!defined('SYSLOG_CONFIG')) {
fwrite(STDERR, 'ERROR: Syslog database configuration is missing.' . PHP_EOL);
exit(1);
}

$database_last_error = '';
if (!syslog_connect()) {
fwrite(STDERR, 'ERROR: Unable to connect to the Syslog database.' . PHP_EOL);
exit(1);
}

if ($repair || $upgrade) {
$exit_code = syslog_schema_repair_database() ? 0 : 1;
} else {
$exit_code = syslog_schema_report_audit_results() ? 0 : 1;
}

exit($exit_code);

/**
* Repair missing tables and rerun the existing Syslog schema migrations.
*
* @return bool Whether the migrations and final table audit succeeded.
*/
function syslog_schema_repair_database(): bool {
print 'Upgrading Syslog schema and repairing missing tables...' . PHP_EOL;
syslog_setup_table_new([
'upgrade_type' => 'upgrade',
'engine' => read_config_option('syslog_install_engine') ?: 'InnoDB',
'db_type' => 'part',
'days' => read_config_option('syslog_install_days')
], true);
if (!syslog_check_upgrade(true)) {
fwrite(STDERR, 'ERROR: Syslog schema upgrade failed; check the Cacti log.' . PHP_EOL);
return false;
}

$success = syslog_schema_report_audit_results(false);
if ($success) {
print 'Syslog schema upgrade completed; all required tables exist.' . PHP_EOL;
}

return $success;
}

/**
* Report missing Syslog tables for the configured Data Collector without repairing them.
*
* @param bool $audit Print the audit success message for read-only runs.
*
* @return bool Whether all required tables exist and the audit queries succeeded.
*/
function syslog_schema_report_audit_results(bool $audit = true): bool {
global $config, $database_last_error;

$tables = [
'syslog', 'syslog_alert', 'syslog_alert_suppression', 'syslog_incoming',
'syslog_remove', 'syslog_reports', 'syslog_saved_searches', 'syslog_status',
'syslog_programs', 'syslog_hosts', 'syslog_facilities', 'syslog_priorities',
'syslog_host_facilities', 'syslog_removed', 'syslog_logs',
'syslog_dashboards', 'syslog_dashboard_panels', 'syslog_dashboards_perm',
'syslog_saved_searches_perm', 'syslog_device_rule', 'syslog_replication_output',
'syslog_replication_recovery'
];
if ((int) $config['poller_id'] <= 1) {
$tables[] = 'syslog_replication_receipts';
$tables[] = 'syslog_replication_collectors';
}

$missing = [];
foreach ($tables as $table) {
if (!syslog_db_table_exists($table, false)) {
$missing[] = $table;
}
}

if (!empty($database_last_error)) {
fwrite(STDERR, 'ERROR: Schema check encountered a database error; check the Cacti log.' . PHP_EOL);
return false;
}
if ($missing) {
fwrite(STDERR, 'ERROR: Missing tables: ' . implode(', ', $missing) . PHP_EOL);
return false;
}

if ($audit) {
print 'Syslog schema audit passed; all required tables exist.' . PHP_EOL;
}
return true;
}

/**
* Display the Syslog schema utility usage and options.
*
* @return void
*/
function syslog_schema_display_help(): void {
print 'Usage: php syslog_schema.php --audit|--repair|--upgrade' . PHP_EOL;
print ' --audit Report missing tables without changing the schema.' . PHP_EOL;
print ' --repair Repair missing tables and rerun schema migrations.' . PHP_EOL;
print ' --upgrade Upgrade the schema, including missing-table repair.' . PHP_EOL;
print 'Repair and upgrade preserve existing data and preferences.' . PHP_EOL;
}
9 changes: 7 additions & 2 deletions includes/schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -771,16 +771,21 @@ function syslog_setup_table_new(array $options, bool $repair = false): void {

/** Complete an interrupted install or upgrade without dropping existing data. */
function syslog_ensure_table_structures(): bool {
global $config;

require_once(__DIR__ . '/settings.php');

$tables = [
'syslog', 'syslog_alert', 'syslog_incoming', 'syslog_alert_suppression',
'syslog_remove', 'syslog_reports', 'syslog_status', 'syslog_programs',
'syslog_hosts', 'syslog_facilities', 'syslog_priorities', 'syslog_host_facilities',
'syslog_removed', 'syslog_logs', 'syslog_device_rule',
'syslog_replication_output', 'syslog_replication_receipts',
'syslog_replication_collectors', 'syslog_replication_recovery'
'syslog_replication_output', 'syslog_replication_recovery'
];
if ((int) $config['poller_id'] <= 1) {
$tables[] = 'syslog_replication_receipts';
$tables[] = 'syslog_replication_collectors';
}
$rebuilt = false;

foreach ($tables as $table) {
Expand Down
4 changes: 2 additions & 2 deletions setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ function syslog_upgrade_create_permission_realms(): bool {
}

/** Upgrade the Syslog schema and permissions at install/upgrade time. */
function syslog_check_upgrade(): bool {
function syslog_check_upgrade(bool $force = false): bool {
global $config, $syslogdb_default, $syslog_levels, $syslog_upgrade, $syslog_cnn;

require_once(__DIR__ . '/includes/schema.php');
Expand Down Expand Up @@ -864,7 +864,7 @@ function syslog_check_upgrade(): bool {

$version = plugin_syslog_version();
$installed = db_fetch_cell_prepared('SELECT version FROM plugin_config WHERE directory = ?', ['syslog']);
if ($installed !== false && version_compare((string) $installed, $version['version'], '>=')) {
if (!$force && $installed !== false && version_compare((string) $installed, $version['version'], '>=')) {
syslog_notice_traditional_tables(true);
include_once($config['base_path'] . '/lib/poller.php');
api_plugin_replicate_config();
Expand Down
1 change: 1 addition & 0 deletions syslog_process.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
}

// replicate in syslog tables sync is enabled
require_once(__DIR__ . '/includes/processing.php');
syslog_replicate_in();
}

Expand Down
8 changes: 8 additions & 0 deletions tests/Unit/SetupModuleBoundaryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@
}
});

it('loads the processing module before remote CLI replication', function () {
$source = plugin_test_read_source('syslog_process.php');
$include = strpos($source, "require_once(__DIR__ . '/includes/processing.php');");
$call = strpos($source, 'syslog_replicate_in();');

expect($include)->not->toBeFalse()->toBeLessThan($call);
});

it('registers extracted callbacks against their owning module', function () {
$setup = plugin_test_read_source('setup.php');

Expand Down
3 changes: 3 additions & 0 deletions tests/bin/patch-coverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ function changed_lines($base_ref) {
'syslog_counter.php',
'syslog_process.php',
'syslog_recovery.php',
// CLI bootstrap and top-level execution require Cacti; behavior is checked
// by tests/regression/cli_schema_upgrade_test.php in an isolated CLI fixture.
'cli/syslog_schema.php',
// Setup modules are loaded by Cacti hook paths rather than directly by the
// isolated Pest bootstrap. Their behavior is covered by targeted tests, but
// they are intentionally absent from Clover's source list.
Expand Down
Loading
Loading