Oculus/main.go

185 lines
4.5 KiB
Go

package main
import (
"crypto/md5"
"fmt"
"log"
"os"
"os/exec"
"strings"
"sync"
"time"
)
type ContainerInfo struct {
ID string
Name string
Interval time.Duration
Ignores []string
}
var notifiedChanges = struct {
sync.RWMutex
changes map[string]string
}{changes: make(map[string]string)}
func main() {
// Check if GLITCHTIP_DSN environment variable is set
glitchtipDSN := os.Getenv("GLITCHTIP_DSN")
if glitchtipDSN == "" {
log.Fatal("GLITCHTIP_DSN environment variable is not set")
}
log.Println("Starting Oculus...")
for {
containers, err := getContainers()
if err != nil {
log.Printf("Error listing containers: %v", err)
time.Sleep(1 * time.Minute)
continue
}
for _, container := range containers {
log.Printf("Monitoring container: %s (%s) every %s", container.Name, container.ID, container.Interval)
go monitorContainer(container)
}
// Sleep for 1 minute before checking for new containers again
time.Sleep(1 * time.Minute)
}
}
func getContainers() ([]ContainerInfo, error) {
log.Println("Fetching container list...")
output, err := exec.Command("docker", "ps", "--format", "{{.ID}} {{.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) < 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], ",")
}
containers = append(containers, ContainerInfo{
ID: id,
Name: cname,
Interval: interval,
Ignores: ignores,
})
}
log.Printf("Found %d containers to monitor.", len(containers))
return containers, nil
}
func monitorContainer(container ContainerInfo) {
for {
log.Printf("Checking diffs for container: %s (%s)", container.Name, container.ID)
checkDiff(container)
time.Sleep(container.Interval)
}
}
func checkDiff(container ContainerInfo) {
cmd := exec.Command("docker", "diff", container.ID)
output, err := cmd.Output()
if err != nil {
log.Printf("Error running docker diff for container %s: %v", container.ID, err)
return
}
diffOutput := string(output)
for _, ignore := range container.Ignores {
diffOutput = removeIgnoredPaths(diffOutput, ignore)
}
if diffOutput != "" {
diffHash := fmt.Sprintf("%x", md5.Sum([]byte(diffOutput)))
notifiedChanges.RLock()
lastNotifiedHash, notified := notifiedChanges.changes[container.ID]
notifiedChanges.RUnlock()
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, diffOutput)
if err != nil {
log.Printf("Error writing diff to file for container %s: %v", container.ID, err)
}
// Send notification using go-glitch
log.Printf("Sending notification for container: %s (%s)", container.Name, container.ID)
cmd = exec.Command("go-glitch")
cmd.Stdin = strings.NewReader(diffOutput)
output, err = cmd.CombinedOutput()
if err != nil {
log.Printf("Error sending notification for container %s: %v", container.ID, err)
log.Printf("go-glitch output: %s", output)
} else {
notifiedChanges.Lock()
notifiedChanges.changes[container.ID] = diffHash
notifiedChanges.Unlock()
}
} else {
log.Printf("No new changes detected for container: %s (%s)", container.Name, container.ID)
}
} else {
log.Printf("No significant changes detected for container: %s (%s)", container.Name, container.ID)
}
}
func removeIgnoredPaths(diffOutput string, ignore string) string {
lines := strings.Split(diffOutput, "\n")
filteredLines := []string{}
for _, line := range lines {
if !strings.Contains(line, ignore) {
filteredLines = append(filteredLines, line)
}
}
return strings.Join(filteredLines, "\n")
}
func writeToFile(filename, content string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(content)
return err
}