go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/_motor/providers/os/events/watcher.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package events 5 6 import ( 7 "errors" 8 "sync" 9 "time" 10 11 "go.mondoo.com/cnquery/motor/providers/os" 12 13 "github.com/rs/zerolog/log" 14 "go.mondoo.com/cnquery/motor/providers" 15 ) 16 17 // Subscriptions is a map to store all watcher subscriptions 18 type Subscriptions struct{ sync.Map } 19 20 // Store a new subscription 21 func (c *Subscriptions) Store(k string, v *WatcherSubscription) { 22 c.Map.Store(k, v) 23 } 24 25 // Load a subscription 26 func (c *Subscriptions) Load(k string) (*WatcherSubscription, bool) { 27 res, ok := c.Map.Load(k) 28 if !ok { 29 return nil, ok 30 } 31 return res.(*WatcherSubscription), ok 32 } 33 34 func (c *Subscriptions) Delete(k string) { 35 c.Map.Delete(k) 36 } 37 38 func (c *Subscriptions) Range(f func(string, *WatcherSubscription) bool) { 39 c.Map.Range(func(key interface{}, value interface{}) bool { 40 return f(key.(string), value.(*WatcherSubscription)) 41 }) 42 } 43 44 type Watcher struct { 45 provider os.OperatingSystemProvider 46 subscriptions *Subscriptions 47 jm *JobManager 48 SleepDuration time.Duration 49 } 50 51 type WatcherSubscription struct { 52 typ string 53 observable func(providers.Observable) 54 } 55 56 func NewWatcher(provider os.OperatingSystemProvider) *Watcher { 57 w := &Watcher{provider: provider, subscriptions: &Subscriptions{}} 58 w.provider = provider 59 w.jm = NewJobManager(provider) 60 w.SleepDuration = time.Duration(10 * time.Second) 61 return w 62 } 63 64 // the internal unique id is a combination of the typ + id 65 func (w *Watcher) subscriberId(typ string, id string) string { 66 sid := typ + id 67 return sid 68 } 69 70 func (w *Watcher) Subscribe(typ string, id string, observable func(providers.Observable)) error { 71 var job *Job 72 73 log.Debug().Str("id", id).Str("typ", typ).Msg("motor.watcher> subscribe") 74 sid := w.subscriberId(typ, id) 75 76 // throw an error if the id is already registered 77 _, ok := w.subscriptions.Load(sid) 78 if ok { 79 return errors.New("resource " + typ + " with " + id + " is already registered") 80 } 81 82 // register the right job to gather the information 83 switch typ { 84 case "file": 85 job = &Job{ 86 ID: sid, 87 ScheduledFor: time.Now(), 88 Interval: w.SleepDuration, 89 Runnable: NewFileRunnable(id), 90 Repeat: -1, 91 Callback: []func(o providers.Observable){ 92 func(o providers.Observable) { 93 observable(o) 94 }, 95 }, 96 } 97 case "command": 98 job = &Job{ 99 ID: sid, 100 ScheduledFor: time.Now(), 101 Interval: w.SleepDuration, 102 Runnable: NewCommandRunnable(id), 103 Repeat: -1, 104 Callback: []func(o providers.Observable){ 105 func(o providers.Observable) { 106 observable(o) 107 }, 108 }, 109 } 110 default: 111 return errors.New("unknown typ " + typ) 112 } 113 114 jobid, err := w.jm.Schedule(job) 115 if err != nil { 116 return err 117 } 118 119 // verify that the job id is our given id 120 if jobid != sid { 121 w.jm.Delete(jobid) 122 return errors.New("something is wrong, the job ids are not identical") 123 } 124 125 // store the subscription 126 w.subscriptions.Store(sid, &WatcherSubscription{ 127 typ: typ, 128 observable: observable, 129 }) 130 131 return nil 132 } 133 134 func (w *Watcher) Unsubscribe(typ string, id string) error { 135 log.Debug().Str("id", id).Str("typ", typ).Msg("motor.watcher> unsubscribe") 136 // gather internal id 137 sid := w.subscriberId(typ, id) 138 return w.unsubscribe(sid) 139 } 140 141 func (w *Watcher) unsubscribe(sid string) error { 142 // stop jobs in flight 143 w.jm.Delete(sid) 144 145 // remove the subscription and un-register the jobs 146 w.subscriptions.Delete(sid) 147 return nil 148 } 149 150 func (w *Watcher) TearDown() error { 151 log.Debug().Msg("motor.watcher> teardown") 152 // remove all subscriptions 153 w.subscriptions.Range(func(k string, v *WatcherSubscription) bool { 154 if err := w.unsubscribe(k); err != nil { 155 log.Warn().Str("sub", k).Err(err).Msg("motor.watch> teardown unsubscribe failed") 156 } 157 return true 158 }) 159 160 // tear down job manager, all subscriptions should be stopped already 161 w.jm.TearDown() 162 163 return nil 164 }