github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/aagent/watchers/watcher/watcher.go (about) 1 // Copyright (c) 2020-2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package watcher 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "os" 13 "sync" 14 "text/template" 15 "time" 16 17 "github.com/choria-io/go-choria/aagent/model" 18 "github.com/choria-io/go-choria/backoff" 19 "github.com/choria-io/go-choria/internal/util" 20 "github.com/choria-io/go-choria/lifecycle" 21 governor "github.com/choria-io/go-choria/providers/governor/streams" 22 "github.com/tidwall/gjson" 23 ) 24 25 type Watcher struct { 26 name string 27 wtype string 28 announceInterval time.Duration 29 statechg chan struct{} 30 activeStates []string 31 machine model.Machine 32 succEvent string 33 failEvent string 34 35 deleteCb func() 36 currentStateCb func() any 37 govCancel func() 38 39 mu sync.Mutex 40 } 41 42 func NewWatcher(name string, wtype string, announceInterval time.Duration, activeStates []string, machine model.Machine, fail string, success string) (*Watcher, error) { 43 if name == "" { 44 return nil, fmt.Errorf("name is required") 45 } 46 47 if wtype == "" { 48 return nil, fmt.Errorf("watcher type is required") 49 } 50 51 if machine == nil { 52 return nil, fmt.Errorf("machine is required") 53 } 54 55 w := &Watcher{ 56 name: name, 57 wtype: wtype, 58 announceInterval: announceInterval, 59 statechg: make(chan struct{}, 1), 60 failEvent: fail, 61 succEvent: success, 62 machine: machine, 63 activeStates: activeStates, 64 } 65 66 return w, nil 67 } 68 69 func (w *Watcher) FactsFile() (string, error) { 70 tf, err := os.CreateTemp("", "") 71 if err != nil { 72 return "", err 73 } 74 75 _, err = tf.Write(w.machine.Facts()) 76 if err != nil { 77 tf.Close() 78 os.Remove(tf.Name()) 79 return "", err 80 } 81 tf.Close() 82 83 return tf.Name(), nil 84 } 85 86 func (w *Watcher) DataCopyFile() (string, error) { 87 dat := w.machine.Data() 88 89 j, err := json.Marshal(dat) 90 if err != nil { 91 return "", err 92 } 93 94 tf, err := os.CreateTemp("", "") 95 if err != nil { 96 return "", err 97 } 98 99 _, err = tf.Write(j) 100 if err != nil { 101 tf.Close() 102 os.Remove(tf.Name()) 103 return "", err 104 } 105 tf.Close() 106 107 return tf.Name(), nil 108 } 109 110 func (w *Watcher) CancelGovernor() { 111 w.mu.Lock() 112 defer w.mu.Unlock() 113 114 if w.govCancel != nil { 115 w.govCancel() 116 } 117 } 118 119 func (w *Watcher) sendGovernorLC(t lifecycle.GovernorEventType, name string, seq uint64) { 120 w.machine.PublishLifecycleEvent(lifecycle.Governor, 121 lifecycle.Identity(w.machine.Identity()), 122 lifecycle.Component(w.machine.Name()), 123 lifecycle.GovernorType(t), 124 lifecycle.GovernorSequence(seq), 125 lifecycle.GovernorName(name)) 126 } 127 128 func (w *Watcher) EnterGovernor(ctx context.Context, name string, timeout time.Duration) (governor.Finisher, error) { 129 var err error 130 131 name, err = w.ProcessTemplate(name) 132 if err != nil { 133 return nil, fmt.Errorf("could not parse governor name template: %s", err) 134 } 135 136 w.Infof("Using governor %s", name) 137 138 mgr, err := w.machine.JetStreamConnection() 139 if err != nil { 140 return nil, fmt.Errorf("JetStream connection not set") 141 } 142 143 w.Infof("Obtaining a slot in the %s Governor with %v timeout", name, timeout) 144 subj := util.GovernorSubject(name, w.machine.MainCollective()) 145 gov := governor.New(name, mgr.NatsConn(), governor.WithLogger(w), governor.WithSubject(subj), governor.WithBackoff(backoff.FiveSec)) 146 147 var gCtx context.Context 148 w.mu.Lock() 149 gCtx, w.govCancel = context.WithTimeout(ctx, timeout) 150 w.mu.Unlock() 151 defer w.CancelGovernor() 152 153 fin, seq, err := gov.Start(gCtx, fmt.Sprintf("Auto Agent %s#%s @ %s", w.machine.Name(), w.name, w.machine.Identity())) 154 if err != nil { 155 w.Errorf("Could not obtain a slot in the Governor %s: %s", name, err) 156 w.sendGovernorLC(lifecycle.GovernorTimeoutEvent, name, 0) 157 return nil, err 158 } 159 160 w.sendGovernorLC(lifecycle.GovernorEnterEvent, name, seq) 161 162 finisher := func() error { 163 w.sendGovernorLC(lifecycle.GovernorExitEvent, name, seq) 164 return fin() 165 } 166 167 return finisher, nil 168 } 169 170 func (w *Watcher) ProcessTemplate(s string) (string, error) { 171 funcs, err := w.templateFuncMap() 172 if err != nil { 173 return "", err 174 } 175 176 t, err := template.New("machine").Funcs(funcs).Parse(s) 177 if err != nil { 178 return "", err 179 } 180 181 buf := bytes.NewBuffer([]byte{}) 182 183 err = t.Execute(buf, nil) 184 if err != nil { 185 return "", err 186 } 187 188 return buf.String(), nil 189 } 190 191 func (w *Watcher) templateFuncMap() (template.FuncMap, error) { 192 facts := w.machine.Facts() 193 data := w.machine.Data() 194 jdata, err := json.Marshal(data) 195 if err != nil { 196 return nil, err 197 } 198 199 input := map[string]json.RawMessage{ 200 "facts": facts, 201 "data": jdata, 202 } 203 204 jinput, err := json.Marshal(input) 205 if err != nil { 206 return nil, err 207 } 208 209 return util.FuncMap(map[string]any{ 210 "lookup": func(q string, dflt any) any { 211 r := gjson.GetBytes(jinput, q) 212 if !r.Exists() { 213 w.Infof("Query did not match any data, returning default: %s", q) 214 215 return dflt 216 } 217 218 return r.Value() 219 }, 220 }), nil 221 } 222 223 func (w *Watcher) Machine() model.Machine { 224 return w.machine 225 } 226 227 func (w *Watcher) SuccessEvent() string { 228 return w.succEvent 229 } 230 231 func (w *Watcher) FailEvent() string { 232 return w.failEvent 233 } 234 235 func (w *Watcher) StateChangeC() chan struct{} { 236 return w.statechg 237 } 238 239 func (w *Watcher) SetDeleteFunc(f func()) { 240 w.mu.Lock() 241 defer w.mu.Unlock() 242 243 w.deleteCb = f 244 } 245 246 func (w *Watcher) NotifyWatcherState(state any) { 247 w.machine.NotifyWatcherState(w.name, state) 248 } 249 250 func (w *Watcher) SuccessTransition() error { 251 if w.succEvent == "" { 252 return nil 253 } 254 255 w.Infof("success transitioning using %s event", w.succEvent) 256 return w.machine.Transition(w.succEvent) 257 } 258 259 func (w *Watcher) FailureTransition() error { 260 if w.failEvent == "" { 261 return nil 262 } 263 264 w.Infof("fail transitioning using %s event", w.failEvent) 265 return w.machine.Transition(w.failEvent) 266 } 267 268 func (w *Watcher) Transition(event string) error { 269 if event == "" { 270 return nil 271 } 272 273 return w.machine.Transition(event) 274 } 275 276 func (w *Watcher) NotifyStateChance() { 277 w.mu.Lock() 278 defer w.mu.Unlock() 279 280 select { 281 case w.statechg <- struct{}{}: 282 default: 283 } 284 } 285 286 func (w *Watcher) CurrentState() any { 287 if w.currentStateCb != nil { 288 return w.currentStateCb() 289 } 290 291 return nil 292 } 293 294 func (w *Watcher) AnnounceInterval() time.Duration { 295 return w.announceInterval 296 } 297 298 func (w *Watcher) Type() string { 299 return w.wtype 300 } 301 302 func (w *Watcher) Name() string { 303 return w.name 304 } 305 306 func (w *Watcher) Delete() { 307 w.mu.Lock() 308 defer w.mu.Unlock() 309 310 if w.deleteCb != nil { 311 w.deleteCb() 312 } 313 } 314 315 func (w *Watcher) ShouldWatch() bool { 316 if len(w.activeStates) == 0 { 317 return true 318 } 319 320 for _, e := range w.activeStates { 321 if e == w.machine.State() { 322 return true 323 } 324 } 325 326 return false 327 } 328 329 func (w *Watcher) Debugf(format string, args ...any) { 330 w.machine.Debugf(w.name, format, args...) 331 } 332 333 func (w *Watcher) Infof(format string, args ...any) { 334 w.machine.Infof(w.name, format, args...) 335 } 336 337 func (w *Watcher) Warnf(format string, args ...any) { 338 w.machine.Warnf(w.name, format, args...) 339 } 340 341 func (w *Watcher) Errorf(format string, args ...any) { 342 w.machine.Errorf(w.name, format, args...) 343 }