From a2ea3209f18c440fe31bf1660f4652b5fdde5424 Mon Sep 17 00:00:00 2001 From: kalipso Date: Mon, 17 Jul 2023 18:07:44 +0200 Subject: [PATCH] initial commit --- actions/actions.go | 154 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 + gokill.go | 117 ++++++++++++++++++++++++++++++++ triggers/triggers.go | 68 +++++++++++++++++++ 4 files changed, 342 insertions(+) create mode 100644 actions/actions.go create mode 100644 go.mod create mode 100644 gokill.go create mode 100644 triggers/triggers.go diff --git a/actions/actions.go b/actions/actions.go new file mode 100644 index 0000000..983f698 --- /dev/null +++ b/actions/actions.go @@ -0,0 +1,154 @@ +package actions + +import ( + "fmt" + "sort" + "time" +) + +type OptionMissingError struct { + optionName string +} + +func (o OptionMissingError) Error() string { + return fmt.Sprintf("Error during config parsing: option %s could not be parsed.", o.optionName) +} + +type Options map[string]interface{} + +type ActionConfig struct { + Type string `json:"type"` + Options Options `json:"options"` + Stage int `json:"stage"` +} + +type KillSwitchConfig struct { + Name string `json:"name"` + Type string `json:"type"` + Options Options `json:"options"` + Actions []ActionConfig `json:"actions"` +} + +type Action interface { + Execute() +} + +type Printer struct { + Message string + ActionChan chan bool +} + +func (p Printer) Execute() { + fmt.Printf("Print action fires. Message: %s", p.Message) + p.ActionChan <- true +} + +type TimeOut struct { + Duration time.Duration + ActionChan chan bool +} + +func (t TimeOut) Execute() { + fmt.Printf("Waiting %d seconds\n", t.Duration/time.Second) + time.Sleep(t.Duration) + t.ActionChan <- true +} + +type Stage struct { + Actions []Action +} + +type StagedActions struct { + ActionChan chan bool + StageCount int + Stages []Stage +} + +func (a StagedActions) Execute() { + for idx, stage := range a.Stages { + if idx < a.StageCount { + continue + } + + fmt.Printf("Execute Stage %v\n", idx+1) + for actionidx, _ := range stage.Actions { + go stage.Actions[actionidx].Execute() + } + + for range stage.Actions { + <-a.ActionChan + } + } +} + +func NewPrint(config ActionConfig, c chan bool) (Action, error) { + opts := config.Options + message, ok := opts["message"] + + if !ok { + return nil, OptionMissingError{"message"} + } + + return Printer{fmt.Sprintf("%v", message), c}, nil +} + +func NewTimeOut(config ActionConfig, c chan bool) (Action, error) { + opts := config.Options + duration, ok := opts["duration"] + + if !ok { + return nil, OptionMissingError{"message"} + } + + return TimeOut{time.Duration(duration.(float64)) * time.Second, c}, nil +} + +func NewSingleAction(config ActionConfig, c chan bool) (Action, error) { + if config.Type == "Print" { + return NewPrint(config, c) + } + + if config.Type == "TimeOut" { + return NewTimeOut(config, c) + } + + return nil, fmt.Errorf("Error parsing config: Action with type %s does not exists", config.Type) +} + +func NewAction(config []ActionConfig) (Action, error) { + if len(config) == 1 { + return NewSingleAction(config[0], make(chan bool)) + } + + sort.Slice(config, func(i, j int) bool { + return config[i].Stage < config[j].Stage + }) + + stagedActions := StagedActions{make(chan bool), 0, []Stage{}} + + stageMap := make(map[int][]Action) + + for _, actionCfg := range config { + newAction, err := NewSingleAction(actionCfg, stagedActions.ActionChan) + + if err != nil { + return nil, err + } + + val, exists := stageMap[actionCfg.Stage] + + if !exists { + stageMap[actionCfg.Stage] = []Action{newAction} + continue + } + + stageMap[actionCfg.Stage] = append(val, newAction) + } + + for _, value := range stageMap { + stagedActions.Stages = append(stagedActions.Stages, Stage{value}) + } + + return stagedActions, nil + //return Action{}, fmt.Errorf("Error parsing config: Action with type %s does not exists", config.Type) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d2926a1 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module unknown.com/gokill + +go 1.20 diff --git a/gokill.go b/gokill.go new file mode 100644 index 0000000..0530bf4 --- /dev/null +++ b/gokill.go @@ -0,0 +1,117 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + + "unknown.com/gokill/triggers" +) + +func main() { + configFile := flag.String("c", "", "path to config file") + flag.Parse() + + if *configFile == "" { + fmt.Println("No config file given. Use --help to show usage.") + //return + } + + b := []byte(` + +[ + { + "type": "TimeOut", + "name": "custom timeout", + "options": { + "duration": 5 + }, + "actions": [ + { + "type": "TimeOut", + "options": { + "duration": 4 + }, + "stage": 1 + }, + { + "type": "Print", + "options": { + "message": "shutdown -h now" + }, + "stage": 1 + }, + { + "type": "Print", + "options": { + "message": "shutdown -h now" + }, + "stage": 2 + }, + { + "type": "TimeOut", + "options": { + "duration": 4 + }, + "stage": 5 + }, + { + "type": "Print", + "options": { + "message": "shutdown -h now" + }, + "stage": 4 + }, + { + "type": "Print", + "options": { + "message": "shutdown -h now" + }, + "stage": 7 + } + ] + } +] + `) + + var f []triggers.KillSwitchConfig + err := json.Unmarshal(b, &f) + + if err != nil { + fmt.Println(err) + return + } + + trigger, err := triggers.NewTrigger(f[0]) + + if err != nil { + fmt.Println(err) + return + } + + trigger.Listen() + + //stagedActions := actions.StagedActions{make(chan bool), 0, []actions.Stage{}} + + //stageOne := actions.Stage{[]actions.Action{ + // actions.Printer{"first action\n", stagedActions.ActionChan}, + // actions.Printer{"second actiloo\n", stagedActions.ActionChan}, + // actions.TimeOut{stagedActions.ActionChan}, + //}} + + //stageTwo := actions.Stage{[]actions.Action{ + // actions.Printer{"third action\n", stagedActions.ActionChan}, + // actions.TimeOut{stagedActions.ActionChan}, + //}} + + //stageThree := actions.Stage{[]actions.Action{ + // actions.Printer{"four action\n", stagedActions.ActionChan}, + // actions.Printer{"five action\n", stagedActions.ActionChan}, + // actions.Printer{"six action\n", stagedActions.ActionChan}, + //}} + + //stagedActions.Stages = []actions.Stage{stageOne, stageTwo, stageThree} + + //timeOut := triggers.NewTimeOut(2*time.Second, stagedActions) + //timeOut.Listen() +} diff --git a/triggers/triggers.go b/triggers/triggers.go new file mode 100644 index 0000000..4108094 --- /dev/null +++ b/triggers/triggers.go @@ -0,0 +1,68 @@ +package triggers + +import ( + "fmt" + "time" + + "unknown.com/gokill/actions" +) + +type Options map[string]interface{} + +type KillSwitchConfig struct { + Name string `json:"name"` + Type string `json:"type"` + Options Options `json:"options"` + Actions []actions.ActionConfig `json:"actions"` +} + +type Trigger interface { + Listen() +} + +type TimeOut struct { + d time.Duration + action actions.Action +} + +func (t TimeOut) Listen() { + fmt.Println("TimeOut listens") + time.Sleep(t.d) + fmt.Println("TimeOut fires") + t.action.Execute() +} + +type OptionMissingError struct { + optionName string +} + +func (o OptionMissingError) Error() string { + return fmt.Sprintf("Error during config parsing: option %s could not be parsed.", o.optionName) +} + +// func NewTimeOut(d time.Duration, action actions.Action) TimeOut { +func NewTimeOut(config KillSwitchConfig) (TimeOut, error) { + opts := config.Options + + duration, ok := opts["duration"] + + if !ok { + return TimeOut{}, OptionMissingError{"duration"} + } + + action, err := actions.NewAction(config.Actions) + + if err != nil { + return TimeOut{}, err + } + + return TimeOut{time.Duration(duration.(float64)) * time.Second, action}, nil +} + +func NewTrigger(config KillSwitchConfig) (Trigger, error) { + if config.Type == "TimeOut" { + return NewTimeOut(config) + } + + return nil, fmt.Errorf("Error parsing config: Trigger with type %s does not exists", config.Type) +}