Browse Source

Implement configuration and check subcommand

Douglas Thrift 1 year ago
parent
commit
3c4242ae86
10 changed files with 182 additions and 6 deletions
  1. 1 0
      .gitignore
  2. 27 1
      cmd/presence/check.go
  3. 8 4
      cmd/presence/main.go
  4. 57 0
      config.go
  5. 76 0
      config_test.go
  6. 1 1
      go.mod
  7. 2 0
      tests/bad_mac_encoding.yml
  8. 3 0
      tests/defaults.yml
  9. 5 0
      tests/success.yml
  10. 2 0
      tests/wrong_mac_encoding.yml

+ 1 - 0
.gitignore

@@ -16,3 +16,4 @@
 # vendor/
 
 .vscode/
+presence.yml

+ 27 - 1
cmd/presence/check.go

@@ -1,9 +1,35 @@
 package main
 
+import (
+	"goa.design/clue/log"
+
+	"douglasthrift.net/presence"
+)
+
 type (
-	Check struct{}
+	Check struct {
+		Values bool `help:"Show config values." short:"V"`
+	}
 )
 
 func (c *Check) Run(cli *CLI) error {
+	ctx := cli.Context()
+	config, err := presence.ParseConfig(cli.Config)
+	if err != nil {
+		log.Error(ctx, err, log.Fields{"config": cli.Config})
+		return err
+	}
+
+	if c.Values {
+		log.Info(ctx, log.Fields{"interval": config.Interval})
+
+		as := make([]string, len(config.MACAddresses))
+		for i, a := range config.MACAddresses {
+			as[i] = a.String()
+		}
+		log.Info(ctx, log.Fields{"mac_addresses": as})
+
+		log.Info(ctx, log.Fields{"ping_count": config.PingCount})
+	}
 	return nil
 }

+ 8 - 4
cmd/presence/main.go

