Compare commits

...

10 Commits

Author SHA1 Message Date
8e8088ab39 [triggers/usb] fix print statement 2023-10-28 12:37:37 +02:00
2b33525ea1 [actions/unix_command] init 2023-10-28 12:37:18 +02:00
d6f09d7c84 [triggers/usb] add UsbDisconnect Trigger 2023-10-28 11:40:17 +02:00
cec34477c0 [actions/printer] add missing newline 2023-10-28 11:39:05 +02:00
15bb4dc862 fix timeout 2023-10-25 01:09:02 +02:00
2df2a00721 fix ethernet_test 2023-10-24 23:35:30 +02:00
0354d86796 refactor triggger/action registration 2023-09-30 18:49:12 +02:00
64bce5827c add gitignore 2023-09-30 18:48:42 +02:00
74a61c0a51 change doc output 2023-09-30 15:32:02 +02:00
62aff64f3b add basic ethernet test 2023-09-01 23:17:29 +02:00
12 changed files with 329 additions and 41 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
example.json
go.sum
go.mod
gokill
output.md
thoughts.md

View File

@@ -10,6 +10,12 @@ import (
type Action interface {
Execute()
DryExecute()
Create(internal.ActionConfig, chan bool) (Action, error)
}
type DocumentedAction interface {
Action
internal.Documenter
}
type Stage struct {
@@ -58,21 +64,15 @@ func (a StagedActions) Execute() {
a.executeInternal(func(a Action) { a.Execute() })
}
func (a StagedActions) Create(config internal.ActionConfig, c chan bool) (Action, error) {
return StagedActions{}, nil
}
func NewSingleAction(config internal.ActionConfig, c chan bool) (Action, error) {
if config.Type == "Print" {
return NewPrint(config, c)
}
if config.Type == "Timeout" {
return NewTimeOut(config, c)
}
if config.Type == "Command" {
return NewCommand(config, c)
}
if config.Type == "Shutdown" {
return NewShutdown(config, c)
for _, availableAction := range GetAllActions() {
if config.Type == availableAction.GetName() {
return availableAction.Create(config, c)
}
}
return nil, fmt.Errorf("Error parsing config: Action with type %s does not exists", config.Type)
@@ -111,11 +111,21 @@ func NewAction(config []internal.ActionConfig) (Action, error) {
return stagedActions, nil
}
func GetDocumenters() []internal.Documenter {
return []internal.Documenter{
func GetAllActions() []DocumentedAction {
return []DocumentedAction{
Printer{},
TimeOut{},
Command{},
Shutdown{},
}
}
func GetDocumenters() []internal.Documenter {
var result []internal.Documenter
for _, action := range GetAllActions() {
result = append(result, action)
}
return result
}

View File

@@ -13,16 +13,16 @@ type Printer struct {
}
func (p Printer) Execute() {
fmt.Printf("Print action fires. Message: %s", p.Message)
fmt.Printf("Print action fires. Message: %s\n", p.Message)
p.ActionChan <- true
}
func (p Printer) DryExecute() {
fmt.Printf("Print action fire test. Message: %s", p.Message)
fmt.Printf("Print action fire test. Message: %s\n", p.Message)
p.ActionChan <- true
}
func NewPrint(config internal.ActionConfig, c chan bool) (Action, error) {
func (p Printer) Create(config internal.ActionConfig, c chan bool) (Action, error) {
var result Printer
err := json.Unmarshal(config.Options, &result)

View File

@@ -11,24 +11,24 @@ type Shutdown struct {
ActionChan chan bool
}
func (c Shutdown) DryExecute() {
func (s Shutdown) DryExecute() {
fmt.Println("Test Shutdown executed...")
c.ActionChan <- true
s.ActionChan <- true
}
func (c Shutdown) Execute() {
func (s Shutdown) Execute() {
if err := exec.Command("shutdown", "-h", "now").Run(); err != nil {
fmt.Println("Failed to initiate shutdown:", err)
}
fmt.Println("Shutdown executed...")
c.ActionChan <- true
s.ActionChan <- true
}
func NewShutdown(config internal.ActionConfig, c chan bool) (Action, error) {
func (s Shutdown) Create(config internal.ActionConfig, c chan bool) (Action, error) {
return Shutdown{c}, nil
}

View File

@@ -18,12 +18,12 @@ func (t TimeOut) DryExecute() {
}
func (t TimeOut) Execute() {
fmt.Printf("Waiting %d seconds\n", t.Duration/time.Second)
time.Sleep(t.Duration)
fmt.Printf("Waiting %d seconds\n", t.Duration)
time.Sleep(time.Duration(t.Duration) * time.Second)
t.ActionChan <- true
}
func NewTimeOut(config internal.ActionConfig, c chan bool) (Action, error) {
func (t TimeOut) Create(config internal.ActionConfig, c chan bool) (Action, error) {
var result TimeOut
err := json.Unmarshal(config.Options, &result)
@@ -45,6 +45,6 @@ func (p TimeOut) GetDescription() string {
func (p TimeOut) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{"duration", "string", "duration in seconds", "0"},
{"duration", "int", "duration in seconds", "0"},
}
}

76
actions/unix_command.go Normal file
View File

@@ -0,0 +1,76 @@
package actions
import (
"encoding/json"
"fmt"
"os/exec"
"unknown.com/gokill/internal"
)
type Command struct {
Command string `json:"command"`
Args []string `json:"args"`
ActionChan chan bool
}
func (c Command) DryExecute() {
fmt.Printf("Test Executing Command:\n%s ", c.Command)
for _, arg := range c.Args {
fmt.Printf("%s ", arg)
}
fmt.Println("")
c.ActionChan <- true
}
func (c Command) Execute() {
cmd := exec.Command(c.Command, c.Args...)
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(string(stdout[:]))
c.ActionChan <- true
}
func CreateCommand(config internal.ActionConfig, c chan bool) (Command, error) {
result := Command{}
err := json.Unmarshal(config.Options, &result)
if err != nil {
return Command{}, err
}
if result.Command == "" {
return Command{}, internal.OptionMissingError{"command"}
}
result.ActionChan = c
return result, nil
}
func (cc Command) Create(config internal.ActionConfig, c chan bool) (Action, error) {
return CreateCommand(config, c)
}
func (p Command) GetName() string {
return "Command"
}
func (p Command) GetDescription() string {
return "When triggered executes given command"
}
func (p Command) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{"command", "string", "command to execute", ""},
{"args", "string[]", "args", ""},
}
}

View File

@@ -21,7 +21,7 @@ func GetDocumentation() string {
result += fmt.Sprintf("\n### %v\nDescription: %v \nValues:\n", act.GetName(), act.GetDescription())
for _, opt := range act.GetOptions() {
result += fmt.Sprintf("- Name: %v\n\t- Type: %v\n\t- Descr: %v\n\t- Default: %v\n",
result += fmt.Sprintf("- Name: **%v**\n\t- Type: %v\n\t- Descr: %v\n\t- Default: %v\n",
opt.Name, opt.Type, opt.Description, opt.Default)
result += "\n\n"
}

View File

@@ -49,8 +49,7 @@ func (t EthernetDisconnect) Listen() {
actions.Fire(t.action)
}
// func NewTimeOut(d time.Duration, action actions.Action) EthernetDisconnect {
func NewEthernetDisconnect(config internal.KillSwitchConfig) (EthernetDisconnect, error) {
func CreateEthernetDisconnect(config internal.KillSwitchConfig) (EthernetDisconnect, error) {
result := EthernetDisconnect{
WaitTillConnected: true,
}
@@ -76,6 +75,10 @@ func NewEthernetDisconnect(config internal.KillSwitchConfig) (EthernetDisconnect
return result, nil
}
func (e EthernetDisconnect) Create(config internal.KillSwitchConfig) (Trigger, error) {
return CreateEthernetDisconnect(config)
}
func (p EthernetDisconnect) GetName() string {
return "EthernetDisconnect"
}

82
triggers/ethernet_test.go Normal file
View File

@@ -0,0 +1,82 @@
package triggers
import (
"testing"
"unknown.com/gokill/internal"
)
func TestEthernetDisconnetConfig(t *testing.T) {
type EthernetTest struct {
testConfig internal.KillSwitchConfig
expectedError error
expectedResult EthernetDisconnect
}
testConfigs := []EthernetTest{
EthernetTest{
testConfig: internal.KillSwitchConfig{
Options: []byte("{}"),
},
expectedError: internal.OptionMissingError{"interfaceName"},
expectedResult: EthernetDisconnect{},
},
EthernetTest{
testConfig: internal.KillSwitchConfig{
Options: []byte(`{
"waitTillConnected": false
}`),
},
expectedError: internal.OptionMissingError{"interfaceName"},
expectedResult: EthernetDisconnect{},
},
EthernetTest{
testConfig: internal.KillSwitchConfig{
Options: []byte(`{
"interfaceName": "eth0",
"waitTillConnected": false
}`),
},
expectedError: nil,
expectedResult: EthernetDisconnect{WaitTillConnected: false, InterfaceName: "eth0"},
},
EthernetTest{
testConfig: internal.KillSwitchConfig{
Options: []byte(`{
"interfaceName": "eth0",
"waitTillConnected": true
}`),
},
expectedError: nil,
expectedResult: EthernetDisconnect{WaitTillConnected: true, InterfaceName: "eth0"},
},
EthernetTest{
testConfig: internal.KillSwitchConfig{
Options: []byte("{ \"interfaceName\": \"eth0\" }"),
},
expectedError: nil,
expectedResult: EthernetDisconnect{WaitTillConnected: true, InterfaceName: "eth0"},
},
}
for _, testConfig := range testConfigs {
result, err := CreateEthernetDisconnect(testConfig.testConfig)
if err != testConfig.expectedError {
t.Errorf("Error was incorrect, got: %s, want: %s.", err, testConfig.expectedError)
}
if result.WaitTillConnected != testConfig.expectedResult.WaitTillConnected {
t.Errorf("WaitTillConnected was incorrect, got: %v, want: %v.", result, testConfig.expectedResult)
}
if result.InterfaceName != testConfig.expectedResult.InterfaceName {
t.Errorf("InterfaceName was incorrect, got: %v, want: %v.", result, testConfig.expectedResult)
}
}
}

View File

@@ -22,7 +22,7 @@ func (t TimeOut) Listen() {
actions.Fire(t.action)
}
func NewTimeOut(config internal.KillSwitchConfig) (TimeOut, error) {
func (t TimeOut) Create(config internal.KillSwitchConfig) (Trigger, error) {
var result TimeOut
err := json.Unmarshal(config.Options, &result)
@@ -50,6 +50,6 @@ func (p TimeOut) GetDescription() string {
func (p TimeOut) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{"duration", "string", "duration in seconds", "0"},
{"duration", "int", "duration in seconds", "0"},
}
}

View File

@@ -8,23 +8,38 @@ import (
type Trigger interface {
Listen()
Create(internal.KillSwitchConfig) (Trigger, error)
}
type DocumentedTrigger interface {
internal.Documenter
Trigger
}
func NewTrigger(config internal.KillSwitchConfig) (Trigger, error) {
if config.Type == "Timeout" {
return NewTimeOut(config)
}
if config.Type == "EthernetDisconnect" {
return NewEthernetDisconnect(config)
for _, availableTrigger := range GetAllTriggers() {
if config.Type == availableTrigger.GetName() {
return availableTrigger.Create(config)
}
}
return nil, fmt.Errorf("Error parsing config: Trigger with type %s does not exists", config.Type)
}
func GetDocumenters() []internal.Documenter {
return []internal.Documenter{
func GetAllTriggers() []DocumentedTrigger {
return []DocumentedTrigger{
TimeOut{},
EthernetDisconnect{},
UsbDisconnect{},
}
}
func GetDocumenters() []internal.Documenter {
var result []internal.Documenter
for _, action := range GetAllTriggers() {
result = append(result, action)
}
return result
}

96
triggers/usb.go Normal file
View File

@@ -0,0 +1,96 @@
package triggers
import (
"encoding/json"
"errors"
"fmt"
"os"
"time"
"unknown.com/gokill/actions"
"unknown.com/gokill/internal"
)
type UsbDisconnect struct {
WaitTillConnected bool `json:"waitTillConnected"`
DeviceName string `json:"deviceName"`
action actions.Action
}
func isUsbConnected(deviceName string) bool {
devicePath := "/dev/disk/by-id/" + deviceName
_, err := os.Open(devicePath)
if errors.Is(err, os.ErrNotExist) {
return false
}
return true
}
func (t UsbDisconnect) Listen() {
if t.WaitTillConnected {
for !isUsbConnected(t.DeviceName) {
time.Sleep(1 * time.Second)
}
fmt.Printf("Device %s detected.\n", t.DeviceName)
fmt.Println("UsbDisconnect Trigger is Armed")
}
for {
if !isUsbConnected(t.DeviceName) {
break
}
time.Sleep(1 * time.Second)
}
actions.Fire(t.action)
}
func CreateUsbDisconnect(config internal.KillSwitchConfig) (UsbDisconnect, error) {
result := UsbDisconnect{
WaitTillConnected: true,
}
err := json.Unmarshal(config.Options, &result)
if err != nil {
return UsbDisconnect{}, err
}
if result.DeviceName == "" {
return UsbDisconnect{}, internal.OptionMissingError{"deviceName"}
}
action, err := actions.NewAction(config.Actions)
if err != nil {
return UsbDisconnect{}, err
}
result.action = action
return result, nil
}
func (e UsbDisconnect) Create(config internal.KillSwitchConfig) (Trigger, error) {
return CreateUsbDisconnect(config)
}
func (p UsbDisconnect) GetName() string {
return "UsbDisconnect"
}
func (p UsbDisconnect) GetDescription() string {
return "Triggers when given usb drive is disconnected"
}
func (p UsbDisconnect) GetOptions() []internal.ConfigOption {
return []internal.ConfigOption{
{"waitTillConnected", "bool", "Only trigger when device was connected before", "true"},
{"deviceId", "string", "Name of device under /dev/disk/by-id/", "\"\""},
}
}