diff --git a/events/event.go b/events/event.go index c826d42..bd91705 100644 --- a/events/event.go +++ b/events/event.go @@ -17,6 +17,8 @@ func Make(message dockerEvents.Message, client *client.Client) Event { return makeContainer(message, client) case "network": return makeNetwork(message, client) + case "volume": + return makeVolume(message, client) default: log.Printf("unknown event type %v", message.Type) return nil diff --git a/events/volume.go b/events/volume.go new file mode 100644 index 0000000..11796de --- /dev/null +++ b/events/volume.go @@ -0,0 +1,56 @@ +package events + +import ( + "context" + "github.com/docker/docker/api/types" + dockerEvents "github.com/docker/docker/api/types/events" + "github.com/docker/docker/client" + "log" +) + +type Volume struct { + Action string + ID string + Destination *string + Volume *types.Volume + Container *types.ContainerJSON +} + +//goland:noinspection ALL +func (c Volume) __interface_event() { + panic("interface event guard") +} + +func makeVolume(message dockerEvents.Message, client *client.Client) Event { + var e Volume + e.ID = message.Actor.ID + e.Action = message.Action + + if message.Action != "destroy" { + volume, err := client.VolumeInspect(context.TODO(), message.Actor.ID) + if err != nil { + log.Printf("error inspecting volume %v: %v", message.Actor.ID, err) + } else { + e.Volume = &volume + } + } + + if message.Action == "mount" || message.Action == "unmount" { + if containerId, ok := message.Actor.Attributes["container"]; ok { + container, err := client.ContainerInspect(context.TODO(), containerId) + if err != nil { + log.Printf("error inspecting container %v: %v", containerId, err) + } else { + e.Container = &container + } + } + } + + if message.Action == "mount" { + if dest, ok := message.Actor.Attributes["destination"]; ok { + e.Destination = &dest + } + } + + return e +} diff --git a/example.conf b/example.conf index 0ceb68f..1641936 100644 --- a/example.conf +++ b/example.conf @@ -1,12 +1,9 @@ - [Container] -Action=start -Run=notify-send "{{ .Container.Name }}" +Run=notify-send "container {{ .Action }}: {{ .ID }}" [Network] -Action=connect -Run=notify-send "connected container {{ .Container.Name }} to network {{ .Network.Name }}" +Run=notify-send "network {{ .Action }} {{ .ID }}" -[Network] -Action=disconnect -Run=notify-send "disconnected container {{ .Container.Name }} from network {{ .Network.Name }}" +[Volume] +Name=foo* +Run=notify-send "volume {{ .Action }} {{ .ID }}" diff --git a/handlers/container.go b/handlers/container.go index d66bb7a..9c6784f 100644 --- a/handlers/container.go +++ b/handlers/container.go @@ -65,41 +65,43 @@ func readContainerFromConfig(section *ini.Section) (Container, error) { var err error handler.Label = make(map[string]*string) for _, key := range section.Keys() { - val := key.String() + values := key.ValueWithShadows() switch key.Name() { case "Action": - handler.Action = append(handler.Action, val) + handler.Action = values break case "Name": - handler.Name = append(handler.Name, val) + handler.Name = values break case "Image": - handler.Image = append(handler.Image, val) + handler.Image = values break case "ID": - handler.ID = append(handler.ID, val) + handler.ID = values break case "Hostname": - handler.Hostname = append(handler.Hostname, val) + handler.Hostname = values break case "Label": - parts := strings.SplitN(val, "=", 2) - var k string - var v *string - if len(parts) == 2 { - k = parts[0] - v = &parts[1] - } else { - k = parts[0] - v = nil + for _, val := range values { + parts := strings.SplitN(val, "=", 2) + var k string + var v *string + if len(parts) == 2 { + k = parts[0] + v = &parts[1] + } else { + k = parts[0] + v = nil + } + handler.Label[k] = v } - handler.Label[k] = v break case "Run": - if handler.Run != nil { + if len(values) > 1 { return handler, errors.New(fmt.Sprintf("duplicate key %v in section %v", key.Name(), section.Name())) } - handler.Run, err = template.New("Run").Parse(val) + handler.Run, err = template.New("Run").Parse(values[0]) if err != nil { return handler, err } diff --git a/handlers/handler.go b/handlers/handler.go index 28691c2..8138a13 100644 --- a/handlers/handler.go +++ b/handlers/handler.go @@ -18,6 +18,8 @@ func ReadFromConfig(section *ini.Section) (Handler, error) { return readContainerFromConfig(section) case "Network": return readNetworkFromConfig(section) + case "Volume": + return readVolumeFromConfig(section) default: return nil, errors.New(fmt.Sprintf("unknown section %v", section.Name())) } diff --git a/handlers/network.go b/handlers/network.go index 359121d..2e857a8 100644 --- a/handlers/network.go +++ b/handlers/network.go @@ -89,64 +89,68 @@ func readNetworkFromConfig(section *ini.Section) (Network, error) { handler.Label = make(map[string]*string) handler.ContainerLabel = make(map[string]*string) for _, key := range section.Keys() { - val := key.String() + values := key.ValueWithShadows() switch key.Name() { case "Action": - handler.Action = append(handler.Action, val) + handler.Action = values break case "Name": - handler.Name = append(handler.Name, val) + handler.Name = values break case "ID": - handler.ID = append(handler.ID, val) + handler.ID = values break case "Driver": - handler.Driver = append(handler.Driver, val) + handler.Driver = values break case "Label": - parts := strings.SplitN(val, "=", 2) - var k string - var v *string - if len(parts) == 2 { - k = parts[0] - v = &parts[1] - } else { - k = parts[0] - v = nil + for _, val := range values { + parts := strings.SplitN(val, "=", 2) + var k string + var v *string + if len(parts) == 2 { + k = parts[0] + v = &parts[1] + } else { + k = parts[0] + v = nil + } + handler.Label[k] = v } - handler.Label[k] = v break case "ContainerName": - handler.ContainerName = append(handler.ContainerName, val) + handler.ContainerName = values break case "ContainerID": - handler.ContainerID = append(handler.ContainerID, val) + handler.ContainerID = values break case "ContainerImage": - handler.ContainerImage = append(handler.ContainerImage, val) + handler.ContainerImage = values break case "ContainerHostname": - handler.ContainerHostname = append(handler.ContainerHostname, val) + handler.ContainerHostname = values break case "ContainerLabel": - parts := strings.SplitN(val, "=", 2) - var k string - var v *string - if len(parts) == 2 { - k = parts[0] - v = &parts[1] - } else { - k = parts[0] - v = nil + for _, val := range values { + parts := strings.SplitN(val, "=", 2) + var k string + var v *string + if len(parts) == 2 { + k = parts[0] + v = &parts[1] + } else { + k = parts[0] + v = nil + } + handler.ContainerLabel[k] = v } - handler.ContainerLabel[k] = v break case "Run": - if handler.Run != nil { + if len(values) > 1 { return handler, errors.New(fmt.Sprintf("duplicate key %v in section %v", key.Name(), section.Name())) } - handler.Run, err = template.New("Run").Parse(val) + handler.Run, err = template.New("Run").Parse(values[0]) if err != nil { return handler, err } diff --git a/handlers/volume.go b/handlers/volume.go new file mode 100644 index 0000000..f9f39a4 --- /dev/null +++ b/handlers/volume.go @@ -0,0 +1,177 @@ +package handlers + +import ( + "docker-event-handler/events" + "errors" + "fmt" + "gopkg.in/ini.v1" + "strings" + "text/template" +) + +type Volume struct { + Action []string + Name []string + Driver []string + Destination []string + ID []string + Label map[string]*string + ContainerName []string + ContainerImage []string + ContainerID []string + ContainerHostname []string + ContainerLabel map[string]*string + Run *template.Template +} + +func (h Volume) Matches(event events.Event) bool { + e, ok := event.(events.Volume) + if !ok { + return false + } + + if !stringListMatches(h.Action, e.Action) { + return false + } + + if !stringListMatches(h.ID, e.ID) { + return false + } + + if e.Volume == nil { + if len(h.Name) > 0 || len(h.Driver) > 0 || len(h.Label) > 0 { + return false + } + } else { + if !stringListMatches(h.Name, e.Volume.Name) { + return false + } + if !stringListMatches(h.Driver, e.Volume.Driver) { + return false + } + if !labelsMatch(h.Label, e.Volume.Labels) { + return false + } + } + + if e.Destination == nil { + if len(h.Destination) > 0 { + return false + } + } else { + if !stringListMatches(h.Destination, *e.Destination) { + return false + } + } + + if e.Container == nil { + if len(h.ContainerID) > 0 || len(h.ContainerName) > 0 || len(h.ContainerImage) > 0 || len(h.ContainerHostname) > 0 || len(h.ContainerLabel) > 0 { + return false + } + } else { + if !stringListMatches(h.ContainerID, e.Container.ID) { + return false + } + if !stringListMatches(h.ContainerName, e.Container.Name) { + return false + } + if !stringListMatches(h.ContainerImage, e.Container.Image) { + return false + } + if !stringListMatches(h.ContainerHostname, e.Container.Config.Hostname) { + return false + } + if !labelsMatch(h.ContainerLabel, e.Container.Config.Labels) { + return false + } + } + + return true +} + +func (h Volume) Invoke(event events.Event) error { + e := event.(events.Volume) // enforce that the event is a volume event + return runTemplatedScript(h.Run, e) +} + +func readVolumeFromConfig(section *ini.Section) (Volume, error) { + var handler Volume + var err error + handler.Label = make(map[string]*string) + handler.ContainerLabel = make(map[string]*string) + for _, key := range section.Keys() { + values := key.ValueWithShadows() + switch key.Name() { + case "Action": + handler.Action = values + break + case "Name": + handler.Name = values + break + case "ID": + handler.ID = values + break + case "Driver": + handler.Driver = values + break + case "Destination": + handler.Destination = values + break + case "Label": + for _, val := range values { + parts := strings.SplitN(val, "=", 2) + var k string + var v *string + if len(parts) == 2 { + k = parts[0] + v = &parts[1] + } else { + k = parts[0] + v = nil + } + handler.Label[k] = v + } + break + + case "ContainerName": + handler.ContainerName = values + break + case "ContainerID": + handler.ContainerID = values + break + case "ContainerImage": + handler.ContainerImage = values + break + case "ContainerHostname": + handler.ContainerHostname = values + break + case "ContainerLabel": + for _, val := range values { + parts := strings.SplitN(val, "=", 2) + var k string + var v *string + if len(parts) == 2 { + k = parts[0] + v = &parts[1] + } else { + k = parts[0] + v = nil + } + handler.ContainerLabel[k] = v + } + break + case "Run": + if len(values) > 1 { + return handler, errors.New(fmt.Sprintf("duplicate key %v in section %v", key.Name(), section.Name())) + } + handler.Run, err = template.New("Run").Parse(values[0]) + if err != nil { + return handler, err + } + break + default: + return handler, errors.New(fmt.Sprintf("unknown key %v in section %v", key.Name(), section.Name())) + } + } + return handler, nil +} diff --git a/main.go b/main.go index 579820d..905d83f 100644 --- a/main.go +++ b/main.go @@ -87,6 +87,7 @@ func main() { Filters: filters.NewArgs( filters.Arg("type", dockerEvents.ContainerEventType), filters.Arg("type", dockerEvents.NetworkEventType), + filters.Arg("type", dockerEvents.VolumeEventType), ), })