@@ -3,6 +3,7 @@ package main
 import (
 	"context"
 	"fmt"
+	"path/filepath"
 	"runtime"
 
 	"github.com/alecthomas/kong"
@@ -11,6 +12,7 @@ import (
 
 type (
 	CLI struct {
+		Config  string           `default:"${config}" help:"Set the configuration file." short:"c" type:"existingfile"`
 		Debug   bool             `help:"Show debug information in log." short:"d"`
 		Version kong.VersionFlag `help:"Show version information." short:"v"`
 
@@ -20,9 +22,10 @@ type (
 )
 
 var (
-	version = "dev"
-	commit  = "none"
-	date    = "unknown"
+	version      = "dev"
+	commit       = "none"
+	date         = "unknown"
+	configPrefix = ""
 )
 
 func main() {
@@ -31,6 +34,7 @@ func main() {
 		cli,
 		kong.Description("Home network presence detection daemon for IFTTT"), kong.UsageOnError(),
 		kong.Vars{
+			"config":  filepath.Join(configPrefix, "presence.yml"),
 			"version": fmt.Sprintf("presence version %v %v %v/%v %v %v", version, runtime.Version(), runtime.GOOS, runtime.GOARCH, commit, date),
 		},
 	)
@@ -43,7 +47,7 @@ func (cli *CLI) Context() (ctx context.Context) {
 	if cli.Debug {
 		ctx = log.Context(ctx, log.WithDebug())
 	} else {
-		ctx = log.Context(ctx)
+		ctx = log.Context(ctx, log.WithDisableBuffering(func(context.Context) bool { return true }))
 	}
 	return
 }

+ 57 - 0
config.go

@@ -0,0 +1,57 @@
+package presence
+
+import (
+	"net"
+	"os"
+	"time"
+
+	"gopkg.in/yaml.v3"
+)
+
+type (
+	MACAddress struct {
+		net.HardwareAddr
+	}
+
+	Config struct {
+		Interval     time.Duration `yaml:"interval"`
+		MACAddresses []MACAddress  `yaml:"mac_addresses"`
+		PingCount    uint          `yaml:"ping_count"`
+	}
+)
+
+func ParseConfig(name string) (*Config, error) {
+	f, err := os.Open(name)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	d := yaml.NewDecoder(f)
+	d.KnownFields(true)
+
+	c := &Config{}
+	err = d.Decode(c)
+	if err != nil {
+		return nil, err
+	}
+
+	if c.Interval == 0 {
+		c.Interval = 30 * time.Second
+	}
+	if c.PingCount == 0 {
+		c.PingCount = 1
+	}
+	return c, nil
+}
+
+func (a *MACAddress) UnmarshalYAML(value *yaml.Node) (err error) {
+	var s string
+	err = value.Decode(&s)
+	if err != nil {
+		return
+	}
+
+	a.HardwareAddr, err = net.ParseMAC(s)
+	return
+}

+ 76 - 0
config_test.go

@@ -0,0 +1,76 @@
+package presence
+
+import (
+	"net"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParseConfig(t *testing.T) {
+	cases := []struct {
+		name, file string
+		config     *Config
+		err        string
+	}{
+		{
+			name: "success",
+			file: "success.yml",
+			config: &Config{
+				Interval: 1 * time.Minute,
+				MACAddresses: []MACAddress{
+					{net.HardwareAddr{0, 0, 0, 0, 0, 0xa}},
+					{net.HardwareAddr{0, 0, 0, 0, 0, 0xb}},
+				},
+				PingCount: 5,
+			},
+		},
+		{
+			name: "defaults",
+			file: "defaults.yml",
+			config: &Config{
+				Interval: 30 * time.Second,
+				MACAddresses: []MACAddress{
+					{net.HardwareAddr{0, 0, 0, 0, 0, 1}},
+					{net.HardwareAddr{0, 0, 0, 0, 0, 2}},
+				},
+				PingCount: 1,
+			},
+		},
+		{
+			name: "nonexistent file",
+			file: "nonexistent.yml",
+			err:  "open tests/nonexistent.yml: no such file or directory",
+		},
+		{
+			name: "wrong MAC encoding",
+			file: "wrong_mac_encoding.yml",
+			err:  "yaml: unmarshal errors:\n  line 2: cannot unmarshal !!seq into string",
+		},
+		{
+			name: "bad MAC encoding",
+			file: "bad_mac_encoding.yml",
+			err:  "address 00-00-00-00-00-0x: invalid MAC address",
+		},
+	}
+
+	for _, tc := range cases {
+		tc := tc
+
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			assert := assert.New(t)
+
+			c, err := ParseConfig(filepath.Join("tests", tc.file))
+			if tc.err != "" {
+				assert.ErrorContains(err, tc.err)
+			} else {
+				assert.NoError(err)
+				assert.Equal(tc.config, c)
+			}
+		})
+	}
+}

+ 1 - 1
go.mod

@@ -6,6 +6,7 @@ require (
 	github.com/alecthomas/kong v0.6.1
 	github.com/stretchr/testify v1.7.5
 	goa.design/clue v0.7.0
+	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
@@ -27,5 +28,4 @@ require (
 	google.golang.org/grpc v1.47.0 // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
 )

+ 2 - 0
tests/bad_mac_encoding.yml

@@ -0,0 +1,2 @@
+mac_addresses:
+  - 00-00-00-00-00-0x

+ 3 - 0
tests/defaults.yml

@@ -0,0 +1,3 @@
+mac_addresses:
+  - 00:00:00:00:00:01
+  - 00:00:00:00:00:02

+ 5 - 0
tests/success.yml

@@ -0,0 +1,5 @@
+interval: 1m
+mac_addresses:
+  - 00:00:00:00:00:0a
+  - 00:00:00:00:00:0b
+ping_count: 5

+ 2 - 0
tests/wrong_mac_encoding.yml

@@ -0,0 +1,2 @@
+mac_addresses:
+  - [0, 0, 0, 0, 0, 0xc]