Skip to content

Commit

Permalink
docs & such
Browse files Browse the repository at this point in the history
  • Loading branch information
gweebg committed Feb 16, 2024
1 parent 77e927e commit 00d57e7
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 46 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Installing `ipwatcher` is pretty straightforward, clone the repository and then
> cd ipwatcher
> make
```
This should generate the executable at `ipwatcher/bin`, with the name `ipwatcher`.
This should generate the executable at `ipwatcher/build`, with the name `ipwatcher`.

To use the application, just execute `ipwatcher` with:
```bash
Expand Down
14 changes: 1 addition & 13 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,6 @@ import (

func main() {

//if len(os.Args) > 1 {
//
// command := os.Args[1]
// if command == "add-source" {
// // ! Execute CLI for source addition.
// // ! Maybe use a flag set to separate flags.
// // ! source := flag.NewFlagSet("add-source", flag.ExitOnError)
// }
//
// return
//}

configFlags := map[string]interface{}{}

configFlags["version"] = flag.String(
Expand All @@ -39,7 +27,7 @@ func main() {

configFlags["exec"] = flag.Bool(
"exec",
false,
true,
"enable execution of configuration defined actions",
)

Expand Down
11 changes: 8 additions & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ import (
"reflect"
)

var config *viper.Viper
var (
config *viper.Viper
)

func Init(userFlags map[string]interface{}) {

// todo: hot-reload upon configuration changes, use viper

config = viper.New()

config.SetConfigType("yaml")
config.SetConfigName("config")
config.AddConfigPath("./")
config.AddConfigPath("config/")

load(userFlags)
}

func load(userFlags map[string]interface{}) {

err := config.ReadInConfig()
utils.Check(err, "")

Expand Down
40 changes: 33 additions & 7 deletions internal/watcher/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,37 @@ import (
"context"
"errors"
"fmt"
"github.com/gweebg/ipwatcher/internal/config"
"github.com/rs/zerolog"
"os/exec"
"strings"
"time"

"github.com/gweebg/ipwatcher/internal/config"
"github.com/rs/zerolog"
)

// Executor is used to execute actions when an event is triggered.
// Needs an error channel to be passed, to be able to indicate when
// errors occur while executing the actions.
type Executor struct {
Timeout time.Duration
logger zerolog.Logger
errorChan chan error
}

// NewExecutor creates a config.Exec executor.
//
// Usage of a watcher.Executor
//
// ex := NewExecutor(errorChannel)
// action = config.Exec{
// Type: "python",
// Args: "",
// Path: "script.py",
// }
// ex.Execute(action)
//
// Each execution is associated with a context.ContextWithTimeout delimiting
// the maximum time the action has to execute, defined on the configuration file.
func NewExecutor(errorChan chan error) *Executor {

c := config.GetConfig()
Expand All @@ -34,30 +52,38 @@ func NewExecutor(errorChan chan error) *Executor {
}
}

// ExecuteSlice executes, in parallel, a slice of config.Exec actions.
func (e *Executor) ExecuteSlice(actions []config.Exec) {
for _, action := range actions {
e.logger.Debug().Str("command", action.String()).Msg("executing action")
go e.Execute(action)
}
}

// Execute executes the given config.Exec action defined on the configuration
// file under 'events.<event>.actions'. Runs the action with a timed out context.Context
// killing the process if a configuration file defined threshold (in seconds) is crossed,
// limiting the execution time of the action.
func (e *Executor) Execute(action config.Exec) {

ctx, cancel := context.WithTimeout(context.Background(), e.Timeout)
defer cancel()

args := strings.Split(action.Args, " ")
args = append([]string{action.Path}, args...)

cmd := exec.CommandContext(ctx, action.Type, args...)

// control the execution time of the current action
go func() {
<-ctx.Done()
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
e.logger.Warn().Str("action", action.String()).Err(errors.New(fmt.Sprintf("execution time exceeded (max is %v)", e.Timeout))).Send()
_ = cmd.Process.Kill() // try to kill just in case of children processes
}
}()

args := strings.Split(action.Args, " ")
args = append([]string{action.Path}, args...)

cmd := exec.CommandContext(ctx, action.Type, args...)

// redirecting the stderr of the spawned process to the pipe for later logging
stderr, _ := cmd.StderrPipe()
if err := cmd.Start(); err != nil {
e.errorChan <- errors.Join(err, ErrorExecutor)
Expand Down
42 changes: 20 additions & 22 deletions internal/watcher/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,54 @@ import (
)

var (
// ErrorDatabase represents an error specific to database operations
ErrorDatabase = errors.New("database error")
// ErrorNotifier represents an error specific to notifier operations
ErrorNotifier = errors.New("notifier error")
// ErrorExecutor represents an error specific to the executor
ErrorExecutor = errors.New("executor error")
ErrorFetch = errors.New("fetch error")
// ErrorFetch represents an error specific to address fetching operations
ErrorFetch = errors.New("fetch error")
)

// Watcher is the main part of the IP watcher service. According to a defined
// timeout checks for address changes, invoking handlers to when different
// actions are triggered (on_change, on_match and on_error).
type Watcher struct {

// Version indicates the versions the watcher is supposed to track (v4|v6|all)
Version string
// allowApi exposes an API with the database records
allowApi bool
// allowExec enables the execution of actions upon an event
// Timeout represents the duration between each address query
Timeout time.Duration

allowApi bool
allowExec bool

// notifier allows for email notification sending
notifier *Notifier
// fetcher is responsible for fetching information relative to the address
fetcher *Fetcher
// executor allows for the execution of configuration defined actions
fetcher *Fetcher
executor *Executor

// Timeout represents the duration between each address query
Timeout time.Duration
// ticker is a *time.Ticker object responsible for waiting Timeout
ticker *time.Ticker

// tickerQuitChan allows the stop of the ticker
ticker *time.Ticker
tickerQuitChan chan struct{}
// errorChan handles errors coming from the event handlers
errorChan chan error
// logger is the logger for this service
logger zerolog.Logger
errorChan chan error
logger zerolog.Logger
}

// NewWatcher creates a new watcher. Its parameters are set according
// to the values set on the YAML configuration file.
func NewWatcher() *Watcher {

c := config.GetConfig()

timeout := time.Duration(
c.GetInt("watcher.timeout")) * time.Second

// only set the notifier and executor if the flags for it are set to true
var notifier *Notifier = nil
if c.GetBool("flags.notify") {
notifier = NewNotifier()
}

errorChan := make(chan error)

var executor *Executor = nil
if c.GetBool("flags.exec") {
executor = NewExecutor(errorChan)
Expand Down Expand Up @@ -130,14 +129,13 @@ func (w *Watcher) HandleEvent(eventType string, ctx context.Context) {
if handler != nil {

if handler.Notify && w.notifier != nil {

err := w.notifier.NotifyMail(ctx)
if err != nil {
w.errorChan <- errors.Join(err, ErrorNotifier)
}
w.logger.Info().
Str("event", eventType).
Msgf("recipients (%d) notified", len(w.notifier.Recipients))
Msgf("notified %d recipients", len(w.notifier.Recipients))
}

if w.executor != nil {
Expand Down

0 comments on commit 00d57e7

Please sign in to comment.