261 lines
5.9 KiB
Go
261 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/client"
|
|
)
|
|
|
|
const (
|
|
notifyScript = "/notify.sh"
|
|
logDir = "/log"
|
|
)
|
|
|
|
type ContainerDiff struct {
|
|
ID string
|
|
Image string
|
|
Labels map[string]string
|
|
}
|
|
|
|
type FileChange struct {
|
|
Path string
|
|
Kind string
|
|
}
|
|
|
|
func getRunningContainers(cli *client.Client) ([]ContainerDiff, error) {
|
|
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var diffs []ContainerDiff
|
|
for _, container := range containers {
|
|
if _, ok := container.Labels["oculus.cname"]; ok {
|
|
diffs = append(diffs, ContainerDiff{
|
|
ID: container.ID,
|
|
Image: container.Image,
|
|
Labels: container.Labels,
|
|
})
|
|
}
|
|
}
|
|
|
|
return diffs, nil
|
|
}
|
|
|
|
func saveDiffs(diffs []FileChange, filePath string) error {
|
|
data, err := json.Marshal(diffs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(filePath, data, 0644)
|
|
}
|
|
|
|
func loadDiffs(filePath string) ([]FileChange, error) {
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var diffs []FileChange
|
|
err = json.Unmarshal(data, &diffs)
|
|
return diffs, err
|
|
}
|
|
|
|
func shouldIgnore(change string, ignoreList []string) bool {
|
|
for _, ignore := range ignoreList {
|
|
if strings.Contains(change, ignore) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func logDetection(containerID, message string) {
|
|
logFilePath := filepath.Join(logDir, fmt.Sprintf("%s.log", containerID))
|
|
f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
log.Printf("Error opening log file: %v", err)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
log.SetOutput(f)
|
|
log.Println(message)
|
|
sendToGlitchtip(logFilePath)
|
|
}
|
|
|
|
func sendToGlitchtip(logFilePath string) {
|
|
if dsn := os.Getenv("GLITCHTIP_DSN"); dsn != "" {
|
|
cmd := exec.Command("go-glitch", logFilePath)
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
log.Printf("Error sending to Go Glitch: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func compareContainers(cli *client.Client, mode string) {
|
|
currentDiffs, err := getRunningContainers(cli)
|
|
if err != nil {
|
|
log.Fatalf("Error getting current containers: %v", err)
|
|
}
|
|
|
|
for _, current := range currentDiffs {
|
|
if current.Labels["oculus.mode"] == mode {
|
|
filePath := filepath.Join(logDir, fmt.Sprintf("%s.json", current.Labels["oculus.cname"]))
|
|
savedDiffs, err := loadDiffs(filePath)
|
|
if err != nil {
|
|
log.Printf("Error loading saved diffs for %s: %v", current.ID, err)
|
|
savedDiffs = []FileChange{} // Initialize to an empty slice
|
|
}
|
|
|
|
ignoreList := strings.Split(current.Labels["oculus.ignorelist"], ",")
|
|
|
|
currentChanges, err := cli.ContainerDiff(context.Background(), current.ID)
|
|
if err != nil {
|
|
log.Printf("Error getting container diff for %s: %v", current.ID, err)
|
|
continue
|
|
}
|
|
|
|
var changes []FileChange
|
|
for _, change := range currentChanges {
|
|
path := change.Path
|
|
kind := ""
|
|
switch change.Kind {
|
|
case 0:
|
|
kind = "Modified"
|
|
case 1:
|
|
kind = "Added"
|
|
case 2:
|
|
kind = "Deleted"
|
|
}
|
|
|
|
if !shouldIgnore(path, ignoreList) {
|
|
changes = append(changes, FileChange{
|
|
Path: path,
|
|
Kind: kind,
|
|
})
|
|
}
|
|
}
|
|
|
|
if mode == "monitor" {
|
|
err = saveDiffs(changes, filePath)
|
|
if err != nil {
|
|
log.Fatalf("Error saving diffs: %v", err)
|
|
}
|
|
} else if mode == "report" {
|
|
for _, change := range changes {
|
|
message := fmt.Sprintf("Container %s changed: %s %s", current.ID, change.Kind, change.Path)
|
|
logDetection(current.Labels["oculus.cname"], message)
|
|
runNotifyScript(filepath.Join(logDir, fmt.Sprintf("%s.log", current.Labels["oculus.cname"])))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func runNotifyScript(logFilePath string) {
|
|
cmd := exec.Command(notifyScript, logFilePath)
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
log.Printf("Error running notify script: %v", err)
|
|
}
|
|
}
|
|
|
|
func watchDockerEvents(cli *client.Client) {
|
|
eventFilter := filters.NewArgs()
|
|
eventFilter.Add("type", "container")
|
|
eventFilter.Add("event", "start")
|
|
eventFilter.Add("event", "stop")
|
|
eventFilter.Add("event", "destroy")
|
|
|
|
messages, errs := cli.Events(context.Background(), types.EventsOptions{
|
|
Filters: eventFilter,
|
|
})
|
|
|
|
for {
|
|
select {
|
|
case event := <-messages:
|
|
log.Printf("Docker event received: %s for container %s", event.Action, event.ID)
|
|
compareContainers(cli, "monitor")
|
|
case err := <-errs:
|
|
log.Printf("Error watching Docker events: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func monitorContainers(cli *client.Client, interval time.Duration) {
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
compareContainers(cli, "monitor")
|
|
}
|
|
}
|
|
}
|
|
|
|
func reportContainers(cli *client.Client, interval time.Duration) {
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
compareContainers(cli, "report")
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
if err != nil {
|
|
log.Fatalf("Error creating Docker client: %v", err)
|
|
}
|
|
|
|
go watchDockerEvents(cli)
|
|
|
|
// Get running containers and set up monitoring intervals
|
|
containers, err := getRunningContainers(cli)
|
|
if err != nil {
|
|
log.Fatalf("Error getting running containers: %v", err)
|
|
}
|
|
|
|
for _, container := range containers {
|
|
if mode, ok := container.Labels["oculus.mode"]; ok {
|
|
intervalStr := container.Labels["oculus.interval"]
|
|
interval, err := time.ParseDuration(intervalStr)
|
|
if err != nil {
|
|
log.Fatalf("Invalid interval format for container %s: %v", container.ID, err)
|
|
}
|
|
|
|
if mode == "monitor" {
|
|
go monitorContainers(cli, interval)
|
|
} else if mode == "report" {
|
|
go reportContainers(cli, interval)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Graceful shutdown handling
|
|
sigs := make(chan os.Signal, 1)
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
<-sigs
|
|
log.Println("Shutting down...")
|
|
}
|