-
Notifications
You must be signed in to change notification settings - Fork 0
/
guard.go
142 lines (121 loc) · 3.52 KB
/
guard.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main
import (
"context"
"flag"
"fmt"
"io"
stdlog "log"
"os"
"os/exec"
"regexp"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
type (
// CmdRequest ist a guarded command request
CmdRequest struct {
Name string
Command string
Debug bool
ErrFile string
ErrFileQuiet bool
ErrFileHideUUID bool
QuietTimes string
Timeout time.Duration
Lockfile string
Regex *regexp.Regexp
Config *Config
Status *CmdStatus
Reporter *Reporter
}
// CmdStatus is the commands status
CmdStatus struct {
Stdout io.Writer // captures stdout
Stderr io.Writer // captures stderr
Combined io.Writer // captures stdout and stderr
ExitCode int // captures the exitcode
}
// GuardFunc is a middleware function
GuardFunc func(ctx context.Context, cr *CmdRequest) (err error)
)
func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{
Out: os.Stderr,
TimeFormat: zerolog.TimeFormatUnix,
})
stdlog.SetFlags(0)
stdlog.Default().SetOutput(
log.Logger.
Level(zerolog.DebugLevel).
With().
Str("logger", "stdlog").
Logger(),
)
cr := CmdRequest{}
cr.Config = ParseConfig()
cr.Status = &CmdStatus{}
f := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
f.StringVar(&cr.Name, "name", "guard", "cron name in syslog")
f.StringVar(&cr.ErrFile, "errfile", "/var/log/cronstatus", "error report file")
f.BoolVar(&cr.ErrFileQuiet, "errfile-quiet", false, "hide timings in error report file")
f.BoolVar(&cr.ErrFileHideUUID, "errfile-no-uuid", false, "hide uuid in error report file")
f.StringVar(&cr.QuietTimes, "quiet-times", "", "time ranges to ignore errors, format 'start(cron format):duration(golang duration):...")
f.DurationVar(&cr.Timeout, "timeout", 0, "timeout for the cron, set to enable")
f.StringVar(&cr.Lockfile, "lockfile", "", "lockfile to prevent the cron running twice, set to enable")
f.BoolVar(&cr.Debug, "debug", false, "enable debugging")
regexFlag := f.String("regex", `(?im)\b(err|fail|crit)`, "regex for bad words")
if err := f.Parse(os.Args[1:]); err != nil {
log.Fatal().Err(err).Msg("unable to parse arguments")
}
cr.Regex = regexp.MustCompile(*regexFlag)
if len(f.Args()) != 1 {
log.Fatal().Msgf("more than one command argument given: '%v'", f.Args())
}
if !cr.Debug {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}
cr.Command = f.Arg(0)
r := chained(
runner, timeout, validateStdout, validateStderr, quietIgnore,
lockfile, sentryHandler, headerize, combineLogs, insertUUID,
writeSyslog, setupLogs,
)
err := r(context.Background(), &cr)
if err != nil {
log.Fatal().Err(err).Msg("execution failed")
}
}
// chained chaines all the middlewares together (reversed execution order)
func chained(final func() GuardFunc, middlewares ...func(GuardFunc) GuardFunc) (g GuardFunc) {
g = final()
for _, m := range middlewares {
g = m(g)
}
return
}
// runner executes the guarded command
func runner() GuardFunc {
return func(ctx context.Context, cr *CmdRequest) (err error) {
cmd := exec.CommandContext(ctx, "bash", "-c", cr.Command)
cmd.Stdout = cr.Status.Stdout
cmd.Stderr = cr.Status.Stderr
err = cmd.Start()
if err != nil {
return fmt.Errorf("unable to run command: %s", err)
}
err = cmd.Wait()
log.Debug().Err(err).Str("middleware", "runner").Msg("executed")
if err != nil {
switch casted := err.(type) {
case *exec.ExitError:
cr.Status.ExitCode = casted.ExitCode()
default:
cr.Status.ExitCode = 1
err = fmt.Errorf("unable to execute command: %w", err)
}
return err
}
return err
}
}