diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0690ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +watchdog +generated-version.go diff --git a/README.md b/README.md index 478e185..f8ad0b2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ Git: ```bash git clone https://git.coolaj86.com/coolaj86/watchdog.go.git pushd watchdog.go/ -go build -o bin/watchdog +go generate -mod=vendor ./... +pushd cmd/watchdog +go build -mod=vendor ``` Zip: diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..2a64f37 --- /dev/null +++ b/build.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +export CGO_ENABLED=0 +#GOOS=windows GOARCH=amd64 go install +go tool dist list + +gocmd=watchdog.go +golib="" +echo "" + +echo "" +echo "Windows amd64" +GOOS=windows GOARCH=amd64 go build -o dist/windows-amd64/watchdog.exe $gocmd $golib +echo "Windows 386" +GOOS=windows GOARCH=386 go build -o dist/windows-386/watchdog.exe $gocmd $golib + +echo "" +echo "Darwin (macOS) amd64" +GOOS=darwin GOARCH=amd64 go build -o dist/darwin-amd64/watchdog $gocmd $golib + +echo "" +echo "Linux amd64" +GOOS=linux GOARCH=amd64 go build -o dist/linux-amd64/watchdog $gocmd $golib +echo "Linux 386" + +echo "" +GOOS=linux GOARCH=386 go build -o dist/linux-386/watchdog $gocmd $golib +echo "RPi 3 B+ ARMv7" +GOOS=linux GOARCH=arm GOARM=7 go build -o dist/linux-armv7/watchdog $gocmd $golib +echo "RPi Zero ARMv5" +GOOS=linux GOARCH=arm GOARM=5 go build -o dist/linux-armv5/watchdog $gocmd $golib + +my_ver=$(git describe --tags) +pushd dist + ls -d *-* | while read my_dist + do + if [ -d "$my_dist" ]; then + #tar -czvf watchdog-$my_ver-$my_dist.tar.gz $my_dist + zip -r watchdog-$my_ver-$my_dist.zip $my_dist + fi + done +popd + +echo "" +echo "" diff --git a/doc.go b/doc.go index fb03f91..8b12f6b 100644 --- a/doc.go +++ b/doc.go @@ -4,4 +4,6 @@ // The watchdog package is meant to be used as a binary only. // The git tag version describes the state of the binary, // not the state of the library. The API is not yet stable. +// +// See https://git.rootproject.org/root/watchdog.go for pre-built binaries. package watchdog diff --git a/tools/version/version.go b/tools/version/version.go new file mode 100644 index 0000000..f1a04a4 --- /dev/null +++ b/tools/version/version.go @@ -0,0 +1,123 @@ +// +build tools + +package main + +import ( + "bytes" + "fmt" + "go/format" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + "text/template" + "time" +) + +var exactVer *regexp.Regexp +var gitVer *regexp.Regexp +var verFile = "generated-version.go" + +func init() { + // exactly vX.Y.Z (go-compatible semver) + exactVer = regexp.MustCompile(`^v\d+\.\d+\.\d+$`) + + // vX.Y.Z-n-g0000000 git post-release, semver prerelease + gitVer = regexp.MustCompile(`^(v\d+\.\d+)\.(\d+)-(\d+)-g`) +} + +// Goal: Either use an exact version like v1.0.0 +// or translate the git version like v1.0.0-4-g0000000 +// to a semver like v1.0.1-pre4+g0000000 +// Don't fail when git repo isn't available. +func main() { + desc := gitDesc() + rev := gitRev() + ver := semVer(desc) + ts := time.Now().Format(time.RFC3339) + + v := struct { + Timestamp string + Version string + GitRev string + }{ + Timestamp: ts, + Version: ver, + GitRev: rev, + } + + // Create or overwrite the go file from template + var buf bytes.Buffer + if err := versionTpl.Execute(&buf, v); nil != err { + panic(err) + } + + // Format + src, err := format.Source(buf.Bytes()) + if nil != err { + panic(err) + } + + // Write to disk (in the Current Working Directory) + f, err := os.Create(verFile) + if nil != err { + panic(err) + } + if _, err := f.Write(src); nil != err { + panic(err) + } + if err := f.Close(); nil != err { + panic(err) + } +} + +func gitDesc() string { + args := strings.Split("git describe --tags --dirty --always", " ") + cmd := exec.Command(args[0], args[1:]...) + out, err := cmd.CombinedOutput() + if nil != err { + // Don't panic, just carry on + out = []byte("v0.0.0-0-g0000000") + } + return strings.TrimSpace(string(out)) +} + +func gitRev() string { + args := strings.Split("git rev-parse HEAD", " ") + cmd := exec.Command(args[0], args[1:]...) + out, err := cmd.CombinedOutput() + if nil != err { + // Don't panic, just carry on + out = []byte("00000000000000000") + } + return strings.TrimSpace(string(out)) +} + +func semVer(desc string) string { + var ver string + if exactVer.MatchString(desc) { + // v1.0.0 + ver = desc + } else if gitVer.MatchString(desc) { + // ((v1.0).(0)-(1)) + vers := gitVer.FindStringSubmatch(desc) + patch, err := strconv.Atoi(vers[2]) + if nil != err { + panic(err) + } + // v1.0.1-pre1 + ver = fmt.Sprintf("%s.%d-pre%s", vers[1], patch+1, vers[3]) + } + return ver +} + +var versionTpl = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. +package main + +func init() { + Timestamp = "{{ .Timestamp }}" + Version = "{{ .Version }}" + GitRev = "{{ .GitRev }}" +} +`)) diff --git a/watchdog.go b/watchdog.go index ccad730..86f77c4 100644 --- a/watchdog.go +++ b/watchdog.go @@ -1,4 +1,4 @@ -package main +package watchdog import ( "bytes" @@ -6,90 +6,14 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net" "net/http" "net/url" - "os" "os/exec" "strings" "time" ) -func usage() { - fmt.Println("Usage: watchdog -c config.json") -} - -func main() { - if 3 != len(os.Args) { - usage() - os.Exit(1) - return - } - if "-c" != os.Args[1] { - usage() - os.Exit(1) - return - } - - filename := os.Args[2] - f, err := os.Open(filename) - if nil != err { - log.Fatal(err) - return - } - - configFile, err := ioutil.ReadAll(f) - if nil != err { - log.Fatal(err) - return - } - - config := &Config{} - err = json.Unmarshal(configFile, config) - if nil != err { - log.Fatal(err) - return - } - - //fmt.Printf("%#v\n", config) - - done := make(chan struct{}, 1) - - allWebhooks := make(map[string]ConfigWebhook) - - for i := range config.Webhooks { - h := config.Webhooks[i] - allWebhooks[h.Name] = h - } - - logQueue := make(chan string, 10) - go logger(logQueue) - for i := range config.Watches { - c := config.Watches[i] - logQueue <- fmt.Sprintf("Watching '%s'", c.Name) - go func(c ConfigWatch) { - d := New(&Dog{ - Name: c.Name, - CheckURL: c.URL, - Keywords: c.Keywords, - Recover: c.RecoverScript, - Webhooks: c.Webhooks, - AllWebhooks: allWebhooks, - logger: logQueue, - }) - d.Watch() - }(config.Watches[i]) - } - - if 0 == len(config.Watches) { - log.Fatal("Nothing to watch") - return - } - - <-done -} - type Dog struct { Name string CheckURL string @@ -97,7 +21,7 @@ type Dog struct { Recover string Webhooks []string AllWebhooks map[string]ConfigWebhook - logger chan string + Logger chan string error error failures int passes int @@ -121,7 +45,7 @@ func (d *Dog) Watch() { } func (d *Dog) watch() { - d.logger <- fmt.Sprintf("Check: '%s'", d.Name) + d.Logger <- fmt.Sprintf("Check: '%s'", d.Name) err := d.check() if nil == err { @@ -183,11 +107,11 @@ func (d *Dog) check() error { if !bytes.Contains(b, []byte(d.Keywords)) { err = fmt.Errorf("Down: '%s' Not Found for '%s'", d.Keywords, d.Name) - d.logger <- fmt.Sprintf("%s", err) + d.Logger <- fmt.Sprintf("%s", err) d.error = err return err } else { - d.logger <- fmt.Sprintf("Up: '%s'", d.Name) + d.Logger <- fmt.Sprintf("Up: '%s'", d.Name) } return nil @@ -203,25 +127,25 @@ func (d *Dog) recover() { pipe, err := cmd.StdinPipe() pipe.Write([]byte(d.Recover)) if nil != err { - d.logger <- fmt.Sprintf("[Recover] Could not write to bash '%s': %s", d.Recover, err) + d.Logger <- fmt.Sprintf("[Recover] Could not write to bash '%s': %s", d.Recover, err) } err = cmd.Start() if nil != err { - d.logger <- fmt.Sprintf("[Recover] Could not start '%s': %s", d.Recover, err) + d.Logger <- fmt.Sprintf("[Recover] Could not start '%s': %s", d.Recover, err) } err = pipe.Close() if nil != err { - d.logger <- fmt.Sprintf("[Recover] Could not close '%s': %s", d.Recover, err) + d.Logger <- fmt.Sprintf("[Recover] Could not close '%s': %s", d.Recover, err) } err = cmd.Wait() cancel() if nil != err { - d.logger <- fmt.Sprintf("[Recover] '%s' failed for '%s': %s", d.Recover, d.Name, err) + d.Logger <- fmt.Sprintf("[Recover] '%s' failed for '%s': %s", d.Recover, d.Name, err) } } func (d *Dog) notify(hardFail bool) { - d.logger <- fmt.Sprintf("Notifying the authorities of %s's failure", d.Name) + d.Logger <- fmt.Sprintf("Notifying the authorities of %s's failure", d.Name) d.lastNotified = time.Now() for i := range d.Webhooks { @@ -234,7 +158,7 @@ func (d *Dog) notify(hardFail bool) { if !ok { // TODO check in main when config is read d.Webhooks[i] = "" - d.logger <- fmt.Sprintf("[Warning] Could not find webhook '%s' for '%s'", name, h.Name) + d.Logger <- fmt.Sprintf("[Warning] Could not find webhook '%s' for '%s'", name, h.Name) continue } @@ -253,14 +177,14 @@ func (d *Dog) notify(hardFail bool) { // because `{{` gets urlencoded //k = strings.Replace(k, "{{ .Name }}", d.Name, -1) v = strings.Replace(v, "{{ .Name }}", d.Name, -1) - d.logger <- fmt.Sprintf("[HEADER] %s: %s", k, v) + d.Logger <- fmt.Sprintf("[HEADER] %s: %s", k, v) form.Set(k, v) } body = strings.NewReader(form.Encode()) } else if 0 != len(h.JSON) { bodyBuf, err := json.Marshal(h.JSON) if nil != err { - d.logger <- fmt.Sprintf("[Notify] JSON Marshal Error for '%s': %s", h.Name, err) + d.Logger <- fmt.Sprintf("[Notify] JSON Marshal Error for '%s': %s", h.Name, err) continue } // `{{` should be left alone @@ -270,7 +194,7 @@ func (d *Dog) notify(hardFail bool) { client := NewHTTPClient() req, err := http.NewRequest(h.Method, h.URL, body) if nil != err { - d.logger <- fmt.Sprintf("[Notify] HTTP Client Network Error for '%s': %s", h.Name, err) + d.Logger <- fmt.Sprintf("[Notify] HTTP Client Network Error for '%s': %s", h.Name, err) continue } @@ -299,12 +223,12 @@ func (d *Dog) notify(hardFail bool) { resp, err := client.Do(req) if nil != err { - d.logger <- fmt.Sprintf("[Notify] HTTP Client Error for '%s': %s", h.Name, err) + d.Logger <- fmt.Sprintf("[Notify] HTTP Client Error for '%s': %s", h.Name, err) continue } if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { - d.logger <- fmt.Sprintf("[Notify] Response Error for '%s': %s", h.Name, resp.Status) + d.Logger <- fmt.Sprintf("[Notify] Response Error for '%s': %s", h.Name, resp.Status) continue } @@ -314,12 +238,12 @@ func (d *Dog) notify(hardFail bool) { decoder := json.NewDecoder(resp.Body) err = decoder.Decode(&data) if err != nil { - d.logger <- fmt.Sprintf("[Notify] Response Body Error for '%s': %s", h.Name, resp.Status) + d.Logger <- fmt.Sprintf("[Notify] Response Body Error for '%s': %s", h.Name, resp.Status) continue } // TODO some sort of way to determine if data is successful (keywords) - d.logger <- fmt.Sprintf("[Notify] Success? %#v", data) + d.Logger <- fmt.Sprintf("[Notify] Success? %#v", data) } } @@ -362,12 +286,3 @@ func NewHTTPClient() *http.Client { } return client } - -// This is so that the log messages don't trample -// over each other when they happen simultaneously. -func logger(msgs chan string) { - for { - msg := <-msgs - log.Println(msg) - } -}