Clean up subprocess handling and make shell use optional (#3509)
* Clean up handling of subprocesses and make using a shell optional * Update docs for subprocess changes * Fix tests for new subprocess behavior * More cleanup of subprocesses * Minor adjustments and cleanup for subprocess logic * Makes the watch handler reload test use the new path. * Adds check tests for new args path, and updates existing tests to use new path. * Adds support for script args in Docker checks. * Fixes the sanitize unit test. * Adds panic for unknown watch type, and reverts back to Run(). * Adds shell option back to consul lock command. * Adds shell option back to consul exec command. * Adds shell back into consul watch command. * Refactors signal forwarding and makes Windows-friendly. * Adds a clarifying comment. * Changes error wording to a warning. * Scopes signals to interrupt and kill. This avoids us trying to send SIGCHILD to the dead process. * Adds an error for shell=false for consul exec. * Adds notes about the deprecated script and handler fields. * De-nests an if statement.
This commit is contained in:
parent
23b057f46b
commit
be04bfed34
|
@ -535,16 +535,40 @@ func (a *Agent) reloadWatches(cfg *config.RuntimeConfig) error {
|
|||
var watchPlans []*watch.Plan
|
||||
for _, params := range cfg.Watches {
|
||||
// Parse the watches, excluding the handler
|
||||
wp, err := watch.ParseExempt(params, []string{"handler"})
|
||||
wp, err := watch.ParseExempt(params, []string{"handler", "args"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse watch (%#v): %v", params, err)
|
||||
}
|
||||
|
||||
// Get the handler
|
||||
h := wp.Exempt["handler"]
|
||||
if _, ok := h.(string); h == nil || !ok {
|
||||
// Get the handler and subprocess arguments
|
||||
handler, hasHandler := wp.Exempt["handler"]
|
||||
args, hasArgs := wp.Exempt["args"]
|
||||
if hasHandler {
|
||||
a.logger.Printf("[WARN] agent: The 'handler' field in watches has been deprecated " +
|
||||
"and replaced with the 'args' field. See https://www.consul.io/docs/agent/watches.html")
|
||||
}
|
||||
if _, ok := handler.(string); hasHandler && !ok {
|
||||
return fmt.Errorf("Watch handler must be a string")
|
||||
}
|
||||
if raw, ok := args.([]interface{}); hasArgs && ok {
|
||||
var parsed []string
|
||||
for _, arg := range raw {
|
||||
if v, ok := arg.(string); !ok {
|
||||
return fmt.Errorf("Watch args must be a list of strings")
|
||||
} else {
|
||||
parsed = append(parsed, v)
|
||||
}
|
||||
}
|
||||
wp.Exempt["args"] = parsed
|
||||
} else if hasArgs && !ok {
|
||||
return fmt.Errorf("Watch args must be a list of strings")
|
||||
}
|
||||
if hasHandler && hasArgs {
|
||||
return fmt.Errorf("Cannot define both watch handler and args")
|
||||
}
|
||||
if !hasHandler && !hasArgs {
|
||||
return fmt.Errorf("Must define either watch handler or args")
|
||||
}
|
||||
|
||||
// Store the watch plan
|
||||
watchPlans = append(watchPlans, wp)
|
||||
|
@ -566,7 +590,13 @@ func (a *Agent) reloadWatches(cfg *config.RuntimeConfig) error {
|
|||
for _, wp := range watchPlans {
|
||||
a.watchPlans = append(a.watchPlans, wp)
|
||||
go func(wp *watch.Plan) {
|
||||
wp.Handler = makeWatchHandler(a.LogOutput, wp.Exempt["handler"])
|
||||
var handler interface{}
|
||||
if h, ok := wp.Exempt["handler"]; ok {
|
||||
handler = h
|
||||
} else {
|
||||
handler = wp.Exempt["args"]
|
||||
}
|
||||
wp.Handler = makeWatchHandler(a.LogOutput, handler)
|
||||
wp.LogOutput = a.LogOutput
|
||||
if err := wp.Run(addr); err != nil {
|
||||
a.logger.Printf("[ERR] Failed to run watch: %v", err)
|
||||
|
@ -1684,6 +1714,7 @@ func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *structs.CheckType,
|
|||
DockerContainerID: chkType.DockerContainerID,
|
||||
Shell: chkType.Shell,
|
||||
Script: chkType.Script,
|
||||
ScriptArgs: chkType.ScriptArgs,
|
||||
Interval: chkType.Interval,
|
||||
Logger: a.logger,
|
||||
client: a.dockerClient,
|
||||
|
@ -1697,18 +1728,24 @@ func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *structs.CheckType,
|
|||
delete(a.checkMonitors, check.CheckID)
|
||||
}
|
||||
if chkType.Interval < MinInterval {
|
||||
a.logger.Println(fmt.Sprintf("[WARN] agent: check '%s' has interval below minimum of %v",
|
||||
check.CheckID, MinInterval))
|
||||
a.logger.Printf("[WARN] agent: check '%s' has interval below minimum of %v",
|
||||
check.CheckID, MinInterval)
|
||||
chkType.Interval = MinInterval
|
||||
}
|
||||
if chkType.Script != "" {
|
||||
a.logger.Printf("[WARN] agent: check %q has the 'script' field, which has been deprecated "+
|
||||
"and replaced with the 'args' field. See https://www.consul.io/docs/agent/checks.html",
|
||||
check.CheckID)
|
||||
}
|
||||
|
||||
monitor := &CheckMonitor{
|
||||
Notify: a.state,
|
||||
CheckID: check.CheckID,
|
||||
Script: chkType.Script,
|
||||
Interval: chkType.Interval,
|
||||
Timeout: chkType.Timeout,
|
||||
Logger: a.logger,
|
||||
Notify: a.state,
|
||||
CheckID: check.CheckID,
|
||||
Script: chkType.Script,
|
||||
ScriptArgs: chkType.ScriptArgs,
|
||||
Interval: chkType.Interval,
|
||||
Timeout: chkType.Timeout,
|
||||
Logger: a.logger,
|
||||
}
|
||||
monitor.Start()
|
||||
a.checkMonitors[check.CheckID] = monitor
|
||||
|
|
|
@ -1928,9 +1928,9 @@ func TestAgent_reloadWatches(t *testing.T) {
|
|||
newConf := *a.config
|
||||
newConf.Watches = []map[string]interface{}{
|
||||
{
|
||||
"type": "key",
|
||||
"key": "asdf",
|
||||
"handler": "ls",
|
||||
"type": "key",
|
||||
"key": "asdf",
|
||||
"args": []interface{}{"ls"},
|
||||
},
|
||||
}
|
||||
if err := a.reloadWatches(&newConf); err != nil {
|
||||
|
@ -1942,9 +1942,9 @@ func TestAgent_reloadWatches(t *testing.T) {
|
|||
newConf.HTTPAddrs = make([]net.Addr, 0)
|
||||
newConf.Watches = []map[string]interface{}{
|
||||
{
|
||||
"type": "key",
|
||||
"key": "asdf",
|
||||
"handler": "ls",
|
||||
"type": "key",
|
||||
"key": "asdf",
|
||||
"args": []interface{}{"ls"},
|
||||
},
|
||||
}
|
||||
if err := a.reloadWatches(&newConf); err != nil {
|
||||
|
@ -1955,9 +1955,9 @@ func TestAgent_reloadWatches(t *testing.T) {
|
|||
newConf.HTTPSAddrs = make([]net.Addr, 0)
|
||||
newConf.Watches = []map[string]interface{}{
|
||||
{
|
||||
"type": "key",
|
||||
"key": "asdf",
|
||||
"handler": "ls",
|
||||
"type": "key",
|
||||
"key": "asdf",
|
||||
"args": []interface{}{"ls"},
|
||||
},
|
||||
}
|
||||
if err := a.reloadWatches(&newConf); err == nil || !strings.Contains(err.Error(), "watch plans require an HTTP or HTTPS endpoint") {
|
||||
|
|
|
@ -49,12 +49,13 @@ type CheckNotifier interface {
|
|||
// determine the health of a given check. It is compatible with
|
||||
// nagios plugins and expects the output in the same format.
|
||||
type CheckMonitor struct {
|
||||
Notify CheckNotifier
|
||||
CheckID types.CheckID
|
||||
Script string
|
||||
Interval time.Duration
|
||||
Timeout time.Duration
|
||||
Logger *log.Logger
|
||||
Notify CheckNotifier
|
||||
CheckID types.CheckID
|
||||
Script string
|
||||
ScriptArgs []string
|
||||
Interval time.Duration
|
||||
Timeout time.Duration
|
||||
Logger *log.Logger
|
||||
|
||||
stop bool
|
||||
stopCh chan struct{}
|
||||
|
@ -101,7 +102,13 @@ func (c *CheckMonitor) run() {
|
|||
// check is invoked periodically to perform the script check
|
||||
func (c *CheckMonitor) check() {
|
||||
// Create the command
|
||||
cmd, err := ExecScript(c.Script)
|
||||
var cmd *exec.Cmd
|
||||
var err error
|
||||
if len(c.ScriptArgs) > 0 {
|
||||
cmd, err = ExecSubprocess(c.ScriptArgs)
|
||||
} else {
|
||||
cmd, err = ExecScript(c.Script)
|
||||
}
|
||||
if err != nil {
|
||||
c.Logger.Printf("[ERR] agent: failed to setup invoke '%s': %s", c.Script, err)
|
||||
c.Notify.UpdateCheck(c.CheckID, api.HealthCritical, err.Error())
|
||||
|
@ -524,6 +531,7 @@ type CheckDocker struct {
|
|||
Notify CheckNotifier
|
||||
CheckID types.CheckID
|
||||
Script string
|
||||
ScriptArgs []string
|
||||
DockerContainerID string
|
||||
Shell string
|
||||
Interval time.Duration
|
||||
|
@ -599,7 +607,13 @@ func (c *CheckDocker) check() {
|
|||
}
|
||||
|
||||
func (c *CheckDocker) doCheck() (string, *circbuf.Buffer, error) {
|
||||
cmd := []string{c.Shell, "-c", c.Script}
|
||||
var cmd []string
|
||||
if len(c.ScriptArgs) > 0 {
|
||||
cmd = c.ScriptArgs
|
||||
} else {
|
||||
cmd = []string{c.Shell, "-c", c.Script}
|
||||
}
|
||||
|
||||
execID, err := c.client.CreateExec(c.DockerContainerID, cmd)
|
||||
if err != nil {
|
||||
return api.HealthCritical, nil, err
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"github.com/hashicorp/consul/types"
|
||||
)
|
||||
|
||||
func TestCheckMonitor(t *testing.T) {
|
||||
func TestCheckMonitor_Script(t *testing.T) {
|
||||
tests := []struct {
|
||||
script, status string
|
||||
}{
|
||||
|
@ -54,16 +54,51 @@ func TestCheckMonitor(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCheckMonitor_Args(t *testing.T) {
|
||||
tests := []struct {
|
||||
args []string
|
||||
status string
|
||||
}{
|
||||
{[]string{"sh", "-c", "exit 0"}, "passing"},
|
||||
{[]string{"sh", "-c", "exit 1"}, "warning"},
|
||||
{[]string{"sh", "-c", "exit 2"}, "critical"},
|
||||
{[]string{"foobarbaz"}, "critical"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.status, func(t *testing.T) {
|
||||
notif := mock.NewNotify()
|
||||
check := &CheckMonitor{
|
||||
Notify: notif,
|
||||
CheckID: types.CheckID("foo"),
|
||||
ScriptArgs: tt.args,
|
||||
Interval: 25 * time.Millisecond,
|
||||
Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags),
|
||||
}
|
||||
check.Start()
|
||||
defer check.Stop()
|
||||
retry.Run(t, func(r *retry.R) {
|
||||
if got, want := notif.Updates("foo"), 2; got < want {
|
||||
r.Fatalf("got %d updates want at least %d", got, want)
|
||||
}
|
||||
if got, want := notif.State("foo"), tt.status; got != want {
|
||||
r.Fatalf("got state %q want %q", got, want)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMonitor_Timeout(t *testing.T) {
|
||||
// t.Parallel() // timing test. no parallel
|
||||
notif := mock.NewNotify()
|
||||
check := &CheckMonitor{
|
||||
Notify: notif,
|
||||
CheckID: types.CheckID("foo"),
|
||||
Script: "sleep 1 && exit 0",
|
||||
Interval: 50 * time.Millisecond,
|
||||
Timeout: 25 * time.Millisecond,
|
||||
Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags),
|
||||
Notify: notif,
|
||||
CheckID: types.CheckID("foo"),
|
||||
ScriptArgs: []string{"sh", "-c", "sleep 1 && exit 0"},
|
||||
Interval: 50 * time.Millisecond,
|
||||
Timeout: 25 * time.Millisecond,
|
||||
Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags),
|
||||
}
|
||||
check.Start()
|
||||
defer check.Stop()
|
||||
|
@ -83,11 +118,11 @@ func TestCheckMonitor_RandomStagger(t *testing.T) {
|
|||
// t.Parallel() // timing test. no parallel
|
||||
notif := mock.NewNotify()
|
||||
check := &CheckMonitor{
|
||||
Notify: notif,
|
||||
CheckID: types.CheckID("foo"),
|
||||
Script: "exit 0",
|
||||
Interval: 25 * time.Millisecond,
|
||||
Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags),
|
||||
Notify: notif,
|
||||
CheckID: types.CheckID("foo"),
|
||||
ScriptArgs: []string{"sh", "-c", "exit 0"},
|
||||
Interval: 25 * time.Millisecond,
|
||||
Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags),
|
||||
}
|
||||
check.Start()
|
||||
defer check.Stop()
|
||||
|
@ -108,11 +143,11 @@ func TestCheckMonitor_LimitOutput(t *testing.T) {
|
|||
t.Parallel()
|
||||
notif := mock.NewNotify()
|
||||
check := &CheckMonitor{
|
||||
Notify: notif,
|
||||
CheckID: types.CheckID("foo"),
|
||||
Script: "od -N 81920 /dev/urandom",
|
||||
Interval: 25 * time.Millisecond,
|
||||
Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags),
|
||||
Notify: notif,
|
||||
CheckID: types.CheckID("foo"),
|
||||
ScriptArgs: []string{"od", "-N", "81920", "/dev/urandom"},
|
||||
Interval: 25 * time.Millisecond,
|
||||
Logger: log.New(ioutil.Discard, UniqueID(), log.LstdFlags),
|
||||
}
|
||||
check.Start()
|
||||
defer check.Stop()
|
||||
|
@ -775,7 +810,7 @@ func TestCheck_Docker(t *testing.T) {
|
|||
check := &CheckDocker{
|
||||
Notify: notif,
|
||||
CheckID: id,
|
||||
Script: "/health.sh",
|
||||
ScriptArgs: []string{"/health.sh"},
|
||||
DockerContainerID: "123",
|
||||
Interval: 25 * time.Millisecond,
|
||||
client: c,
|
||||
|
|
|
@ -871,6 +871,7 @@ func (b *Builder) checkVal(v *CheckDefinition) *structs.CheckDefinition {
|
|||
Token: b.stringVal(v.Token),
|
||||
Status: b.stringVal(v.Status),
|
||||
Script: b.stringVal(v.Script),
|
||||
ScriptArgs: v.ScriptArgs,
|
||||
HTTP: b.stringVal(v.HTTP),
|
||||
Header: v.Header,
|
||||
Method: b.stringVal(v.Method),
|
||||
|
|
|
@ -312,6 +312,7 @@ type CheckDefinition struct {
|
|||
Token *string `json:"token,omitempty" hcl:"token" mapstructure:"token"`
|
||||
Status *string `json:"status,omitempty" hcl:"status" mapstructure:"status"`
|
||||
Script *string `json:"script,omitempty" hcl:"script" mapstructure:"script"`
|
||||
ScriptArgs []string `json:"args,omitempty" hcl:"args" mapstructure:"args"`
|
||||
HTTP *string `json:"http,omitempty" hcl:"http" mapstructure:"http"`
|
||||
Header map[string][]string `json:"header,omitempty" hcl:"header" mapstructure:"header"`
|
||||
Method *string `json:"method,omitempty" hcl:"method" mapstructure:"method"`
|
||||
|
|
|
@ -1967,6 +1967,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"token": "oo4BCTgJ",
|
||||
"status": "qLykAl5u",
|
||||
"script": "dhGfIF8n",
|
||||
"args": ["f3BemRjy", "e5zgpef7"],
|
||||
"http": "29B93haH",
|
||||
"header": {
|
||||
"hBq0zn1q": [ "2a9o9ZKP", "vKwA5lR6" ],
|
||||
|
@ -1991,6 +1992,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"token": "toO59sh8",
|
||||
"status": "9RlWsXMV",
|
||||
"script": "8qbd8tWw",
|
||||
"args": ["4BAJttck", "4D2NPtTQ"],
|
||||
"http": "dohLcyQ2",
|
||||
"header": {
|
||||
"ZBfTin3L": [ "1sDbEqYG", "lJGASsWK" ],
|
||||
|
@ -2014,6 +2016,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"token": "a3nQzHuy",
|
||||
"status": "irj26nf3",
|
||||
"script": "FJsI1oXt",
|
||||
"args": ["9s526ogY", "gSlOHj1w"],
|
||||
"http": "yzhgsQ7Y",
|
||||
"header": {
|
||||
"zcqwA8dO": [ "qb1zx0DL", "sXCxPFsD" ],
|
||||
|
@ -2139,6 +2142,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"status": "rCvn53TH",
|
||||
"notes": "fti5lfF3",
|
||||
"script": "rtj34nfd",
|
||||
"args": ["16WRUmwS", "QWk7j7ae"],
|
||||
"http": "dl3Fgme3",
|
||||
"header": {
|
||||
"rjm4DEd3": ["2m3m2Fls"],
|
||||
|
@ -2161,6 +2165,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"notes": "yP5nKbW0",
|
||||
"status": "7oLMEyfu",
|
||||
"script": "NlUQ3nTE",
|
||||
"args": ["5wEZtZpv", "0Ihyk8cS"],
|
||||
"http": "KyDjGY9H",
|
||||
"header": {
|
||||
"gv5qefTz": [ "5Olo2pMG", "PvvKWQU5" ],
|
||||
|
@ -2182,6 +2187,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"notes": "SVqApqeM",
|
||||
"status": "XXkVoZXt",
|
||||
"script": "IXLZTM6E",
|
||||
"args": ["wD05Bvao", "rLYB7kQC"],
|
||||
"http": "kyICZsn8",
|
||||
"header": {
|
||||
"4ebP5vL4": [ "G20SrL5Q", "DwPKlMbo" ],
|
||||
|
@ -2214,6 +2220,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"status": "pDQKEhWL",
|
||||
"notes": "Yt8EDLev",
|
||||
"script": "MDu7wjlD",
|
||||
"args": ["81EDZLPa", "bPY5X8xd"],
|
||||
"http": "qzHYvmJO",
|
||||
"header": {
|
||||
"UkpmZ3a3": ["2dfzXuxZ"],
|
||||
|
@ -2245,6 +2252,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"notes": "CQy86DH0",
|
||||
"status": "P0SWDvrk",
|
||||
"script": "6BhLJ7R9",
|
||||
"args": ["EXvkYIuG", "BATOyt6h"],
|
||||
"http": "u97ByEiW",
|
||||
"header": {
|
||||
"MUlReo8L": [ "AUZG7wHG", "gsN0Dc2N" ],
|
||||
|
@ -2266,6 +2274,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"notes": "jKChDOdl",
|
||||
"status": "5qFz6OZn",
|
||||
"script": "PbdxFZ3K",
|
||||
"args": ["NMtYWlT9", "vj74JXsm"],
|
||||
"http": "1LBDJhw4",
|
||||
"header": {
|
||||
"cXPmnv1M": [ "imDqfaBx", "NFxZ1bQe" ],
|
||||
|
@ -2338,6 +2347,11 @@ func TestFullConfig(t *testing.T) {
|
|||
"datacenter": "GyE6jpeW",
|
||||
"key": "j9lF1Tve",
|
||||
"handler": "90N7S4LN"
|
||||
}, {
|
||||
"type": "keyprefix",
|
||||
"datacenter": "fYrl3F5d",
|
||||
"key": "sl3Dffu7",
|
||||
"args": ["dltjDJ2a", "flEa7C2d"]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
|
@ -2383,6 +2397,7 @@ func TestFullConfig(t *testing.T) {
|
|||
token = "oo4BCTgJ"
|
||||
status = "qLykAl5u"
|
||||
script = "dhGfIF8n"
|
||||
args = ["f3BemRjy", "e5zgpef7"]
|
||||
http = "29B93haH"
|
||||
header = {
|
||||
hBq0zn1q = [ "2a9o9ZKP", "vKwA5lR6" ]
|
||||
|
@ -2407,6 +2422,7 @@ func TestFullConfig(t *testing.T) {
|
|||
token = "toO59sh8"
|
||||
status = "9RlWsXMV"
|
||||
script = "8qbd8tWw"
|
||||
args = ["4BAJttck", "4D2NPtTQ"]
|
||||
http = "dohLcyQ2"
|
||||
header = {
|
||||
"ZBfTin3L" = [ "1sDbEqYG", "lJGASsWK" ]
|
||||
|
@ -2430,6 +2446,7 @@ func TestFullConfig(t *testing.T) {
|
|||
token = "a3nQzHuy"
|
||||
status = "irj26nf3"
|
||||
script = "FJsI1oXt"
|
||||
args = ["9s526ogY", "gSlOHj1w"]
|
||||
http = "yzhgsQ7Y"
|
||||
header = {
|
||||
"zcqwA8dO" = [ "qb1zx0DL", "sXCxPFsD" ]
|
||||
|
@ -2555,6 +2572,7 @@ func TestFullConfig(t *testing.T) {
|
|||
status = "rCvn53TH"
|
||||
notes = "fti5lfF3"
|
||||
script = "rtj34nfd"
|
||||
args = ["16WRUmwS", "QWk7j7ae"]
|
||||
http = "dl3Fgme3"
|
||||
header = {
|
||||
rjm4DEd3 = [ "2m3m2Fls" ]
|
||||
|
@ -2577,6 +2595,7 @@ func TestFullConfig(t *testing.T) {
|
|||
notes = "yP5nKbW0"
|
||||
status = "7oLMEyfu"
|
||||
script = "NlUQ3nTE"
|
||||
args = ["5wEZtZpv", "0Ihyk8cS"]
|
||||
http = "KyDjGY9H"
|
||||
header = {
|
||||
"gv5qefTz" = [ "5Olo2pMG", "PvvKWQU5" ]
|
||||
|
@ -2598,6 +2617,7 @@ func TestFullConfig(t *testing.T) {
|
|||
notes = "SVqApqeM"
|
||||
status = "XXkVoZXt"
|
||||
script = "IXLZTM6E"
|
||||
args = ["wD05Bvao", "rLYB7kQC"]
|
||||
http = "kyICZsn8"
|
||||
header = {
|
||||
"4ebP5vL4" = [ "G20SrL5Q", "DwPKlMbo" ]
|
||||
|
@ -2630,6 +2650,7 @@ func TestFullConfig(t *testing.T) {
|
|||
status = "pDQKEhWL"
|
||||
notes = "Yt8EDLev"
|
||||
script = "MDu7wjlD"
|
||||
args = ["81EDZLPa", "bPY5X8xd"]
|
||||
http = "qzHYvmJO"
|
||||
header = {
|
||||
UkpmZ3a3 = [ "2dfzXuxZ" ]
|
||||
|
@ -2661,6 +2682,7 @@ func TestFullConfig(t *testing.T) {
|
|||
notes = "CQy86DH0"
|
||||
status = "P0SWDvrk"
|
||||
script = "6BhLJ7R9"
|
||||
args = ["EXvkYIuG", "BATOyt6h"]
|
||||
http = "u97ByEiW"
|
||||
header = {
|
||||
"MUlReo8L" = [ "AUZG7wHG", "gsN0Dc2N" ]
|
||||
|
@ -2682,6 +2704,7 @@ func TestFullConfig(t *testing.T) {
|
|||
notes = "jKChDOdl"
|
||||
status = "5qFz6OZn"
|
||||
script = "PbdxFZ3K"
|
||||
args = ["NMtYWlT9", "vj74JXsm"]
|
||||
http = "1LBDJhw4"
|
||||
header = {
|
||||
"cXPmnv1M" = [ "imDqfaBx", "NFxZ1bQe" ],
|
||||
|
@ -2753,6 +2776,11 @@ func TestFullConfig(t *testing.T) {
|
|||
datacenter = "GyE6jpeW"
|
||||
key = "j9lF1Tve"
|
||||
handler = "90N7S4LN"
|
||||
}, {
|
||||
type = "keyprefix"
|
||||
datacenter = "fYrl3F5d"
|
||||
key = "sl3Dffu7"
|
||||
args = ["dltjDJ2a", "flEa7C2d"]
|
||||
}]
|
||||
`}
|
||||
|
||||
|
@ -2929,14 +2957,15 @@ func TestFullConfig(t *testing.T) {
|
|||
CertFile: "7s4QAzDk",
|
||||
Checks: []*structs.CheckDefinition{
|
||||
&structs.CheckDefinition{
|
||||
ID: "uAjE6m9Z",
|
||||
Name: "QsZRGpYr",
|
||||
Notes: "VJ7Sk4BY",
|
||||
ServiceID: "lSulPcyz",
|
||||
Token: "toO59sh8",
|
||||
Status: "9RlWsXMV",
|
||||
Script: "8qbd8tWw",
|
||||
HTTP: "dohLcyQ2",
|
||||
ID: "uAjE6m9Z",
|
||||
Name: "QsZRGpYr",
|
||||
Notes: "VJ7Sk4BY",
|
||||
ServiceID: "lSulPcyz",
|
||||
Token: "toO59sh8",
|
||||
Status: "9RlWsXMV",
|
||||
Script: "8qbd8tWw",
|
||||
ScriptArgs: []string{"4BAJttck", "4D2NPtTQ"},
|
||||
HTTP: "dohLcyQ2",
|
||||
Header: map[string][]string{
|
||||
"ZBfTin3L": []string{"1sDbEqYG", "lJGASsWK"},
|
||||
"Ui0nU99X": []string{"LMccm3Qe", "k5H5RggQ"},
|
||||
|
@ -2952,14 +2981,15 @@ func TestFullConfig(t *testing.T) {
|
|||
DeregisterCriticalServiceAfter: 14232 * time.Second,
|
||||
},
|
||||
&structs.CheckDefinition{
|
||||
ID: "Cqq95BhP",
|
||||
Name: "3qXpkS0i",
|
||||
Notes: "sb5qLTex",
|
||||
ServiceID: "CmUUcRna",
|
||||
Token: "a3nQzHuy",
|
||||
Status: "irj26nf3",
|
||||
Script: "FJsI1oXt",
|
||||
HTTP: "yzhgsQ7Y",
|
||||
ID: "Cqq95BhP",
|
||||
Name: "3qXpkS0i",
|
||||
Notes: "sb5qLTex",
|
||||
ServiceID: "CmUUcRna",
|
||||
Token: "a3nQzHuy",
|
||||
Status: "irj26nf3",
|
||||
Script: "FJsI1oXt",
|
||||
ScriptArgs: []string{"9s526ogY", "gSlOHj1w"},
|
||||
HTTP: "yzhgsQ7Y",
|
||||
Header: map[string][]string{
|
||||
"zcqwA8dO": []string{"qb1zx0DL", "sXCxPFsD"},
|
||||
"qxvdnSE9": []string{"6wBPUYdF", "YYh8wtSZ"},
|
||||
|
@ -2975,14 +3005,15 @@ func TestFullConfig(t *testing.T) {
|
|||
DeregisterCriticalServiceAfter: 2366 * time.Second,
|
||||
},
|
||||
&structs.CheckDefinition{
|
||||
ID: "fZaCAXww",
|
||||
Name: "OOM2eo0f",
|
||||
Notes: "zXzXI9Gt",
|
||||
ServiceID: "L8G0QNmR",
|
||||
Token: "oo4BCTgJ",
|
||||
Status: "qLykAl5u",
|
||||
Script: "dhGfIF8n",
|
||||
HTTP: "29B93haH",
|
||||
ID: "fZaCAXww",
|
||||
Name: "OOM2eo0f",
|
||||
Notes: "zXzXI9Gt",
|
||||
ServiceID: "L8G0QNmR",
|
||||
Token: "oo4BCTgJ",
|
||||
Status: "qLykAl5u",
|
||||
Script: "dhGfIF8n",
|
||||
ScriptArgs: []string{"f3BemRjy", "e5zgpef7"},
|
||||
HTTP: "29B93haH",
|
||||
Header: map[string][]string{
|
||||
"hBq0zn1q": {"2a9o9ZKP", "vKwA5lR6"},
|
||||
"f3r6xFtM": {"RyuIdDWv", "QbxEcIUM"},
|
||||
|
@ -3090,12 +3121,13 @@ func TestFullConfig(t *testing.T) {
|
|||
EnableTagOverride: true,
|
||||
Checks: []*structs.CheckType{
|
||||
&structs.CheckType{
|
||||
CheckID: "qmfeO5if",
|
||||
Name: "atDGP7n5",
|
||||
Status: "pDQKEhWL",
|
||||
Notes: "Yt8EDLev",
|
||||
Script: "MDu7wjlD",
|
||||
HTTP: "qzHYvmJO",
|
||||
CheckID: "qmfeO5if",
|
||||
Name: "atDGP7n5",
|
||||
Status: "pDQKEhWL",
|
||||
Notes: "Yt8EDLev",
|
||||
Script: "MDu7wjlD",
|
||||
ScriptArgs: []string{"81EDZLPa", "bPY5X8xd"},
|
||||
HTTP: "qzHYvmJO",
|
||||
Header: map[string][]string{
|
||||
"UkpmZ3a3": {"2dfzXuxZ"},
|
||||
"cVFpko4u": {"gGqdEB6k", "9LsRo22u"},
|
||||
|
@ -3122,12 +3154,13 @@ func TestFullConfig(t *testing.T) {
|
|||
EnableTagOverride: true,
|
||||
Checks: structs.CheckTypes{
|
||||
&structs.CheckType{
|
||||
CheckID: "GTti9hCo",
|
||||
Name: "9OOS93ne",
|
||||
Notes: "CQy86DH0",
|
||||
Status: "P0SWDvrk",
|
||||
Script: "6BhLJ7R9",
|
||||
HTTP: "u97ByEiW",
|
||||
CheckID: "GTti9hCo",
|
||||
Name: "9OOS93ne",
|
||||
Notes: "CQy86DH0",
|
||||
Status: "P0SWDvrk",
|
||||
Script: "6BhLJ7R9",
|
||||
ScriptArgs: []string{"EXvkYIuG", "BATOyt6h"},
|
||||
HTTP: "u97ByEiW",
|
||||
Header: map[string][]string{
|
||||
"MUlReo8L": {"AUZG7wHG", "gsN0Dc2N"},
|
||||
"1UJXjVrT": {"OJgxzTfk", "xZZrFsq7"},
|
||||
|
@ -3143,12 +3176,13 @@ func TestFullConfig(t *testing.T) {
|
|||
DeregisterCriticalServiceAfter: 84282 * time.Second,
|
||||
},
|
||||
&structs.CheckType{
|
||||
CheckID: "UHsDeLxG",
|
||||
Name: "PQSaPWlT",
|
||||
Notes: "jKChDOdl",
|
||||
Status: "5qFz6OZn",
|
||||
Script: "PbdxFZ3K",
|
||||
HTTP: "1LBDJhw4",
|
||||
CheckID: "UHsDeLxG",
|
||||
Name: "PQSaPWlT",
|
||||
Notes: "jKChDOdl",
|
||||
Status: "5qFz6OZn",
|
||||
Script: "PbdxFZ3K",
|
||||
ScriptArgs: []string{"NMtYWlT9", "vj74JXsm"},
|
||||
HTTP: "1LBDJhw4",
|
||||
Header: map[string][]string{
|
||||
"cXPmnv1M": {"imDqfaBx", "NFxZ1bQe"},
|
||||
"vr7wY7CS": {"EtCoNPPL", "9vAarJ5s"},
|
||||
|
@ -3175,12 +3209,13 @@ func TestFullConfig(t *testing.T) {
|
|||
EnableTagOverride: true,
|
||||
Checks: structs.CheckTypes{
|
||||
&structs.CheckType{
|
||||
CheckID: "Zv99e9Ka",
|
||||
Name: "sgV4F7Pk",
|
||||
Notes: "yP5nKbW0",
|
||||
Status: "7oLMEyfu",
|
||||
Script: "NlUQ3nTE",
|
||||
HTTP: "KyDjGY9H",
|
||||
CheckID: "Zv99e9Ka",
|
||||
Name: "sgV4F7Pk",
|
||||
Notes: "yP5nKbW0",
|
||||
Status: "7oLMEyfu",
|
||||
Script: "NlUQ3nTE",
|
||||
ScriptArgs: []string{"5wEZtZpv", "0Ihyk8cS"},
|
||||
HTTP: "KyDjGY9H",
|
||||
Header: map[string][]string{
|
||||
"gv5qefTz": {"5Olo2pMG", "PvvKWQU5"},
|
||||
"SHOVq1Vv": {"jntFhyym", "GYJh32pp"},
|
||||
|
@ -3196,12 +3231,13 @@ func TestFullConfig(t *testing.T) {
|
|||
DeregisterCriticalServiceAfter: 8482 * time.Second,
|
||||
},
|
||||
&structs.CheckType{
|
||||
CheckID: "G79O6Mpr",
|
||||
Name: "IEqrzrsd",
|
||||
Notes: "SVqApqeM",
|
||||
Status: "XXkVoZXt",
|
||||
Script: "IXLZTM6E",
|
||||
HTTP: "kyICZsn8",
|
||||
CheckID: "G79O6Mpr",
|
||||
Name: "IEqrzrsd",
|
||||
Notes: "SVqApqeM",
|
||||
Status: "XXkVoZXt",
|
||||
Script: "IXLZTM6E",
|
||||
ScriptArgs: []string{"wD05Bvao", "rLYB7kQC"},
|
||||
HTTP: "kyICZsn8",
|
||||
Header: map[string][]string{
|
||||
"4ebP5vL4": {"G20SrL5Q", "DwPKlMbo"},
|
||||
"p2UI34Qz": {"UsG1D0Qh", "NHhRiB6s"},
|
||||
|
@ -3217,12 +3253,13 @@ func TestFullConfig(t *testing.T) {
|
|||
DeregisterCriticalServiceAfter: 4992 * time.Second,
|
||||
},
|
||||
&structs.CheckType{
|
||||
CheckID: "RMi85Dv8",
|
||||
Name: "iehanzuq",
|
||||
Status: "rCvn53TH",
|
||||
Notes: "fti5lfF3",
|
||||
Script: "rtj34nfd",
|
||||
HTTP: "dl3Fgme3",
|
||||
CheckID: "RMi85Dv8",
|
||||
Name: "iehanzuq",
|
||||
Status: "rCvn53TH",
|
||||
Notes: "fti5lfF3",
|
||||
Script: "rtj34nfd",
|
||||
ScriptArgs: []string{"16WRUmwS", "QWk7j7ae"},
|
||||
HTTP: "dl3Fgme3",
|
||||
Header: map[string][]string{
|
||||
"rjm4DEd3": {"2m3m2Fls"},
|
||||
"l4HwQ112": {"fk56MNlo", "dhLK56aZ"},
|
||||
|
@ -3297,6 +3334,12 @@ func TestFullConfig(t *testing.T) {
|
|||
"key": "j9lF1Tve",
|
||||
"handler": "90N7S4LN",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"type": "keyprefix",
|
||||
"datacenter": "fYrl3F5d",
|
||||
"key": "sl3Dffu7",
|
||||
"args": []interface{}{"dltjDJ2a", "flEa7C2d"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -3632,6 +3675,7 @@ func TestSanitize(t *testing.T) {
|
|||
"Name": "zoo",
|
||||
"Notes": "",
|
||||
"Script": "",
|
||||
"ScriptArgs": [],
|
||||
"ServiceID": "",
|
||||
"Shell": "",
|
||||
"Status": "",
|
||||
|
@ -3755,6 +3799,7 @@ func TestSanitize(t *testing.T) {
|
|||
"Name": "blurb",
|
||||
"Notes": "",
|
||||
"Script": "",
|
||||
"ScriptArgs": [],
|
||||
"Shell": "",
|
||||
"Status": "",
|
||||
"TCP": "",
|
||||
|
|
|
@ -49,6 +49,7 @@ type remoteExecEvent struct {
|
|||
// It is stored in the KV store
|
||||
type remoteExecSpec struct {
|
||||
Command string
|
||||
Args []string
|
||||
Script []byte
|
||||
Wait time.Duration
|
||||
}
|
||||
|
@ -160,7 +161,13 @@ func (a *Agent) handleRemoteExec(msg *UserEvent) {
|
|||
|
||||
// Create the exec.Cmd
|
||||
a.logger.Printf("[INFO] agent: remote exec '%s'", script)
|
||||
cmd, err := ExecScript(script)
|
||||
var cmd *exec.Cmd
|
||||
var err error
|
||||
if len(spec.Args) > 0 {
|
||||
cmd, err = ExecSubprocess(spec.Args)
|
||||
} else {
|
||||
cmd, err = ExecScript(script)
|
||||
}
|
||||
if err != nil {
|
||||
a.logger.Printf("[DEBUG] agent: failed to start remote exec: %v", err)
|
||||
exitCode = 255
|
||||
|
@ -178,8 +185,7 @@ func (a *Agent) handleRemoteExec(msg *UserEvent) {
|
|||
cmd.Stderr = writer
|
||||
|
||||
// Start execution
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
if err := cmd.Start(); err != nil {
|
||||
a.logger.Printf("[DEBUG] agent: failed to start remote exec: %v", err)
|
||||
exitCode = 255
|
||||
return
|
||||
|
|
|
@ -22,6 +22,7 @@ type CheckDefinition struct {
|
|||
// ID (CheckID), Name, Status, Notes
|
||||
//
|
||||
Script string
|
||||
ScriptArgs []string
|
||||
HTTP string
|
||||
Header map[string][]string
|
||||
Method string
|
||||
|
@ -61,6 +62,7 @@ func (c *CheckDefinition) CheckType() *CheckType {
|
|||
Notes: c.Notes,
|
||||
|
||||
Script: c.Script,
|
||||
ScriptArgs: c.ScriptArgs,
|
||||
HTTP: c.HTTP,
|
||||
Header: c.Header,
|
||||
Method: c.Method,
|
||||
|
|
|
@ -24,6 +24,7 @@ type CheckType struct {
|
|||
// Update CheckDefinition when adding fields here
|
||||
|
||||
Script string
|
||||
ScriptArgs []string
|
||||
HTTP string
|
||||
Header map[string][]string
|
||||
Method string
|
||||
|
@ -49,7 +50,7 @@ func (c *CheckType) Valid() bool {
|
|||
|
||||
// IsScript checks if this is a check that execs some kind of script.
|
||||
func (c *CheckType) IsScript() bool {
|
||||
return c.Script != ""
|
||||
return c.Script != "" || len(c.ScriptArgs) > 0
|
||||
}
|
||||
|
||||
// IsTTL checks if this is a TTL type
|
||||
|
@ -59,7 +60,7 @@ func (c *CheckType) IsTTL() bool {
|
|||
|
||||
// IsMonitor checks if this is a Monitor type
|
||||
func (c *CheckType) IsMonitor() bool {
|
||||
return c.Script != "" && c.DockerContainerID == "" && c.Interval != 0
|
||||
return c.IsScript() && c.DockerContainerID == "" && c.Interval != 0
|
||||
}
|
||||
|
||||
// IsHTTP checks if this is a HTTP type
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
osuser "os/user"
|
||||
"strconv"
|
||||
"time"
|
||||
|
@ -113,3 +115,34 @@ GROUP:
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecSubprocess returns a command to execute a subprocess directly.
|
||||
func ExecSubprocess(args []string) (*exec.Cmd, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, fmt.Errorf("need an executable to run")
|
||||
}
|
||||
|
||||
return exec.Command(args[0], args[1:]...), nil
|
||||
}
|
||||
|
||||
// ForwardSignals will fire up a goroutine to forward signals to the given
|
||||
// subprocess until the shutdown channel is closed.
|
||||
func ForwardSignals(cmd *exec.Cmd, logFn func(error), shutdownCh <-chan struct{}) {
|
||||
go func() {
|
||||
signalCh := make(chan os.Signal, 10)
|
||||
signal.Notify(signalCh, os.Interrupt, os.Kill)
|
||||
defer signal.Stop(signalCh)
|
||||
|
||||
for {
|
||||
select {
|
||||
case sig := <-signalCh:
|
||||
if err := cmd.Process.Signal(sig); err != nil {
|
||||
logFn(fmt.Errorf("failed to send signal %q: %v", sig, err))
|
||||
}
|
||||
|
||||
case <-shutdownCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"os/exec"
|
||||
)
|
||||
|
||||
// ExecScript returns a command to execute a script
|
||||
// ExecScript returns a command to execute a script through a shell.
|
||||
func ExecScript(script string) (*exec.Cmd, error) {
|
||||
shell := "/bin/sh"
|
||||
if other := os.Getenv("SHELL"); other != "" {
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
// ExecScript returns a command to execute a script
|
||||
// ExecScript returns a command to execute a script through a shell.
|
||||
func ExecScript(script string) (*exec.Cmd, error) {
|
||||
shell := "cmd"
|
||||
if other := os.Getenv("SHELL"); other != "" {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/armon/circbuf"
|
||||
|
@ -21,16 +22,36 @@ const (
|
|||
)
|
||||
|
||||
// makeWatchHandler returns a handler for the given watch
|
||||
func makeWatchHandler(logOutput io.Writer, params interface{}) watch.HandlerFunc {
|
||||
script := params.(string)
|
||||
func makeWatchHandler(logOutput io.Writer, handler interface{}) watch.HandlerFunc {
|
||||
var args []string
|
||||
var script string
|
||||
|
||||
// Figure out whether to run in shell or raw subprocess mode
|
||||
switch h := handler.(type) {
|
||||
case string:
|
||||
script = h
|
||||
case []string:
|
||||
args = h
|
||||
default:
|
||||
panic(fmt.Errorf("unknown handler type %T", handler))
|
||||
}
|
||||
|
||||
logger := log.New(logOutput, "", log.LstdFlags)
|
||||
fn := func(idx uint64, data interface{}) {
|
||||
// Create the command
|
||||
cmd, err := ExecScript(script)
|
||||
var cmd *exec.Cmd
|
||||
var err error
|
||||
|
||||
if len(args) > 0 {
|
||||
cmd, err = ExecSubprocess(args)
|
||||
} else {
|
||||
cmd, err = ExecScript(script)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Printf("[ERR] agent: Failed to setup watch: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Env = append(os.Environ(),
|
||||
"CONSUL_INDEX="+strconv.FormatUint(idx, 10),
|
||||
)
|
||||
|
@ -44,14 +65,14 @@ func makeWatchHandler(logOutput io.Writer, params interface{}) watch.HandlerFunc
|
|||
var inp bytes.Buffer
|
||||
enc := json.NewEncoder(&inp)
|
||||
if err := enc.Encode(data); err != nil {
|
||||
logger.Printf("[ERR] agent: Failed to encode data for watch '%s': %v", script, err)
|
||||
logger.Printf("[ERR] agent: Failed to encode data for watch '%v': %v", handler, err)
|
||||
return
|
||||
}
|
||||
cmd.Stdin = &inp
|
||||
|
||||
// Run the handler
|
||||
if err := cmd.Run(); err != nil {
|
||||
logger.Printf("[ERR] agent: Failed to invoke watch handler '%s': %v", script, err)
|
||||
logger.Printf("[ERR] agent: Failed to run watch handler '%v': %v", handler, err)
|
||||
}
|
||||
|
||||
// Get the output, add a message about truncation
|
||||
|
@ -62,7 +83,7 @@ func makeWatchHandler(logOutput io.Writer, params interface{}) watch.HandlerFunc
|
|||
}
|
||||
|
||||
// Log the output
|
||||
logger.Printf("[DEBUG] agent: watch handler '%s' output: %s", script, outputStr)
|
||||
logger.Printf("[DEBUG] agent: watch handler '%v' output: %s", handler, outputStr)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
|
|
@ -363,7 +363,7 @@ func (cmd *AgentCommand) run(args []string) int {
|
|||
logGate.Flush()
|
||||
|
||||
// wait for signal
|
||||
signalCh := make(chan os.Signal, 4)
|
||||
signalCh := make(chan os.Signal, 10)
|
||||
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
||||
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGPIPE)
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ const (
|
|||
// rExecConf is used to pass around configuration
|
||||
type rExecConf struct {
|
||||
prefix string
|
||||
shell bool
|
||||
|
||||
foreignDC bool
|
||||
localDC string
|
||||
|
@ -66,6 +67,7 @@ type rExecConf struct {
|
|||
replWait time.Duration
|
||||
|
||||
cmd string
|
||||
args []string
|
||||
script []byte
|
||||
|
||||
verbose bool
|
||||
|
@ -83,6 +85,9 @@ type rExecSpec struct {
|
|||
// Command is a single command to run directly in the shell
|
||||
Command string `json:",omitempty"`
|
||||
|
||||
// Args is the list of arguments to run the subprocess directly
|
||||
Args []string `json:",omitempty"`
|
||||
|
||||
// Script should be spilled to a file and executed
|
||||
Script []byte `json:",omitempty"`
|
||||
|
||||
|
@ -134,6 +139,8 @@ func (c *ExecCommand) Run(args []string) int {
|
|||
"Regular expression to filter on service tags. Must be used with -service.")
|
||||
f.StringVar(&c.conf.prefix, "prefix", rExecPrefix,
|
||||
"Prefix in the KV store to use for request data.")
|
||||
f.BoolVar(&c.conf.shell, "shell", true,
|
||||
"Use a shell to run the command.")
|
||||
f.DurationVar(&c.conf.wait, "wait", rExecQuietWait,
|
||||
"Period to wait with no responses before terminating execution.")
|
||||
f.DurationVar(&c.conf.replWait, "wait-repl", rExecReplicationWait,
|
||||
|
@ -151,6 +158,11 @@ func (c *ExecCommand) Run(args []string) int {
|
|||
|
||||
// If there is no command, read stdin for a script input
|
||||
if c.conf.cmd == "-" {
|
||||
if !c.conf.shell {
|
||||
c.UI.Error("Cannot configure -shell=false when reading from stdin")
|
||||
return 1
|
||||
}
|
||||
|
||||
c.conf.cmd = ""
|
||||
var buf bytes.Buffer
|
||||
_, err := io.Copy(&buf, os.Stdin)
|
||||
|
@ -161,10 +173,13 @@ func (c *ExecCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
c.conf.script = buf.Bytes()
|
||||
} else if !c.conf.shell {
|
||||
c.conf.cmd = ""
|
||||
c.conf.args = f.Args()
|
||||
}
|
||||
|
||||
// Ensure we have a command or script
|
||||
if c.conf.cmd == "" && len(c.conf.script) == 0 {
|
||||
if c.conf.cmd == "" && len(c.conf.script) == 0 && len(c.conf.args) == 0 {
|
||||
c.UI.Error("Must specify a command to execute")
|
||||
c.UI.Error("")
|
||||
c.UI.Error(c.Help())
|
||||
|
@ -545,6 +560,7 @@ func (c *ExecCommand) destroySession() error {
|
|||
func (c *ExecCommand) makeRExecSpec() ([]byte, error) {
|
||||
spec := &rExecSpec{
|
||||
Command: c.conf.cmd,
|
||||
Args: c.conf.args,
|
||||
Script: c.conf.script,
|
||||
Wait: c.conf.wait,
|
||||
}
|
||||
|
|
|
@ -34,7 +34,27 @@ func TestExecCommandRun(t *testing.T) {
|
|||
defer a.Shutdown()
|
||||
|
||||
ui, c := testExecCommand(t)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-wait=500ms", "uptime"}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-wait=1s", "uptime"}
|
||||
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d. Error:%#v (std)Output:%#v", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
|
||||
}
|
||||
|
||||
if !strings.Contains(ui.OutputWriter.String(), "load") {
|
||||
t.Fatalf("bad: %#v", ui.OutputWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecCommandRun_NoShell(t *testing.T) {
|
||||
t.Parallel()
|
||||
a := agent.NewTestAgent(t.Name(), `
|
||||
disable_remote_exec = false
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
|
||||
ui, c := testExecCommand(t)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-shell=false", "-wait=1s", "uptime"}
|
||||
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
|
|
|
@ -3,6 +3,7 @@ package command
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -79,6 +80,7 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
|
|||
var name string
|
||||
var passStdin bool
|
||||
var propagateChildCode bool
|
||||
var shell bool
|
||||
var timeout time.Duration
|
||||
|
||||
f := c.BaseCommand.NewFlagSet(c)
|
||||
|
@ -101,6 +103,9 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
|
|||
"is generated based on the provided child command.")
|
||||
f.BoolVar(&passStdin, "pass-stdin", false,
|
||||
"Pass stdin to the child process.")
|
||||
f.BoolVar(&shell, "shell", true,
|
||||
"Use a shell to run the command (can set a custom shell via the SHELL "+
|
||||
"environment variable).")
|
||||
f.DurationVar(&timeout, "timeout", 0,
|
||||
"Maximum amount of time to wait to acquire the lock, specified as a "+
|
||||
"duration like \"1s\" or \"3h\". The default value is 0.")
|
||||
|
@ -129,7 +134,6 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
|
|||
}
|
||||
prefix := extra[0]
|
||||
prefix = strings.TrimPrefix(prefix, "/")
|
||||
script := strings.Join(extra[1:], " ")
|
||||
|
||||
if timeout < 0 {
|
||||
c.UI.Error("Timeout must be positive")
|
||||
|
@ -138,7 +142,7 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
|
|||
|
||||
// Calculate a session name if none provided
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("Consul lock for '%s' at '%s'", script, prefix)
|
||||
name = fmt.Sprintf("Consul lock for '%s' at '%s'", strings.Join(extra[1:], " "), prefix)
|
||||
}
|
||||
|
||||
// Calculate oneshot
|
||||
|
@ -200,7 +204,7 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
|
|||
// Start the child process
|
||||
childErr = make(chan error, 1)
|
||||
go func() {
|
||||
childErr <- c.startChild(script, passStdin)
|
||||
childErr <- c.startChild(f.Args()[1:], passStdin, shell)
|
||||
}()
|
||||
|
||||
// Monitor for shutdown, child termination, or lock loss
|
||||
|
@ -335,12 +339,19 @@ func (c *LockCommand) setupSemaphore(client *api.Client, limit int, prefix, name
|
|||
|
||||
// startChild is a long running routine used to start and
|
||||
// wait for the child process to exit.
|
||||
func (c *LockCommand) startChild(script string, passStdin bool) error {
|
||||
func (c *LockCommand) startChild(args []string, passStdin, shell bool) error {
|
||||
if c.verbose {
|
||||
c.UI.Info(fmt.Sprintf("Starting handler '%s'", script))
|
||||
c.UI.Info("Starting handler")
|
||||
}
|
||||
|
||||
// Create the command
|
||||
cmd, err := agent.ExecScript(script)
|
||||
var cmd *exec.Cmd
|
||||
var err error
|
||||
if !shell {
|
||||
cmd, err = agent.ExecSubprocess(args)
|
||||
} else {
|
||||
cmd, err = agent.ExecScript(strings.Join(args, " "))
|
||||
}
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error executing handler: %s", err))
|
||||
return err
|
||||
|
@ -369,6 +380,14 @@ func (c *LockCommand) startChild(script string, passStdin bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Set up signal forwarding.
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
logFn := func(err error) {
|
||||
c.UI.Error(fmt.Sprintf("Warning, could not forward signal: %s", err))
|
||||
}
|
||||
agent.ForwardSignals(cmd, logFn, doneCh)
|
||||
|
||||
// Setup the child info
|
||||
c.child = cmd.Process
|
||||
c.childLock.Unlock()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -53,8 +52,28 @@ func TestLockCommand_Run(t *testing.T) {
|
|||
|
||||
ui, c := testLockCommand(t)
|
||||
filePath := filepath.Join(a.Config.DataDir, "test_touch")
|
||||
touchCmd := fmt.Sprintf("touch '%s'", filePath)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "test/prefix", touchCmd}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "test/prefix", "touch", filePath}
|
||||
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
// Check for the file
|
||||
_, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLockCommand_Run_NoShell(t *testing.T) {
|
||||
t.Parallel()
|
||||
a := agent.NewTestAgent(t.Name(), ``)
|
||||
defer a.Shutdown()
|
||||
|
||||
ui, c := testLockCommand(t)
|
||||
filePath := filepath.Join(a.Config.DataDir, "test_touch")
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-shell=false", "test/prefix", "touch", filePath}
|
||||
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
|
@ -75,8 +94,7 @@ func TestLockCommand_Try_Lock(t *testing.T) {
|
|||
|
||||
ui, c := testLockCommand(t)
|
||||
filePath := filepath.Join(a.Config.DataDir, "test_touch")
|
||||
touchCmd := fmt.Sprintf("touch '%s'", filePath)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-try=10s", "test/prefix", touchCmd}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-try=10s", "test/prefix", "touch", filePath}
|
||||
|
||||
// Run the command.
|
||||
var lu *LockUnlock
|
||||
|
@ -106,8 +124,7 @@ func TestLockCommand_Try_Semaphore(t *testing.T) {
|
|||
|
||||
ui, c := testLockCommand(t)
|
||||
filePath := filepath.Join(a.Config.DataDir, "test_touch")
|
||||
touchCmd := fmt.Sprintf("touch '%s'", filePath)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-n=3", "-try=10s", "test/prefix", touchCmd}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-n=3", "-try=10s", "test/prefix", "touch", filePath}
|
||||
|
||||
// Run the command.
|
||||
var lu *LockUnlock
|
||||
|
@ -137,8 +154,7 @@ func TestLockCommand_MonitorRetry_Lock_Default(t *testing.T) {
|
|||
|
||||
ui, c := testLockCommand(t)
|
||||
filePath := filepath.Join(a.Config.DataDir, "test_touch")
|
||||
touchCmd := fmt.Sprintf("touch '%s'", filePath)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "test/prefix", touchCmd}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "test/prefix", "touch", filePath}
|
||||
|
||||
// Run the command.
|
||||
var lu *LockUnlock
|
||||
|
@ -169,8 +185,7 @@ func TestLockCommand_MonitorRetry_Semaphore_Default(t *testing.T) {
|
|||
|
||||
ui, c := testLockCommand(t)
|
||||
filePath := filepath.Join(a.Config.DataDir, "test_touch")
|
||||
touchCmd := fmt.Sprintf("touch '%s'", filePath)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-n=3", "test/prefix", touchCmd}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-n=3", "test/prefix", "touch", filePath}
|
||||
|
||||
// Run the command.
|
||||
var lu *LockUnlock
|
||||
|
@ -201,8 +216,7 @@ func TestLockCommand_MonitorRetry_Lock_Arg(t *testing.T) {
|
|||
|
||||
ui, c := testLockCommand(t)
|
||||
filePath := filepath.Join(a.Config.DataDir, "test_touch")
|
||||
touchCmd := fmt.Sprintf("touch '%s'", filePath)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-monitor-retry=9", "test/prefix", touchCmd}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-monitor-retry=9", "test/prefix", "touch", filePath}
|
||||
|
||||
// Run the command.
|
||||
var lu *LockUnlock
|
||||
|
@ -233,8 +247,7 @@ func TestLockCommand_MonitorRetry_Semaphore_Arg(t *testing.T) {
|
|||
|
||||
ui, c := testLockCommand(t)
|
||||
filePath := filepath.Join(a.Config.DataDir, "test_touch")
|
||||
touchCmd := fmt.Sprintf("touch '%s'", filePath)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-n=3", "-monitor-retry=9", "test/prefix", touchCmd}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-n=3", "-monitor-retry=9", "test/prefix", "touch", filePath}
|
||||
|
||||
// Run the command.
|
||||
var lu *LockUnlock
|
||||
|
@ -265,7 +278,7 @@ func TestLockCommand_ChildExitCode(t *testing.T) {
|
|||
|
||||
t.Run("clean exit", func(t *testing.T) {
|
||||
_, c := testLockCommand(t)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-child-exit-code", "test/prefix", "exit 0"}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-child-exit-code", "test/prefix", "sh", "-c", "exit", "0"}
|
||||
if got, want := c.Run(args), 0; got != want {
|
||||
t.Fatalf("got %d want %d", got, want)
|
||||
}
|
||||
|
@ -273,7 +286,7 @@ func TestLockCommand_ChildExitCode(t *testing.T) {
|
|||
|
||||
t.Run("error exit", func(t *testing.T) {
|
||||
_, c := testLockCommand(t)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-child-exit-code", "test/prefix", "exit 1"}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "-child-exit-code", "test/prefix", "exit", "1"}
|
||||
if got, want := c.Run(args), 2; got != want {
|
||||
t.Fatalf("got %d want %d", got, want)
|
||||
}
|
||||
|
@ -281,7 +294,7 @@ func TestLockCommand_ChildExitCode(t *testing.T) {
|
|||
|
||||
t.Run("not propagated", func(t *testing.T) {
|
||||
_, c := testLockCommand(t)
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "test/prefix", "exit 1"}
|
||||
args := []string{"-http-addr=" + a.HTTPAddr(), "test/prefix", "sh", "-c", "exit", "1"}
|
||||
if got, want := c.Run(args), 0; got != want {
|
||||
t.Fatalf("got %d want %d", got, want)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -37,6 +38,7 @@ Usage: consul watch [options] [child...]
|
|||
|
||||
func (c *WatchCommand) Run(args []string) int {
|
||||
var watchType, key, prefix, service, tag, passingOnly, state, name string
|
||||
var shell bool
|
||||
|
||||
f := c.BaseCommand.NewFlagSet(c)
|
||||
f.StringVar(&watchType, "type", "",
|
||||
|
@ -54,6 +56,9 @@ func (c *WatchCommand) Run(args []string) int {
|
|||
f.StringVar(&passingOnly, "passingonly", "",
|
||||
"Specifies if only hosts passing all checks are displayed. "+
|
||||
"Optional for 'service' type, must be one of `[true|false]`. Defaults false.")
|
||||
f.BoolVar(&shell, "shell", true,
|
||||
"Use a shell to run the command (can set a custom shell via the SHELL "+
|
||||
"environment variable).")
|
||||
f.StringVar(&state, "state", "",
|
||||
"Specifies the states to watch. Optional for 'checks' type.")
|
||||
f.StringVar(&name, "name", "",
|
||||
|
@ -71,9 +76,6 @@ func (c *WatchCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
// Grab the script to execute if any
|
||||
script := strings.Join(f.Args(), " ")
|
||||
|
||||
// Compile the watch parameters
|
||||
params := make(map[string]interface{})
|
||||
if watchType != "" {
|
||||
|
@ -140,7 +142,7 @@ func (c *WatchCommand) Run(args []string) int {
|
|||
// 0: false
|
||||
// 1: true
|
||||
errExit := 0
|
||||
if script == "" {
|
||||
if len(f.Args()) == 0 {
|
||||
wp.Handler = func(idx uint64, data interface{}) {
|
||||
defer wp.Stop()
|
||||
buf, err := json.MarshalIndent(data, "", " ")
|
||||
|
@ -152,10 +154,21 @@ func (c *WatchCommand) Run(args []string) int {
|
|||
}
|
||||
} else {
|
||||
wp.Handler = func(idx uint64, data interface{}) {
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
logFn := func(err error) {
|
||||
c.UI.Error(fmt.Sprintf("Warning, could not forward signal: %s", err))
|
||||
}
|
||||
|
||||
// Create the command
|
||||
var buf bytes.Buffer
|
||||
var err error
|
||||
cmd, err := agent.ExecScript(script)
|
||||
var cmd *exec.Cmd
|
||||
if !shell {
|
||||
cmd, err = agent.ExecSubprocess(f.Args())
|
||||
} else {
|
||||
cmd, err = agent.ExecScript(strings.Join(f.Args(), " "))
|
||||
}
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error executing handler: %s", err))
|
||||
goto ERR
|
||||
|
@ -173,8 +186,17 @@ func (c *WatchCommand) Run(args []string) int {
|
|||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// Run the handler
|
||||
if err := cmd.Run(); err != nil {
|
||||
// Run the handler.
|
||||
if err := cmd.Start(); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error starting handler: %s", err))
|
||||
goto ERR
|
||||
}
|
||||
|
||||
// Set up signal forwarding.
|
||||
agent.ForwardSignals(cmd, logFn, doneCh)
|
||||
|
||||
// Wait for the handler to complete.
|
||||
if err := cmd.Wait(); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error executing handler: %s", err))
|
||||
goto ERR
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ A script check:
|
|||
"check": {
|
||||
"id": "mem-util",
|
||||
"name": "Memory utilization",
|
||||
"script": "/usr/local/bin/check_mem.py",
|
||||
"args": ["/usr/local/bin/check_mem.py", "-limit", "256MB"],
|
||||
"interval": "10s",
|
||||
"timeout": "1s"
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ A Docker check:
|
|||
"name": "Memory utilization",
|
||||
"docker_container_id": "f972c95ebf0e",
|
||||
"shell": "/bin/bash",
|
||||
"script": "/usr/local/bin/check_mem.py",
|
||||
"args": ["/usr/local/bin/check_mem.py"],
|
||||
"interval": "10s"
|
||||
}
|
||||
}
|
||||
|
@ -218,6 +218,11 @@ In Consul 0.9.0 and later, the agent must be configured with
|
|||
[`enable_script_checks`](/docs/agent/options.html#_enable_script_checks) set to `true`
|
||||
in order to enable script checks.
|
||||
|
||||
Prior to Consul 1.0, checks used a single `script` field to define the command to run, and
|
||||
would always run in a shell. In Consul 1.0, the `args` array was added so that checks can be
|
||||
run without a shell. The `script` field is deprecated, and you should include the shell in
|
||||
the `args` to run under a shell, eg. `"args": ["sh", "-c", "..."]`.
|
||||
|
||||
## Initial Health Check Status
|
||||
|
||||
By default, when checks are registered against a Consul agent, the state is set
|
||||
|
@ -231,7 +236,7 @@ health check definition, like so:
|
|||
{
|
||||
"check": {
|
||||
"id": "mem",
|
||||
"script": "/bin/check_mem",
|
||||
"args": ["/bin/check_mem", "-limit", "256MB"],
|
||||
"interval": "10s",
|
||||
"status": "passing"
|
||||
}
|
||||
|
@ -274,7 +279,7 @@ key in your configuration file.
|
|||
{
|
||||
"id": "chk1",
|
||||
"name": "mem",
|
||||
"script": "/bin/check_mem",
|
||||
"args": ["/bin/check_mem", "-limit", "256MB"],
|
||||
"interval": "5s"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -45,6 +45,11 @@ Additionally, the `CONSUL_INDEX` environment variable will be set.
|
|||
This maps to the `X-Consul-Index` value in responses from the
|
||||
[HTTP API](/api/index.html).
|
||||
|
||||
Prior to Consul 1.0, watches used a single `handler` field to define the command to run, and
|
||||
would always run in a shell. In Consul 1.0, the `args` array was added so that handlers can be
|
||||
run without a shell. The `handler` field is deprecated, and you should include the shell in
|
||||
the `args` to run under a shell, eg. `"args": ["sh", "-c", "..."]`.
|
||||
|
||||
## Global Parameters
|
||||
|
||||
In addition to the parameters supported by each option type, there
|
||||
|
@ -52,7 +57,8 @@ are a few global parameters that all watches support:
|
|||
|
||||
* `datacenter` - Can be provided to override the agent's default datacenter.
|
||||
* `token` - Can be provided to override the agent's default ACL token.
|
||||
* `handler` - The handler to invoke when the data view updates.
|
||||
* `args` - The handler subprocess and arguments to invoke when the data view updates.
|
||||
* `handler` - The handler shell command to invoke when the data view updates.
|
||||
|
||||
## Watch Types
|
||||
|
||||
|
@ -80,7 +86,7 @@ Here is an example configuration:
|
|||
{
|
||||
"type": "key",
|
||||
"key": "foo/bar/baz",
|
||||
"handler": "/usr/bin/my-key-handler.sh"
|
||||
"args": ["/usr/bin/my-service-handler.sh", "-redis"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -117,7 +123,7 @@ Here is an example configuration:
|
|||
{
|
||||
"type": "keyprefix",
|
||||
"prefix": "foo/",
|
||||
"handler": "/usr/bin/my-prefix-handler.sh"
|
||||
"args": ["/usr/bin/my-service-handler.sh", "-redis"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -231,7 +237,7 @@ Here is an example configuration:
|
|||
{
|
||||
"type": "service",
|
||||
"service": "redis",
|
||||
"handler": "/usr/bin/my-service-handler.sh"
|
||||
"args": ["/usr/bin/my-service-handler.sh", "-redis"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -322,13 +328,13 @@ Here is an example configuration:
|
|||
{
|
||||
"type": "event",
|
||||
"name": "web-deploy",
|
||||
"handler": "/usr/bin/my-deploy-handler.sh"
|
||||
"args": ["/usr/bin/my-service-handler.sh", "-web-deploy"]
|
||||
}
|
||||
```
|
||||
|
||||
Or, using the watch command:
|
||||
|
||||
$ consul watch -type=event -name=web-deploy /usr/bin/my-deploy-handler.sh
|
||||
$ consul watch -type=event -name=web-deploy /usr/bin/my-deploy-handler.sh -web-deploy
|
||||
|
||||
An example of the output of this command:
|
||||
|
||||
|
|
|
@ -61,6 +61,8 @@ completion as a script to evaluate.
|
|||
|
||||
* `-service` - Regular expression to filter to only nodes with matching services.
|
||||
|
||||
* `-shell` - Optional, use a shell to run the command. The default value is true.
|
||||
|
||||
* `-tag` - Regular expression to filter to only nodes with a service that has
|
||||
a matching tag. This must be used with `-service`. As an example, you may
|
||||
do `-service mysql -tag secondary`.
|
||||
|
|
|
@ -69,9 +69,12 @@ Windows has no POSIX compatible notion for `SIGTERM`.
|
|||
* `-name` - Optional name to associate with the underlying session.
|
||||
If not provided, one is generated based on the child command.
|
||||
|
||||
* `-shell` - Optional, use a shell to run the command (can set a custom shell via the
|
||||
SHELL environment variable). The default value is true.
|
||||
|
||||
* `-pass-stdin` - Pass stdin to child process.
|
||||
|
||||
* `timeout` - Maximum amount of time to wait to acquire the lock, specified
|
||||
* `-timeout` - Maximum amount of time to wait to acquire the lock, specified
|
||||
as a duration like `1s` or `3h`. The default value is 0.
|
||||
|
||||
* `-try` - Attempt to acquire the lock up to the given timeout. The timeout is a
|
||||
|
@ -87,4 +90,4 @@ default to `/bin/sh`. It should be noted that not all shells terminate child
|
|||
processes when they receive `SIGTERM`. Under Ubuntu, `/bin/sh` is linked to `dash`,
|
||||
which does **not** terminate its children. In order to ensure that child processes
|
||||
are killed when the lock is lost, be sure to set the `SHELL` environment variable
|
||||
appropriately.
|
||||
appropriately, or run without a shell by setting `-shell=false`.
|
||||
|
|
|
@ -45,6 +45,9 @@ or optionally provided. There is more documentation on watch
|
|||
|
||||
* `-service` - Service to watch. Required for `service` type, optional for `checks` type.
|
||||
|
||||
* `-shell` - Optional, use a shell to run the command (can set a custom shell via the
|
||||
SHELL environment variable). The default value is true.
|
||||
|
||||
* `-state` - Check state to filter on. Optional for `checks` type.
|
||||
|
||||
* `-tag` - Service tag to filter on. Optional for `service` type.
|
||||
|
|
Loading…
Reference in New Issue