Skip to content

Commit

Permalink
Merge bb06a4a into f1b85bf
Browse files Browse the repository at this point in the history
  • Loading branch information
Songmu committed Jan 17, 2019
2 parents f1b85bf + bb06a4a commit f757a42
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 3 deletions.
149 changes: 149 additions & 0 deletions checks/checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package checks

import (
"fmt"
"io"
"os"
"runtime"
"strings"
"sync"

"github.com/mackerelio/checkers"
"github.com/mackerelio/mackerel-agent/config"
cli "gopkg.in/urfave/cli.v1"
yaml "gopkg.in/yaml.v2"
)

// Command is command definition of mkr checks
var Command = cli.Command{
Name: "checks",
Usage: "Utility for check plugins",
Subcommands: []cli.Command{
commandRun,
},
}

var commandRun = cli.Command{
Name: "run",
Usage: "run check commands in mackerel-agent.conf",
Description: `
Execute command of check plugins in mackerel-agent.conf all at once.
It is used for checking setting and operation of the check plugins.
The result is output to stdout in TAP format. If any check fails,
it exits non-zero.
`,
Action: doRunChecks,
}

func doRunChecks(c *cli.Context) error {
confFile := c.GlobalString("conf")
conf, err := config.LoadConfig(confFile)
if err != nil {
return err
}
checkers := make([]checker, len(conf.CheckPlugins))
i := 0
for name, p := range conf.CheckPlugins {
checkers[i] = &checkPluginChecker{
name: name,
cp: p,
}
i++
}
return runChecks(checkers, os.Stdout)
}

type result struct {
Name string `yaml:"-"`
Memo string `yaml:"memo,omitempty"`
Cmd []string `yaml:"command,flow"`
Status string `yaml:"status"`
Stdout string `yaml:"stdout,omitempty"`
Stderr string `yaml:"stderr,omitempty"`
ExitCode int `yaml:"exitCode,omitempty"`
ErrMsg string `yaml:"error,omitempty"`
}

func (re *result) ok() bool {
return re.ExitCode == 0 && re.ErrMsg == ""
}

func (re *result) tapFormat(num int) string {
okOrNot := "ok"
if !re.ok() {
okOrNot = "not ok"
}
b, _ := yaml.Marshal(re)
// indent
yamlStr := " " + strings.Replace(strings.TrimSpace(string(b)), "\n", "\n ", -1)
return fmt.Sprintf("%s %d - %s\n ---\n%s\n ...",
okOrNot, num, re.Name, yamlStr)
}

type checkPluginChecker struct {
name string
cp *config.CheckPlugin
}

func (cpc *checkPluginChecker) check() *result {
p := cpc.cp
stdout, stderr, exitCode, err := p.Command.Run()
cmd := p.Command.Args
if len(cmd) == 0 {
cmd = append(cmd, p.Command.Cmd)
}
errMsg := ""
if err != nil {
errMsg = err.Error()
}

return &result{
Name: cpc.name,
Memo: p.Memo,
Cmd: cmd,
Status: checkers.Status(exitCode).String(),
ExitCode: exitCode,
Stdout: strings.TrimSpace(stdout),
Stderr: strings.TrimSpace(stderr),
ErrMsg: errMsg,
}
}

type checker interface {
check() *result
}

func runChecks(checkers []checker, w io.Writer) error {
ch := make(chan *result)
total := len(checkers)
go func() {
sem := make(chan struct{}, runtime.NumCPU()*2)
wg := &sync.WaitGroup{}
wg.Add(total)
for _, c := range checkers {
go func(c checker) {
defer wg.Done()
sem <- struct{}{}
ch <- c.check()
<-sem
}(c)
}
wg.Wait()
close(ch)
}()
fmt.Fprintln(w, "TAP version 13")
fmt.Fprintf(w, "1..%d\n", total)
testNum, errNum := 1, 0
for re := range ch {
fmt.Fprintln(w, re.tapFormat(testNum))
testNum++
if !re.ok() {
errNum++
}
}
if errNum > 0 {
return fmt.Errorf("Failed %d/%d tests, %3.2f%% okay",
errNum, total, float64(100*(total-errNum))/float64(total))
}
return nil
}
40 changes: 40 additions & 0 deletions checks/checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package checks

import (
"bytes"
"testing"
)

type testChecker struct {
*result
}

func (te *testChecker) check() *result {
return te.result
}

func TestRunChecks(t *testing.T) {
te := &testChecker{&result{
Name: "hoge",
Cmd: []string{"perl", "-E", "say 'Hello'"},
Status: "OK",
Stdout: "Hello",
ExitCode: 0,
}}
buf := &bytes.Buffer{}
runChecks([]checker{te}, buf)

expect := `TAP version 13
1..1
ok 1 - hoge
---
command: [perl, -E, say 'Hello']
status: OK
stdout: Hello
...
`
got := buf.String()
if got != expect {
t.Errorf("something went wrong\ngot:\n%s\nexpect:\n%s", got, expect)
}
}
4 changes: 3 additions & 1 deletion commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (

"github.com/Songmu/prompter"
mkr "github.com/mackerelio/mackerel-client-go"
"github.com/mackerelio/mkr/checks"
"github.com/mackerelio/mkr/logger"
"github.com/mackerelio/mkr/plugin"
"gopkg.in/urfave/cli.v1"
cli "gopkg.in/urfave/cli.v1"
)

func init() {
Expand Down Expand Up @@ -49,6 +50,7 @@ var Commands = []cli.Command{
commandAnnotations,
commandOrg,
plugin.CommandPlugin,
checks.Command,
}

var commandStatus = cli.Command{
Expand Down
9 changes: 7 additions & 2 deletions commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"strings"
"testing"

"gopkg.in/urfave/cli.v1"
cli "gopkg.in/urfave/cli.v1"
)

func TestCommands_requirements(t *testing.T) {
Expand All @@ -31,7 +31,12 @@ func TestCommands_requirements(t *testing.T) {
}
}
for _, sc := range subcs {
if sc.Description == "" {
if sc.Action == nil {
if sc.Description == "" && sc.Usage == "" {
t.Errorf("%s: Neither .Description nor .Usage should be empty", sc.Name)

}
} else if sc.Description == "" {
t.Errorf("%s: cli.Command.Description should not be empty", sc.Name)
}
}
Expand Down

0 comments on commit f757a42

Please sign in to comment.