Skip to content

Commit

Permalink
Merge pull request #15 from asobrien/feature/tags
Browse files Browse the repository at this point in the history
Add support for additional tags
  • Loading branch information
theckman authored Feb 9, 2017
2 parents 0d5b114 + 3789feb commit 3aa19ca
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Application Options:
-p, --passthru passthru stdout/stderr to controlling tty
-P, --use-parent if cronner invocation is runner under cronner, emit the parental values as tags
-s, --sensitive specify whether command output may contain sensitive details, this only avoids it being printed to stderr
-t, --tag= additional tags to add to datadog events and metrics (can be used multiple times), either <key>:<value> or <string> format
-V, --version print the version string and exit
-w, --warn-after=N emit a warning event every N seconds if the job hasn't finished, set to 0 to disable (default: 0)
-W, --wait-secs= how long to wait for the file lock for (default: 0)
Expand Down
16 changes: 16 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"regexp"
"runtime"
"strings"
"unicode"

"github.com/jessevdk/go-flags"
"github.com/tideland/golib/logger"
Expand Down Expand Up @@ -42,6 +43,7 @@ type binArgs struct {
Passthru bool `short:"p" long:"passthru" description:"passthru stdout/stderr to controlling tty"`
Parent bool `short:"P" long:"use-parent" description:"if cronner invocation is runner under cronner, emit the parental values as tags"`
Sensitive bool `short:"s" long:"sensitive" description:"specify whether command output may contain sensitive details, this only avoids it being printed to stderr"`
Tags []string `short:"t" long:"tag" description:"additional tags to add to datadog events and metrics (can be used multiple times), either <key>:<value> or <string> format"`
Version bool `short:"V" long:"version" description:"print the version string and exit"`
WarnAfter uint64 `short:"w" long:"warn-after" default:"0" value-name:"N" description:"emit a warning event every N seconds if the job hasn't finished, set to 0 to disable"`
WaitSeconds uint64 `short:"W" long:"wait-secs" default:"0" description:"how long to wait for the file lock for"`
Expand All @@ -51,6 +53,7 @@ type binArgs struct {
}

var argsLabelRegex = regexp.MustCompile(`^[a-zA-Z0-9_\. ]+$`)
var argsTagsRegex = regexp.MustCompile(`^[\p{L}\d\_\-\.\:\\\/]+$`)

// parse function configures the go-flags parser and runs it
// it also does some light input validation
Expand Down Expand Up @@ -92,6 +95,19 @@ func (a *binArgs) parse(args []string) (string, error) {
return "", fmt.Errorf("cron label '%v' is invalid, it can only be alphanumeric with underscores, periods, and spaces", a.Label)
}

// Make sure tags are valid -- http://docs.datadoghq.com/guides/metrics/#tags
for _, tag := range a.Tags {
if !argsTagsRegex.MatchString(tag) {
return "", fmt.Errorf("tag '%v' is invalid, it can only be alphanumeric with underscores, periods, colons, minuses and slashes", tag)
}
if !unicode.IsLetter([]rune(tag)[0]) {
return "", fmt.Errorf("tag '%v' is invalid, it must start with a letter", tag)
}
if len([]rune(tag)) > 200 {
return "", fmt.Errorf("tag '%v' is invalid, tags must be less than 200 characters", tag)
}
}

if len(a.Args.Command) == 0 {
return "", fmt.Errorf("you must specify a command to run either using by adding it to the end, or using the command flag")
}
Expand Down
64 changes: 64 additions & 0 deletions args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (t *TestSuite) Test_binArgs_parse(c *C) {
c.Check(args.Parent, Equals, false)
c.Check(args.Passthru, Equals, false)
c.Check(args.Sensitive, Equals, false)
c.Check(args.Tags, HasLen, 0)
c.Check(args.Version, Equals, false)
c.Check(args.WarnAfter, Equals, uint64(0))
c.Check(args.WaitSeconds, Equals, uint64(0))
Expand All @@ -133,6 +134,8 @@ func (t *TestSuite) Test_binArgs_parse(c *C) {
"-P",
"-p",
"-s",
"-t", "tag1",
"-t", "tag2",
"-w", "42",
"-W", "84",
"--", "/bin/true",
Expand Down Expand Up @@ -160,6 +163,9 @@ func (t *TestSuite) Test_binArgs_parse(c *C) {
c.Check(args.Parent, Equals, true)
c.Check(args.Passthru, Equals, true)
c.Check(args.Sensitive, Equals, true)
c.Assert(args.Tags, HasLen, 2)
c.Check(args.Tags[0], Equals, "tag1")
c.Check(args.Tags[1], Equals, "tag2")
c.Check(args.Version, Equals, false)
c.Check(args.WarnAfter, Equals, uint64(42))
c.Check(args.WaitSeconds, Equals, uint64(84))
Expand Down Expand Up @@ -187,6 +193,8 @@ func (t *TestSuite) Test_binArgs_parse(c *C) {
"--use-parent",
"--passthru",
"--sensitive",
"--tag", "tag1",
"--tag", "tag2",
"--warn-after", "42",
"--wait-secs", "84",
"--", "/bin/true",
Expand All @@ -212,6 +220,9 @@ func (t *TestSuite) Test_binArgs_parse(c *C) {
c.Check(args.Parent, Equals, true)
c.Check(args.Passthru, Equals, true)
c.Check(args.Sensitive, Equals, true)
c.Assert(args.Tags, HasLen, 2)
c.Check(args.Tags[0], Equals, "tag1")
c.Check(args.Tags[1], Equals, "tag2")
c.Check(args.Version, Equals, false)
c.Check(args.WarnAfter, Equals, uint64(42))
c.Check(args.WaitSeconds, Equals, uint64(84))
Expand All @@ -232,6 +243,8 @@ func (t *TestSuite) Test_binArgs_parse(c *C) {
"--log-path=/var/log/testcronner",
"--log-level=info",
"--namespace=testcronner",
"--tag=tag1",
"--tag=tag2",
"--warn-after=42",
"--wait-secs=84",
"--", "/bin/true",
Expand All @@ -250,11 +263,62 @@ func (t *TestSuite) Test_binArgs_parse(c *C) {
c.Check(args.LogPath, Equals, "/var/log/testcronner")
c.Check(args.LogLevel, Equals, "info")
c.Check(args.Namespace, Equals, "testcronner")
c.Assert(args.Tags, HasLen, 2)
c.Check(args.Tags[0], Equals, "tag1")
c.Check(args.Tags[1], Equals, "tag2")
c.Check(args.Version, Equals, false)
c.Check(args.WarnAfter, Equals, uint64(42))
c.Check(args.WaitSeconds, Equals, uint64(84))
c.Check(args.Cmd, Equals, "/bin/true")
c.Check(len(args.CmdArgs), Equals, 0)

//
// assert that optional tags are validated
//
args = &binArgs{}
cli = []string{
Arg0,
"-l", "test",
"-t", "世界_hello:valid",
"-t", "invalid^tag",
"--", "/bin/true",
}

output, err = args.parse(cli)
c.Assert(err, Not(IsNil))
c.Check(len(output), Equals, 0)
c.Check(err.Error(), Equals, "tag 'invalid^tag' is invalid, it can only be alphanumeric with underscores, periods, colons, minuses and slashes")

args = &binArgs{}
cli = []string{
Arg0,
"-l", "test",
"-t", "_invalid:tag",
"--", "/bin/true",
}

output, err = args.parse(cli)
c.Assert(err, Not(IsNil))
c.Check(len(output), Equals, 0)
c.Check(err.Error(), Equals, "tag '_invalid:tag' is invalid, it must start with a letter")

args = &binArgs{}
tag := "tag_exceeds_200_chars_7eUJgOC2VnpCSsgcgq66Aj5cjiOCxvu736AQHu2zWy0" +
"booxAh2B7vrTfbb59w6YovAZ1HzSGfGxVomAzsAhxVUkSrfnBWb6Xs5WpV7RQAGqu" +
"uCIvPOv5rymEeJaefyligXrsN2VBlSQpXD5M3540VvK90JauGuqIkaCOQeKU2rgrF" +
"NU632ShyW3WOGxipoKuVGpKyvnN"
cli = []string{
Arg0,
"-l", "test",
"-t", tag,
"--", "/bin/true",
}

output, err = args.parse(cli)
c.Assert(err, Not(IsNil))
c.Check(len(output), Equals, 0)
c.Check(err.Error(), Equals, fmt.Sprintf("tag '%v' is invalid, tags must be less than 200 characters", tag))

//
// argument parsing regression tests
//
Expand Down
2 changes: 1 addition & 1 deletion cronner.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
)

// Version is the program's version string
const Version = "0.6.1"
const Version = "0.7.0"

type cmdHandler struct {
gs *godspeed.Godspeed
Expand Down
8 changes: 8 additions & 0 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ func handleCommand(hndlr *cmdHandler) (int, []byte, float64, error) {
tags = append(tags, hndlr.parentMetricTags...)
}

if len(hndlr.opts.Tags) > 0 {
tags = append(tags, hndlr.opts.Tags...)
}

hndlr.gs.Timing(fmt.Sprintf("%v.time", hndlr.opts.Label), monotonicRtMs, tags)
hndlr.gs.Gauge(fmt.Sprintf("%v.exit_code", hndlr.opts.Label), float64(ret), tags)

Expand Down Expand Up @@ -337,6 +341,10 @@ func emitEvent(title, body, label, alertType string, hndlr *cmdHandler) {
tags = append(tags, hndlr.parentEventTags...)
}

if len(hndlr.opts.Tags) > 0 {
tags = append(tags, hndlr.opts.Tags...)
}

hndlr.gs.Event(title, body, fields, tags)
}

Expand Down
98 changes: 98 additions & 0 deletions runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,102 @@ func (t *TestSuite) Test_handleCommand(c *C) {
),
)

//
// Test that DD events contain additional tags
//

// Reset variables used
r = nil
err = nil
runTime = 0
match = nil

t.h.cmd = exec.Command("/bin/echo", "somevalue")
t.h.opts.EventGroup = "testgroup"
t.h.opts.Group = ""
t.h.opts.Parent = false
t.h.parentEventTags = nil
t.h.parentMetricTags = nil
t.h.opts.Tags = append(t.h.opts.Tags, "tag1:val1")

_, r, runTime, err = handleCommand(t.h)
c.Assert(err, IsNil)

stat, ok = <-t.out
c.Assert(ok, Equals, true)
c.Check(
string(stat),
Equals,
fmt.Sprintf(`_e{35,44}:Cron testCmd starting on brainbox01|UUID: %v\n|k:%v|s:cronner|t:info|#source_type:cronner,cronner_label_name:testCmd,cronner_group:testgroup,%v`, t.h.uuid, t.h.uuid, t.h.opts.Tags[0]),
)

stat, ok = <-t.out
c.Assert(ok, Equals, true)
timeStatRegex = regexp.MustCompile("^cronner.testCmd.time:([0-9\\.]+)\\|ms\\|#tag1:val1$")
match = timeStatRegex.FindAllStringSubmatch(string(stat), -1)
c.Assert(len(match), Equals, 1)
c.Assert(len(match[0]), Equals, 2)
c.Check(strconv.FormatFloat(runTime, 'f', -1, 64), Equals, match[0][1])

stat, ok = <-t.out
c.Assert(ok, Equals, true)
c.Check(string(stat), Equals, fmt.Sprintf("cronner.testCmd.exit_code:0|g|#%v", t.h.opts.Tags[0]))

stat, ok = <-t.out
c.Assert(ok, Equals, true)
c.Check(
string(stat),
Equals,
fmt.Sprintf(`_e{55,77}:Cron testCmd succeeded in %.5f seconds on brainbox01|UUID: %v\nexit code: 0\noutput: somevalue\n|k:%v|s:cronner|t:success|#source_type:cronner,cronner_label_name:testCmd,cronner_group:testgroup,%v`, runTime/1000, t.h.uuid, t.h.uuid, t.h.opts.Tags[0]),
)

//
// Test that DD metrics contain additional tags
//

// Reset variables used
r = nil
err = nil
runTime = 0
match = nil

t.h.cmd = exec.Command("/bin/echo", "somevalue")
t.h.opts.EventGroup = ""
t.h.opts.Group = "metricgroup"
t.h.opts.Tags = append(t.h.opts.Tags, "tag1:val1")

_, r, runTime, err = handleCommand(t.h)
c.Assert(err, IsNil)

stat, ok = <-t.out
c.Assert(ok, Equals, true)
c.Check(
string(stat),
Equals,
fmt.Sprintf(`_e{35,44}:Cron testCmd starting on brainbox01|UUID: %v\n|k:%v|s:cronner|t:info|#source_type:cronner,cronner_label_name:testCmd,%v`, t.h.uuid, t.h.uuid,t.h.opts.Tags[0]),
)

stat, ok = <-t.out
c.Assert(ok, Equals, true)
timeStatTagRegex = regexp.MustCompile("^cronner.testCmd.time:([0-9\\.]+)\\|ms\\|#cronner_group:([a-z]+),tag1:val1$")
match = timeStatTagRegex.FindAllStringSubmatch(string(stat), -1)
c.Assert(len(match), Equals, 1)
c.Assert(len(match[0]), Equals, 3)
c.Check(strconv.FormatFloat(runTime, 'f', -1, 64), Equals, match[0][1])
c.Check("metricgroup", Equals, match[0][2])

stat, ok = <-t.out
c.Assert(ok, Equals, true)
c.Check(string(stat), Equals, "cronner.testCmd.exit_code:0|g|#cronner_group:metricgroup,tag1:val1")

stat, ok = <-t.out
c.Assert(ok, Equals, true)
c.Check(
string(stat),
Equals,
fmt.Sprintf(`_e{55,77}:Cron testCmd succeeded in %.5f seconds on brainbox01|UUID: %v\nexit code: 0\noutput: somevalue\n|k:%v|s:cronner|t:success|#source_type:cronner,cronner_label_name:testCmd,tag1:val1`, runTime/1000, t.h.uuid, t.h.uuid),
)

//
// Test that no output is given
//
Expand All @@ -370,6 +466,7 @@ func (t *TestSuite) Test_handleCommand(c *C) {

t.h.parentEventTags = nil
t.h.parentMetricTags = nil
t.h.opts.Tags = nil

retCode, r, _, err = handleCommand(t.h)
c.Assert(err, IsNil)
Expand Down Expand Up @@ -484,6 +581,7 @@ func (t *TestSuite) Test_handleCommand(c *C) {
stat, ok = <-t.out
c.Assert(ok, Equals, true)

timeStatRegex = regexp.MustCompile("^cronner.testCmd.time:([0-9\\.]+)\\|ms$")
match = timeStatRegex.FindAllStringSubmatch(string(stat), -1)
c.Assert(len(match), Equals, 1)
c.Assert(len(match[0]), Equals, 2)
Expand Down

0 comments on commit 3aa19ca

Please sign in to comment.