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