Stable and functional filter.go it manages to match a number of lines I need matched.
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Colin 2024-06-12 13:15:13 -04:00
parent 5ebd0a46eb
commit a6b6f5cbf6
64 changed files with 391 additions and 363 deletions

115
api.go Normal file
View File

@ -0,0 +1,115 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os/exec"
"strings"
)
type ContainerInfo struct {
ID string
Name string
CName string
Interval string
Ignores []string
}
func listContainersHandler(w http.ResponseWriter, r *http.Request) {
containers, err := listContainers()
if err != nil {
http.Error(w, fmt.Sprintf("Error listing containers: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(containers); err != nil {
http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError)
}
}
func getDiffHandler(w http.ResponseWriter, r *http.Request) {
containerID := r.URL.Query().Get("id")
if containerID == "" {
http.Error(w, "Missing container ID", http.StatusBadRequest)
return
}
diffOutput, err := getDiffOutput(containerID)
if err != nil {
http.Error(w, fmt.Sprintf("Error getting diff for container %s: %v", containerID, err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(diffOutput))
}
func startServer() {
http.HandleFunc("/containers", listContainersHandler)
http.HandleFunc("/diff", getDiffHandler)
log.Println("Starting server on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Error starting server: %v", err)
}
}
func listContainers() ([]ContainerInfo, error) {
output, err := exec.Command("docker", "ps", "--format", "{{.ID}} {{.Names}} {{.Label \"oculus.enable\"}} {{.Label \"oculus.interval\"}} {{.Label \"oculus.cname\"}} {{.Label \"oculus.ignores\"}}").Output()
if err != nil {
return nil, err
}
lines := strings.Split(string(output), "\n")
var containers []ContainerInfo
for _, line := range lines {
if line == "" {
continue
}
parts := strings.Fields(line)
if len(parts) < 3 || parts[2] == "" {
continue
}
id := parts[0]
name := parts[1]
interval := "300s"
if len(parts) > 3 && parts[3] != "" {
interval = parts[3]
}
cname := name
if len(parts) > 4 && parts[4] != "" {
cname = parts[4]
}
ignores := []string{}
if len(parts) > 5 && parts[5] != "" {
ignores = strings.Split(parts[5], ",")
}
containers = append(containers, ContainerInfo{
ID: id,
Name: name,
CName: cname,
Interval: interval,
Ignores: ignores,
})
}
return containers, nil
}
func getDiffOutput(containerID string) (string, error) {
cmd := exec.Command("docker", "diff", containerID)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("docker diff failed: %w", err)
}
return string(output), nil
}
func main() {
startServer()
}

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
ARCHITECTURES=("linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64") ARCHITECTURES=("darwin/arm64" "linux/amd64" "linux/arm64" "darwin/amd64" "windows/amd64")
PROJECT_NAME=$(basename "$(pwd)") PROJECT_NAME=$(basename "$(pwd)")
prepare_build() { prepare_build() {
@ -14,19 +14,51 @@ prepare_build() {
build_binary() { build_binary() {
local os=$1 local os=$1
local arch=$2 local arch=$2
local output="dist/${PROJECT_NAME}_${os}_${arch}" local output_main="dist/${os}_${arch}_main"
env GOOS=$os GOARCH=$arch go build -o $output . &> "build_logs/${os}_${arch}.log" local output_filter="dist/${os}_${arch}_filter"
local output_api="dist/${os}_${arch}_api"
env GOOS=$os GOARCH=$arch go build -o $output_main main.go &> "build_logs/${os}_${arch}_main.log"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Build failed for $os/$arch" >> "build_logs/error.log" echo "Build failed for $os/$arch main" >> "build_logs/error.log"
else else
echo "Build succeeded for $os/$arch" echo "Build succeeded for $os/$arch main"
fi fi
env GOOS=$os GOARCH=$arch go build -o $output_filter filter.go &> "build_logs/${os}_${arch}_filter.log"
if [ $? -ne 0 ]; then
echo "Build failed for $os/$arch filter" >> "build_logs/error.log"
else
echo "Build succeeded for $os/$arch filter"
fi
env GOOS=$os GOARCH=$arch go build -o $output_api api.go &> "build_logs/${os}_${arch}_api.log"
if [ $? -ne 0 ]; then
echo "Build failed for $os/$arch api" >> "build_logs/error.log"
else
echo "Build succeeded for $os/$arch api"
fi
if [ "$os" == "linux" ]; then if [ "$os" == "linux" ]; then
env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output}_static" . &> "build_logs/${os}_${arch}_static.log" env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_main}_static" main.go &> "build_logs/${os}_${arch}_main_static.log"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Static build failed for $os/$arch" >> "build_logs/error.log" echo "Static build failed for $os/$arch main" >> "build_logs/error.log"
else else
echo "Static build succeeded for $os/$arch" echo "Static build succeeded for $os/$arch main"
fi
env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_filter}_static" filter.go &> "build_logs/${os}_${arch}_filter_static.log"
if [ $? -ne 0 ]; then
echo "Static build failed for $os/$arch filter" >> "build_logs/error.log"
else
echo "Static build succeeded for $os/$arch filter"
fi
env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_api}_static" api.go &> "build_logs/${os}_${arch}_api_static.log"
if [ $? -ne 0 ]; then
echo "Static build failed for $os/$arch api" >> "build_logs/error.log"
else
echo "Static build succeeded for $os/$arch api"
fi fi
fi fi
} }

View File

@ -1,2 +0,0 @@
# oculus
./main.go:274:21: syntax error: cannot use id, container := watchedContainers.containers as value

View File

View File

View File

View File

@ -1,2 +0,0 @@
# oculus
./main.go:274:21: syntax error: cannot use id, container := watchedContainers.containers as value

View File

View File

View File

View File

@ -1,178 +0,0 @@
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64

View File

@ -1,2 +0,0 @@
# oculus
./main.go:274:21: syntax error: cannot use id, container := watchedContainers.containers as value

View File

View File

View File

View File

View File

View File

View File

@ -1,2 +0,0 @@
# oculus
./main.go:274:21: syntax error: cannot use id, container := watchedContainers.containers as value

View File

@ -1,2 +0,0 @@
# oculus
./main.go:274:21: syntax error: cannot use id, container := watchedContainers.containers as value

View File

View File

View File

View File

View File

View File

View File

@ -1,2 +0,0 @@
# oculus
./main.go:274:21: syntax error: cannot use id, container := watchedContainers.containers as value

View File

@ -1,2 +0,0 @@
# oculus
./main.go:274:21: syntax error: cannot use id, container := watchedContainers.containers as value

View File

View File

View File

View File

@ -0,0 +1 @@
A /newfile.txt

View File

@ -0,0 +1,5 @@
D /file1.txt
A /file2.txt
C /do/match/file3.txt
A /dontmatch.yml
D /dont/match.md

BIN
dist/darwin_amd64_api vendored Executable file

Binary file not shown.

BIN
dist/darwin_amd64_filter vendored Executable file

Binary file not shown.

BIN
dist/darwin_amd64_main vendored Executable file

Binary file not shown.

BIN
dist/darwin_arm64_api vendored Executable file

Binary file not shown.

BIN
dist/darwin_arm64_filter vendored Executable file

Binary file not shown.

BIN
dist/darwin_arm64_main vendored Executable file

Binary file not shown.

BIN
dist/linux_amd64_api vendored Executable file

Binary file not shown.

BIN
dist/linux_amd64_api_static vendored Executable file

Binary file not shown.

BIN
dist/linux_amd64_filter vendored Executable file

Binary file not shown.

BIN
dist/linux_amd64_filter_static vendored Executable file

Binary file not shown.

BIN
dist/linux_amd64_main vendored Executable file

Binary file not shown.

BIN
dist/linux_amd64_main_static vendored Executable file

Binary file not shown.

BIN
dist/linux_arm64_api vendored Executable file

Binary file not shown.

BIN
dist/linux_arm64_api_static vendored Executable file

Binary file not shown.

BIN
dist/linux_arm64_filter vendored Executable file

Binary file not shown.

BIN
dist/linux_arm64_filter_static vendored Executable file

Binary file not shown.

BIN
dist/linux_arm64_main vendored Executable file

Binary file not shown.

BIN
dist/linux_arm64_main_static vendored Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/windows_amd64_api vendored Executable file

Binary file not shown.

BIN
dist/windows_amd64_filter vendored Executable file

Binary file not shown.

BIN
dist/windows_amd64_main vendored Executable file

Binary file not shown.

105
filter.go Normal file
View File

@ -0,0 +1,105 @@
package main
import (
"bufio"
"fmt"
"log"
"os"
"regexp"
"strings"
)
func main() {
if len(os.Args) < 3 {
log.Fatalf("Usage: %s <file> <pattern1> [<pattern2> ... <patternN>]\n", os.Args[0])
}
filename := os.Args[1]
patterns := os.Args[2:]
// Compile patterns into regular expressions
regexPatterns := compilePatterns(patterns)
// Read the file
file, err := os.Open(filename)
if err != nil {
log.Fatalf("Error opening file: %s\n", err)
}
defer file.Close()
var filteredLines []string
var removedCount int
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Printf("Processing line: %s\n", line) // Debug print
if matchesAnyPattern(line, regexPatterns) {
fmt.Printf("Line matches pattern, removing: %s\n", line) // Debug print
removedCount++
} else {
filteredLines = append(filteredLines, line)
}
}
if err := scanner.Err(); err != nil {
log.Fatalf("Error reading file: %s\n", err)
}
// Write the filtered lines back to the file
tempFile, err := os.CreateTemp("", "filtered_output")
if err != nil {
log.Fatalf("Error creating temporary file: %s\n", err)
}
defer os.Remove(tempFile.Name())
for _, line := range filteredLines {
_, err = tempFile.WriteString(line + "\n")
if err != nil {
log.Fatalf("Error writing to temporary file: %s\n", err)
}
}
tempFile.Close()
err = os.Rename(tempFile.Name(), filename)
if err != nil {
log.Fatalf("Error renaming temporary file: %s\n", err)
}
fmt.Printf("Filtered %d lines from %s\n", removedCount, filename)
}
func compilePatterns(patterns []string) []*regexp.Regexp {
var regexPatterns []*regexp.Regexp
for _, pattern := range patterns {
// Convert wildcard pattern to regex pattern
regexPattern := strings.ReplaceAll(regexp.QuoteMeta(pattern), "\\*", ".*")
regexPattern = strings.TrimSuffix(regexPattern, `\.`) // Remove trailing dot if present
fmt.Printf("Compiled pattern: %s to regex: %s\n", pattern, regexPattern) // Debug print
re, err := regexp.Compile(regexPattern)
if err != nil {
log.Fatalf("Error compiling regex pattern: %s\n", err)
}
regexPatterns = append(regexPatterns, re)
}
return regexPatterns
}
func matchesAnyPattern(line string, patterns []*regexp.Regexp) bool {
// Get the full path including directory
parts := strings.Fields(line)
if len(parts) < 2 {
return false
}
fullPath := parts[1]
fmt.Printf("Checking filename: %s against patterns\n", fullPath) // Debug print
for _, pattern := range patterns {
if pattern.MatchString(fullPath) {
fmt.Printf("Pattern matched: %s in filename: %s\n", pattern.String(), fullPath) // Debug print
return true
}
}
return false
}

