From df1ad251d135612390638539a94f190470f4a384 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sun, 9 Jun 2019 03:32:30 -0600 Subject: [PATCH 1/5] WIP temporary decisions for public api --- cmd/watchdog/watchdog.go | 94 ++++++++++++++++++++++++++++++++ watchdog.go | 115 +++++---------------------------------- 2 files changed, 109 insertions(+), 100 deletions(-) create mode 100644 cmd/watchdog/watchdog.go diff --git a/cmd/watchdog/watchdog.go b/cmd/watchdog/watchdog.go new file mode 100644 index 0000000..bbe968e --- /dev/null +++ b/cmd/watchdog/watchdog.go @@ -0,0 +1,94 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + + watchdog "git.rootprojects.org/root/watchdog.go" +) + +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 := &watchdog.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]watchdog.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 watchdog.ConfigWatch) { + d := watchdog.New(&watchdog.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 +} + +// 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) + } +} diff --git a/watchdog.go b/watchdog.go index ccad730..134d687 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 } @@ -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) - } -} -- 2.38.5 From 900440aefa78070676435fad21a7a6254b2f2082 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 10 Jun 2019 03:13:51 -0600 Subject: [PATCH 2/5] ignore binary --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e59495e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +watchdog -- 2.38.5 From 1a37cf2aed433922c9c0698c813d85a21730640c Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 12 Jun 2019 01:45:28 -0600 Subject: [PATCH 3/5] add version info to build --- .gitignore | 1 + README.md | 4 +- cmd/watchdog/watchdog.go | 23 ++++++++ tools/version/version.go | 123 +++++++++++++++++++++++++++++++++++++++ watchdog.go | 6 +- 5 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 tools/version/version.go diff --git a/.gitignore b/.gitignore index e59495e..b0690ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +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/cmd/watchdog/watchdog.go b/cmd/watchdog/watchdog.go index bbe968e..c1b3818 100644 --- a/cmd/watchdog/watchdog.go +++ b/cmd/watchdog/watchdog.go @@ -1,3 +1,6 @@ +//go:generate go run ../../tools/version/version.go + +// Watchdog Binary package main import ( @@ -6,15 +9,35 @@ import ( "io/ioutil" "log" "os" + "strings" watchdog "git.rootprojects.org/root/watchdog.go" ) +var ( + Version = "v0.0.0" + GitRev = "00000000" + Timestamp = "0000-00-00T00:00:00Z" +) + func usage() { fmt.Println("Usage: watchdog -c config.json") } func main() { + for i := range os.Args { + switch { + case strings.HasSuffix(os.Args[i], "version"): + fmt.Println(Timestamp) + fmt.Println(Version) + fmt.Println(GitRev) + os.Exit(0) + case strings.HasSuffix(os.Args[i], "help"): + usage() + os.Exit(0) + } + } + if 3 != len(os.Args) { usage() os.Exit(1) 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 134d687..86f77c4 100644 --- a/watchdog.go +++ b/watchdog.go @@ -177,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 @@ -194,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 } -- 2.38.5 From 2ffb2dce486c4733eb910aa451fb53eba848acf5 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Jun 2019 17:16:17 -0600 Subject: [PATCH 4/5] log hiccups, downtime, and unrecoverable downtime --- build.go | 8 ++++++++ watchdog.go | 10 ++++++++++ 2 files changed, 18 insertions(+) create mode 100644 build.go diff --git a/build.go b/build.go new file mode 100644 index 0000000..076c4c3 --- /dev/null +++ b/build.go @@ -0,0 +1,8 @@ +//go:generate go build -o watchdog cmd/watchdog/watchdog.go + +package watchdog + +// This is takes the place of a makefile. + +// Usage: +// go generate -mod=vendor build.go diff --git a/watchdog.go b/watchdog.go index 86f77c4..2a7806d 100644 --- a/watchdog.go +++ b/watchdog.go @@ -52,6 +52,15 @@ func (d *Dog) watch() { return } + time.Sleep(time.Duration(2) * time.Second) + err2 := d.check() + if nil != err { + d.Logger <- fmt.Sprintf("Down: '%s': %s", d.Name, err2) + } else { + d.Logger <- fmt.Sprintf("Hiccup: '%s': %s", d.Name, err) + return + } + failure := false t := 10 for { @@ -61,6 +70,7 @@ func (d *Dog) watch() { t *= 2 err := d.check() if nil != err { + d.Logger <- fmt.Sprintf("Unrecoverable: '%s': %s", d.Name, err) failure = true } else { failure = false -- 2.38.5 From 06be5b50884bf01c1b0a3f433fc7dec5b9ce9372 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 20 Jun 2019 17:18:09 -0600 Subject: [PATCH 5/5] minor doc updates --- doc.go | 2 ++ 1 file changed, 2 insertions(+) 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 -- 2.38.5