1package agent 2 3import ( 4 "fmt" 5 "log" 6 "os" 7 "strings" 8 "sync" 9 10 "github.com/hashicorp/serf/serf" 11) 12 13// EventHandler is a handler that does things when events happen. 14type EventHandler interface { 15 HandleEvent(serf.Event) 16} 17 18// ScriptEventHandler invokes scripts for the events that it receives. 19type ScriptEventHandler struct { 20 SelfFunc func() serf.Member 21 Scripts []EventScript 22 Logger *log.Logger 23 24 scriptLock sync.Mutex 25 newScripts []EventScript 26} 27 28func (h *ScriptEventHandler) HandleEvent(e serf.Event) { 29 // Swap in the new scripts if any 30 h.scriptLock.Lock() 31 if h.newScripts != nil { 32 h.Scripts = h.newScripts 33 h.newScripts = nil 34 } 35 h.scriptLock.Unlock() 36 37 if h.Logger == nil { 38 h.Logger = log.New(os.Stderr, "", log.LstdFlags) 39 } 40 41 self := h.SelfFunc() 42 for _, script := range h.Scripts { 43 if !script.Invoke(e) { 44 continue 45 } 46 47 err := invokeEventScript(h.Logger, script.Script, self, e) 48 if err != nil { 49 h.Logger.Printf("[ERR] agent: Error invoking script '%s': %s", 50 script.Script, err) 51 } 52 } 53} 54 55// UpdateScripts is used to safely update the scripts we invoke in 56// a thread safe manner 57func (h *ScriptEventHandler) UpdateScripts(scripts []EventScript) { 58 h.scriptLock.Lock() 59 defer h.scriptLock.Unlock() 60 h.newScripts = scripts 61} 62 63// EventFilter is used to filter which events are processed 64type EventFilter struct { 65 Event string 66 Name string 67} 68 69// Invoke tests whether or not this event script should be invoked 70// for the given Serf event. 71func (s *EventFilter) Invoke(e serf.Event) bool { 72 if s.Event == "*" { 73 return true 74 } 75 76 if e.EventType().String() != s.Event { 77 return false 78 } 79 80 if s.Event == "user" && s.Name != "" { 81 userE, ok := e.(serf.UserEvent) 82 if !ok { 83 return false 84 } 85 86 if userE.Name != s.Name { 87 return false 88 } 89 } 90 91 if s.Event == "query" && s.Name != "" { 92 query, ok := e.(*serf.Query) 93 if !ok { 94 return false 95 } 96 97 if query.Name != s.Name { 98 return false 99 } 100 } 101 102 return true 103} 104 105// Valid checks if this is a valid agent event script. 106func (s *EventFilter) Valid() bool { 107 switch s.Event { 108 case "member-join": 109 case "member-leave": 110 case "member-failed": 111 case "member-update": 112 case "member-reap": 113 case "user": 114 case "query": 115 case "*": 116 default: 117 return false 118 } 119 return true 120} 121 122// EventScript is a single event script that will be executed in the 123// case of an event, and is configured from the command-line or from 124// a configuration file. 125type EventScript struct { 126 EventFilter 127 Script string 128} 129 130func (s *EventScript) String() string { 131 if s.Name != "" { 132 return fmt.Sprintf("Event '%s:%s' invoking '%s'", s.Event, s.Name, s.Script) 133 } 134 return fmt.Sprintf("Event '%s' invoking '%s'", s.Event, s.Script) 135} 136 137// ParseEventScript takes a string in the format of "type=script" and 138// parses it into an EventScript struct, if it can. 139func ParseEventScript(v string) []EventScript { 140 var filter, script string 141 parts := strings.SplitN(v, "=", 2) 142 if len(parts) == 1 { 143 script = parts[0] 144 } else { 145 filter = parts[0] 146 script = parts[1] 147 } 148 149 filters := ParseEventFilter(filter) 150 results := make([]EventScript, 0, len(filters)) 151 for _, filt := range filters { 152 result := EventScript{ 153 EventFilter: filt, 154 Script: script, 155 } 156 results = append(results, result) 157 } 158 return results 159} 160 161// ParseEventFilter a string with the event type filters and 162// parses it into a series of EventFilters if it can. 163func ParseEventFilter(v string) []EventFilter { 164 // No filter translates to stream all 165 if v == "" { 166 v = "*" 167 } 168 169 events := strings.Split(v, ",") 170 results := make([]EventFilter, 0, len(events)) 171 for _, event := range events { 172 var result EventFilter 173 var name string 174 175 if strings.HasPrefix(event, "user:") { 176 name = event[len("user:"):] 177 event = "user" 178 } else if strings.HasPrefix(event, "query:") { 179 name = event[len("query:"):] 180 event = "query" 181 } 182 183 result.Event = event 184 result.Name = name 185 results = append(results, result) 186 } 187 188 return results 189} 190