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  }