286
main.go
View File

@ -1,9 +1,13 @@
package main package main
import ( import (
"bufio"
"crypto/md5" "crypto/md5"
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -15,7 +19,8 @@ import (
type ContainerInfo struct { type ContainerInfo struct {
ID string ID string
Name string Name string
Interval time.Duration CName string
Interval string
Ignores []string Ignores []string
} }
@ -24,10 +29,14 @@ var notifiedChanges = struct {
changes map[string]string changes map[string]string
}{changes: make(map[string]string)} }{changes: make(map[string]string)}
var watchedContainers = struct { var apiAddress string
sync.RWMutex
containers map[string]ContainerInfo func init() {
}{containers: make(map[string]ContainerInfo)} apiAddress = os.Getenv("API_ADDRESS")
if apiAddress == "" {
apiAddress = "http://localhost:8080"
}
}
func main() { func main() {
// Check if GLITCHTIP_DSN environment variable is set // Check if GLITCHTIP_DSN environment variable is set
@ -38,132 +47,90 @@ func main() {
log.Println("Starting Oculus...") log.Println("Starting Oculus...")
go monitorContainerList() // Ensure the diffs directory exists
err := ensureDiffsDirectory()
if err != nil {
log.Fatalf("Error creating diffs directory: %v", err)
}
for { for {
containers, err := getContainers() containers, err := fetchContainers()
if err != nil { if err != nil {
log.Printf("Error listing containers: %v", err) log.Printf("Error fetching containers: %v", err)
time.Sleep(1 * time.Minute) time.Sleep(1 * time.Minute)
continue continue
} }
var wg sync.WaitGroup
for _, container := range containers { for _, container := range containers {
watchedContainers.RLock() wg.Add(1)
_, watched := watchedContainers.containers[container.ID] go func(container ContainerInfo) {
watchedContainers.RUnlock() defer wg.Done()
checkAndNotify(container)
if !watched { }(container)
log.Printf("Monitoring container: %s (%s) every %s", container.Name, container.ID, container.Interval)
watchedContainers.Lock()
watchedContainers.containers[container.ID] = container
watchedContainers.Unlock()
go monitorContainer(container, true)
}
} }
wg.Wait()
cleanupHashes(containers)
// Sleep for 1 minute before checking for new containers again // Sleep for 1 minute before checking for new containers again
time.Sleep(1 * time.Minute) time.Sleep(1 * time.Minute)
} }
} }
func getContainers() ([]ContainerInfo, error) { func fetchContainers() ([]ContainerInfo, error) {
log.Println("Fetching container list...") resp, err := http.Get(fmt.Sprintf("%s/containers", apiAddress))
output, err := exec.Command("docker", "ps", "--format", "{{.ID}} {{.Label \"oculus.enable\"}} {{.Label \"oculus.interval\"}} {{.Label \"oculus.cname\"}} {{.Label \"oculus.ignores\"}}").Output()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
lines := strings.Split(string(output), "\n") if resp.StatusCode != http.StatusOK {
var containers []ContainerInfo return nil, fmt.Errorf("error fetching containers: %s", resp.Status)
}
for _, line := range lines {
if line == "" { var containers []ContainerInfo
continue if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil {
} return nil, err
parts := strings.Fields(line)
if len(parts) < 2 || parts[1] == "" {
continue
}
id := parts[0]
intervalStr := "300s"
if len(parts) > 2 && parts[2] != "" {
intervalStr = parts[2]
}
interval, err := time.ParseDuration(intervalStr)
if err != nil {
log.Printf("Invalid interval format for container %s: %v", id, err)
continue
}
cname := id
if len(parts) > 3 && parts[3] != "" {
cname = parts[3]
}
ignores := []string{}
if len(parts) > 4 && parts[4] != "" {
ignores = strings.Split(parts[4], ",")
}
log.Printf("Container ID: %s, Name: %s, Interval: %s, Ignores: %v", id, cname, interval, ignores)
containers = append(containers, ContainerInfo{
ID: id,
Name: cname,
Interval: interval,
Ignores: ignores,
})
} }
log.Printf("Found %d containers to monitor.", len(containers))
return containers, nil return containers, nil
} }
func monitorContainer(container ContainerInfo, initial bool) { func fetchDiffOutput(containerID string) (string, error) {
ticker := time.NewTicker(container.Interval) resp, err := http.Get(fmt.Sprintf("%s/diff?id=%s", apiAddress, containerID))
defer ticker.Stop()
for {
select {
case <-ticker.C:
log.Printf("Checking diffs for container: %s (%s)", container.Name, container.ID)
diffOutput, err := getDiffOutput(container.ID)
if err != nil {
log.Printf("Error getting diffs for container %s (%s): %v", container.Name, container.ID, err)
return
}
filteredOutput, err := filterDiffOutput(diffOutput, container.Ignores)
if err != nil {
log.Printf("Error filtering diffs for container %s (%s): %v", container.Name, container.ID, err)
return
}
log.Printf("Filtered diff output for container %s: %s", container.Name, filteredOutput)
err = handleDiffOutput(container, filteredOutput, initial)
if err != nil {
log.Printf("Error handling diff output for container %s (%s): %v", container.Name, container.ID, err)
return
}
initial = false
}
}
}
func getDiffOutput(containerID string) (string, error) {
cmd := exec.Command("docker", "diff", containerID)
output, err := cmd.Output()
if err != nil { if err != nil {
return "", fmt.Errorf("docker diff failed: %w", err) return "", err
} }
return string(output), nil defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("error fetching diff: %s", resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
} }
func handleDiffOutput(container ContainerInfo, filteredOutput string, initial bool) error { func checkAndNotify(container ContainerInfo) {
diffOutput, err := fetchDiffOutput(container.ID)
if err != nil {
log.Printf("Error getting diffs for container %s (%s): %v", container.Name, container.ID, err)
return
}
filteredOutput, err := filterDiffOutput(diffOutput, container.CName, container.Ignores)
if err != nil {
log.Printf("Error filtering diffs for container %s (%s): %v", container.Name, container.ID, err)
return
}
log.Printf("Filtered diff output for container %s: %s", container.Name, filteredOutput)
if filteredOutput != "" { if filteredOutput != "" {
// Calculate a hash of the filtered diff output
diffHash := fmt.Sprintf("%x", md5.Sum([]byte(filteredOutput))) diffHash := fmt.Sprintf("%x", md5.Sum([]byte(filteredOutput)))
log.Printf("Diff hash for container %s: %s", container.Name, diffHash) log.Printf("Diff hash for container %s: %s", container.Name, diffHash)
@ -171,23 +138,14 @@ func handleDiffOutput(container ContainerInfo, filteredOutput string, initial bo
lastNotifiedHash, notified := notifiedChanges.changes[container.ID] lastNotifiedHash, notified := notifiedChanges.changes[container.ID]
notifiedChanges.RUnlock() notifiedChanges.RUnlock()
if initial { if !notified || lastNotifiedHash != diffHash {
// For the initial check, just store the hash and don't send a notification filename := filepath.Join("diffs", fmt.Sprintf("%s.diff", container.CName))
notifiedChanges.Lock()
notifiedChanges.changes[container.ID] = diffHash
notifiedChanges.Unlock()
log.Printf("Initial check, storing hash for container: %s (%s)", container.Name, container.ID)
} else if !notified || lastNotifiedHash != diffHash {
log.Printf("Writing diff output for container: %s (%s)", container.Name, container.ID)
// Write diff output to a file
filename := fmt.Sprintf("%s.diff", container.Name)
err := writeToFile(filename, filteredOutput) err := writeToFile(filename, filteredOutput)
if err != nil { if err != nil {
return fmt.Errorf("error writing diff to file: %w", err) log.Printf("Error writing diff to file: %v", err)
return
} }
// Send notification using go-glitch
log.Printf("Sending notification for container: %s (%s)", container.Name, container.ID)
err = sendNotification(filteredOutput) err = sendNotification(filteredOutput)
if err != nil { if err != nil {
log.Printf("Error sending notification for container %s: %v", container.ID, err) log.Printf("Error sending notification for container %s: %v", container.ID, err)
@ -203,47 +161,55 @@ func handleDiffOutput(container ContainerInfo, filteredOutput string, initial bo
} else { } else {
log.Printf("No significant changes detected for container: %s (%s)", container.Name, container.ID) log.Printf("No significant changes detected for container: %s (%s)", container.Name, container.ID)
} }
return nil
} }
func filterDiffOutput(diffOutput string, ignores []string) (string, error) { func filterDiffOutput(diffOutput, cname string, ignores []string) (string, error) {
tempFile, err := os.CreateTemp("", "diff_output") filename := filepath.Join("diffs", fmt.Sprintf("%s.diff", cname))
if err != nil {
return "", err
}
defer os.Remove(tempFile.Name())
_, err = tempFile.WriteString(diffOutput) // Write the diff output to the file
err := ioutil.WriteFile(filename, []byte(diffOutput), 0644)
if err != nil { if err != nil {
return "", err return "", fmt.Errorf("error writing diff to file: %s", err)
} }
tempFile.Close()
for _, ignore := range ignores { // Read and filter the diff output
cmd := exec.Command("sed", "-i", fmt.Sprintf("/%s/d", ignore), tempFile.Name()) var filteredLines []string
err := cmd.Run() file, err := os.Open(filename)
if err != nil { if err != nil {
return "", fmt.Errorf("error running sed command: %w", err) return "", fmt.Errorf("error opening diff file: %s", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
ignore := false
for _, pattern := range ignores {
match, err := filepath.Match(pattern, line)
if err != nil {
return "", fmt.Errorf("error matching pattern: %s", err)
}
if match {
ignore = true
break
}
}
if !ignore {
filteredLines = append(filteredLines, line)
} }
} }
filteredOutput, err := os.ReadFile(tempFile.Name()) if err := scanner.Err(); err != nil {
if err != nil { return "", fmt.Errorf("error reading diff file: %s", err)
return "", err
} }
return string(filteredOutput), nil filteredOutput := strings.Join(filteredLines, "\n")
} err = ioutil.WriteFile(filename, []byte(filteredOutput), 0644)
func matchPath(path, pattern string) bool {
match, err := filepath.Match(pattern, filepath.Base(path))
if err != nil { if err != nil {
log.Printf("Error matching pattern: %s with path: %s, error: %v", pattern, path, err) return "", fmt.Errorf("error writing filtered diff to file: %s", err)
return false
} }
log.Printf("Matching path %s with pattern %s: %v", path, pattern, match)
return match return filteredOutput, nil
} }
func writeToFile(filename, content string) error { func writeToFile(filename, content string) error {
@ -268,26 +234,22 @@ func sendNotification(content string) error {
return nil return nil
} }
func monitorContainerList() { func ensureDiffsDirectory() error {
for { return os.MkdirAll("./diffs", os.ModePerm)
watchedContainers.Lock()
for id, container := watchedContainers.containers {
if !isContainerRunning(id) {
log.Printf("Removing stopped container from watch list: %s (%s)", container.Name, id)
delete(watchedContainers.containers, id)
}
}
watchedContainers.Unlock()
time.Sleep(1 * time.Minute)
}
} }
func isContainerRunning(containerID string) bool { func cleanupHashes(containers []ContainerInfo) {
cmd := exec.Command("docker", "inspect", "--format", "{{.State.Running}}", containerID) notifiedChanges.Lock()
output, err := cmd.Output() defer notifiedChanges.Unlock()
if err != nil {
log.Printf("Error inspecting container %s: %v", containerID, err) activeContainers := make(map[string]bool)
return false for _, container := range containers {
activeContainers[container.ID] = true
}
for id := range notifiedChanges.changes {
if !activeContainers[id] {
delete(notifiedChanges.changes, id)
}
} }
return strings.TrimSpace(string(output)) == "true"
} }