Add types.exclude pattern

This commit is contained in:
Pim van Pelt
2025-07-07 00:39:56 +02:00
parent 88e30a40b1
commit c8df809c29
5 changed files with 94 additions and 22 deletions

View File

@ -56,3 +56,6 @@ types:
- system license print # License information - system license print # License information
- / interface print # Interfaces - / interface print # Interfaces
- / export terse # Configuration - / export terse # Configuration
exclude:
- "^# ....-..-.. ..:..:.. by RouterOS"
- "^# .../../.... ..:..:.. by RouterOS"

View File

@ -18,6 +18,7 @@ type Config struct {
type DeviceType struct { type DeviceType struct {
Commands []string `yaml:"commands"` Commands []string `yaml:"commands"`
Exclude []string `yaml:"exclude,omitempty"`
} }
type Device struct { type Device struct {

View File

@ -100,11 +100,13 @@ func main() {
user := deviceConfig.User user := deviceConfig.User
commands := deviceConfig.Commands commands := deviceConfig.Commands
deviceType := deviceConfig.Type deviceType := deviceConfig.Type
var excludePatterns []string
// If device has a type, get commands from types section // If device has a type, get commands and exclude patterns from types section
if deviceType != "" { if deviceType != "" {
if typeConfig, exists := cfg.Types[deviceType]; exists { if typeConfig, exists := cfg.Types[deviceType]; exists {
commands = typeConfig.Commands commands = typeConfig.Commands
excludePatterns = typeConfig.Exclude
} }
} }
@ -127,7 +129,7 @@ func main() {
continue continue
} }
err = backup.BackupCommands(commands, outputDir) err = backup.BackupCommands(commands, excludePatterns, outputDir)
backup.Disconnect() backup.Disconnect()
if err != nil { if err != nil {

View File

@ -8,6 +8,7 @@ import (
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -202,8 +203,33 @@ func (rb *RouterBackup) RunCommand(command string) (string, error) {
return string(output), nil return string(output), nil
} }
// filterOutput removes lines matching exclude patterns from the output
func filterOutput(output string, excludePatterns []string) string {
if len(excludePatterns) == 0 {
return output
}
lines := strings.Split(output, "\n")
var filteredLines []string
for _, line := range lines {
exclude := false
for _, pattern := range excludePatterns {
if matched, _ := regexp.MatchString(pattern, line); matched {
exclude = true
break
}
}
if !exclude {
filteredLines = append(filteredLines, line)
}
}
return strings.Join(filteredLines, "\n")
}
// BackupCommands runs multiple commands and saves outputs to files // BackupCommands runs multiple commands and saves outputs to files
func (rb *RouterBackup) BackupCommands(commands []string, outputDir string) error { func (rb *RouterBackup) BackupCommands(commands []string, excludePatterns []string, outputDir string) error {
if err := os.MkdirAll(outputDir, 0755); err != nil { if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %v", outputDir, err) return fmt.Errorf("failed to create directory %s: %v", outputDir, err)
} }
@ -241,7 +267,8 @@ func (rb *RouterBackup) BackupCommands(commands []string, outputDir string) erro
} }
fmt.Fprintf(file, "## COMMAND: %s\n", command) fmt.Fprintf(file, "## COMMAND: %s\n", command)
file.WriteString(output) filteredOutput := filterOutput(output, excludePatterns)
file.WriteString(filteredOutput)
file.Close() file.Close()
successCount++ successCount++

View File

@ -57,17 +57,22 @@ func TestBackupCommandsDirectoryCreation(t *testing.T) {
// This should create the directory even without a connection // This should create the directory even without a connection
// and fail gracefully when trying to run commands // and fail gracefully when trying to run commands
_ = rb.BackupCommands([]string{"show version"}, outputDir) err := rb.BackupCommands([]string{"show version"}, []string{}, outputDir)
// Should not error on directory creation // Should not error on directory creation
if _, statErr := os.Stat(outputDir); os.IsNotExist(statErr) { if _, statErr := os.Stat(outputDir); os.IsNotExist(statErr) {
t.Error("Expected output directory to be created") t.Error("Expected output directory to be created")
} }
// Should create the output file even if commands fail // Should return error when commands fail
if err == nil {
t.Error("Expected error when commands fail")
}
// Should NOT create the output file when commands fail (atomic behavior)
expectedFile := filepath.Join(outputDir, "testhost") expectedFile := filepath.Join(outputDir, "testhost")
if _, statErr := os.Stat(expectedFile); os.IsNotExist(statErr) { if _, statErr := os.Stat(expectedFile); !os.IsNotExist(statErr) {
t.Error("Expected output file to be created") t.Error("Expected output file to NOT be created when commands fail")
} }
} }
@ -76,15 +81,15 @@ func TestBackupCommandsEmptyCommands(t *testing.T) {
rb := NewRouterBackup("testhost", "", "testuser", "testpass", "", 22) rb := NewRouterBackup("testhost", "", "testuser", "testpass", "", 22)
err := rb.BackupCommands([]string{}, tempDir) err := rb.BackupCommands([]string{}, []string{}, tempDir)
if err != nil { if err == nil {
t.Errorf("Expected no error for empty commands list, got %v", err) t.Error("Expected error for empty commands list (no successful commands)")
} }
// Should still create the output file // Should NOT create the output file when no commands succeed
expectedFile := filepath.Join(tempDir, "testhost") expectedFile := filepath.Join(tempDir, "testhost")
if _, statErr := os.Stat(expectedFile); os.IsNotExist(statErr) { if _, statErr := os.Stat(expectedFile); !os.IsNotExist(statErr) {
t.Error("Expected output file to be created even for empty commands") t.Error("Expected output file to NOT be created when no commands succeed")
} }
} }
@ -160,20 +165,19 @@ func TestBackupCommandsFileOperations(t *testing.T) {
// Create some fake commands (they will fail but we can test file operations) // Create some fake commands (they will fail but we can test file operations)
commands := []string{"show version", "show interfaces"} commands := []string{"show version", "show interfaces"}
err := rb.BackupCommands(commands, tempDir) err := rb.BackupCommands(commands, []string{}, tempDir)
if err != nil { if err == nil {
t.Errorf("Unexpected error: %v", err) t.Error("Expected error when all commands fail")
} }
// Check that output file was created // Check that output file was NOT created (atomic behavior)
outputFile := filepath.Join(tempDir, "testhost") outputFile := filepath.Join(tempDir, "testhost")
_, err = os.ReadFile(outputFile) _, err = os.ReadFile(outputFile)
if err != nil { if err == nil {
t.Fatalf("Failed to read output file: %v", err) t.Error("Expected output file to not exist when all commands fail")
} }
// File should be created (it will be empty if all commands fail) // This test verifies that atomic file behavior works correctly
// This test just verifies the file creation works
} }
func TestRouterBackupConnectionState(t *testing.T) { func TestRouterBackupConnectionState(t *testing.T) {
@ -281,3 +285,38 @@ func TestIPv6AddressFormatting(t *testing.T) {
t.Error("Expected IPv4 address to use tcp4 network type") t.Error("Expected IPv4 address to use tcp4 network type")
} }
} }
func TestFilterOutput(t *testing.T) {
// Test with no exclude patterns
input := "line1\nline2\nline3"
result := filterOutput(input, []string{})
if result != input {
t.Errorf("Expected no filtering with empty patterns, got '%s'", result)
}
// Test with matching pattern
input = "# 2025-07-06 21:30:45 by RouterOS\nconfig line 1\nconfig line 2"
excludePatterns := []string{"^# ....-..-.. ..:..:.. by RouterOS"}
expected := "config line 1\nconfig line 2"
result = filterOutput(input, excludePatterns)
if result != expected {
t.Errorf("Expected '%s', got '%s'", expected, result)
}
// Test with multiple patterns
input = "line1\nDEBUG: debug info\nline2\nINFO: info message\nline3"
excludePatterns = []string{"^DEBUG:", "^INFO:"}
expected = "line1\nline2\nline3"
result = filterOutput(input, excludePatterns)
if result != expected {
t.Errorf("Expected '%s', got '%s'", expected, result)
}
// Test with no matches
input = "line1\nline2\nline3"
excludePatterns = []string{"nomatch"}
result = filterOutput(input, excludePatterns)
if result != input {
t.Errorf("Expected no filtering when patterns don't match, got '%s'", result)
}
}