Skip to content

Commit

Permalink
Merge branch 'watch_run'
Browse files Browse the repository at this point in the history
  • Loading branch information
justone committed Jun 8, 2016
2 parents 31a3bd6 + d8d054f commit b94c4eb
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 85 deletions.
88 changes: 3 additions & 85 deletions notify.go
Expand Up @@ -2,19 +2,12 @@ package main

import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"

"github.com/Sirupsen/logrus"
"github.com/justone/pmb/api"
)

type NotifyCommand struct {
Message string `short:"m" long:"message" description:"Message to send."`
Pid int `short:"p" long:"pid" description:"Notify after PID exits."`
Level float64 `short:"l" long:"level" description:"Notification level (1-5), higher numbers indictate higher importance" default:"3"`
}

Expand All @@ -23,27 +16,18 @@ var notifyCommand NotifyCommand
func (x *NotifyCommand) Execute(args []string) error {
bus := pmb.GetPMB(globalOptions.Primary)

if len(args) == 0 && len(notifyCommand.Message) == 0 && notifyCommand.Pid == 0 {
if len(notifyCommand.Message) == 0 {
return fmt.Errorf("A message is required")
}

// fail fast if pid isn't found
if notifyCommand.Pid > 0 {
found, _ := findProcess(notifyCommand.Pid)

if !found {
return fmt.Errorf("Process %d not found.", notifyCommand.Pid)
}
}

id := pmb.GenerateRandomID("notify")

conn, err := bus.ConnectClient(id, !globalOptions.TrustKey)
if err != nil {
return err
}

return runNotify(conn, id, args)
return runNotify(conn, id)
}

func init() {
Expand All @@ -53,76 +37,10 @@ func init() {
&notifyCommand)
}

func runNotify(conn *pmb.Connection, id string, args []string) error {
func runNotify(conn *pmb.Connection, id string) error {

message := notifyCommand.Message

if len(args) > 0 {
cmd := exec.Command(args[0], args[1:]...)

cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

command := strings.Join(args, " ")
logrus.Infof("Waiting for command '%s' to finish...\n", command)

err := cmd.Run()

result := "successfully"
if err != nil {
result = fmt.Sprintf("with error '%s'", err.Error())
}
logrus.Infof("Process complete.")

if len(message) == 0 {
message = fmt.Sprintf("Command [%s] completed %s.", command, result)
} else {
message = fmt.Sprintf("%s. Command completed %s.", message, result)
}
} else if notifyCommand.Pid != 0 {

notifyExecutable := ""
logrus.Infof("Waiting for pid %d to finish...\n", notifyCommand.Pid)
for {
found, exec := findProcess(notifyCommand.Pid)

// capture the name of the executable for the notification
if len(notifyExecutable) == 0 {
notifyExecutable = exec
}

if !found {
logrus.Infof("Process complete.")
break
} else {
time.Sleep(1 * time.Second)
}
}

if len(message) == 0 {
message = fmt.Sprintf("Command [%s] completed.", notifyExecutable)
}
}

note := pmb.Notification{Message: message, Level: notifyCommand.Level}
return pmb.SendNotification(conn, note)
}

// TODO: use a go-based library for this, maybe gopsutil
func findProcess(pid int) (bool, string) {

procCmd := exec.Command("/bin/ps", "-o", "pid=", "-p", strconv.Itoa(pid))

err := procCmd.Run()

if _, ok := err.(*exec.ExitError); ok {
return false, ""
} else if err != nil {
return false, ""
} else {
return true, fmt.Sprintf("pid %d", pid)
}

return false, ""
}
130 changes: 130 additions & 0 deletions run.go
@@ -0,0 +1,130 @@
package main

import (
"fmt"
"os"
"os/exec"
"strings"
"time"

"github.com/Sirupsen/logrus"
"github.com/justone/pmb/api"
)

type RunCommand struct {
Message string `short:"m" long:"message" description:"Message to send."`
SendTrigger string `short:"s" long:"send-trigger" description:"Send trigger message when done."`
WaitTrigger string `short:"w" long:"wait-trigger" description:"Wait for trigger."`
TriggerAlways bool `short:"a" long:"trigger-always" description:"When trigger received, execute command if previous failed."`
Level float64 `short:"l" long:"level" description:"Notification level (1-5), higher numbers indictate higher importance" default:"3"`
}

var runCommand RunCommand

func (x *RunCommand) Execute(args []string) error {
bus := pmb.GetPMB(globalOptions.Primary)

if len(args) == 0 {
return fmt.Errorf("A command is required")
}

id := pmb.GenerateRandomID("run")

conn, err := bus.ConnectClient(id, !globalOptions.TrustKey)
if err != nil {
return err
}

return runRun(conn, id, args)
}

func init() {
parser.AddCommand("run",
"Run a command.",
"",
&runCommand)
}

func runRun(conn *pmb.Connection, id string, args []string) error {

if waitTrigger := runCommand.WaitTrigger; len(waitTrigger) > 0 {
logrus.Infof("Waiting for trigger '%s' before starting...", waitTrigger)

var triggerMessage pmb.Message
WAIT:
for {
select {
case message := <-conn.In:
data := message.Contents
if data["type"].(string) == "Trigger" && data["from"].(string) == "run" && data["trigger"].(string) == waitTrigger {
triggerMessage = message
break WAIT
}
case _ = <-time.After(10 * time.Minute):
logrus.Warnf("Still waiting for trigger '%s'...", waitTrigger)
}
}

logrus.Infof("Trigger '%s' received...", waitTrigger)
note := pmb.Notification{
Message: fmt.Sprintf("Received trigger %s", waitTrigger),
Level: 3,
}
pmb.SendNotification(conn, note)

if !runCommand.TriggerAlways && !triggerMessage.Contents["success"].(bool) {
return fmt.Errorf("Previous command failed, not running.")
}
}

message := runCommand.Message

cmd := exec.Command(args[0], args[1:]...)

cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

command := strings.Join(args, " ")
logrus.Infof("Waiting for command '%s' to finish...", command)

err := cmd.Run()

cmdSuccess := true
result := "successfully"
if err != nil {
result = fmt.Sprintf("with error '%s'", err.Error())
cmdSuccess = false
}
logrus.Infof("Process complete.")

if len(message) == 0 {
message = fmt.Sprintf("Command [%s] completed %s.", command, result)
} else {
message = fmt.Sprintf("%s. Command completed %s.", message, result)
}

note := pmb.Notification{Message: message, Level: runCommand.Level}
notifyErr := pmb.SendNotification(conn, note)

if sendTrigger := runCommand.SendTrigger; len(sendTrigger) > 0 {
logrus.Infof("Sending trigger '%s'.", sendTrigger)
note := pmb.Notification{
Message: fmt.Sprintf("Sending trigger %s", sendTrigger),
Level: 3,
}
pmb.SendNotification(conn, note)

conn.Out <- pmb.Message{
Contents: map[string]interface{}{
"type": "Trigger",
"trigger": sendTrigger,
"from": "run",
"success": cmdSuccess,
},
}
<-time.After(2 * time.Second)
}

return notifyErr
}
128 changes: 128 additions & 0 deletions watch.go
@@ -0,0 +1,128 @@
package main

import (
"fmt"
"os"
"os/exec"
"strconv"
"time"

"github.com/Sirupsen/logrus"
"github.com/justone/pmb/api"
)

type WatchCommand struct {
Message string `short:"m" long:"message" description:"Message to send."`
Pid int `short:"p" long:"pid" description:"Notify after PID exits."`
File string `short:"f" long:"file" description:"Notify after file stops changing."`
Level float64 `short:"l" long:"level" description:"Notification level (1-5), higher numbers indictate higher importance" default:"3"`
}

var watchCommand WatchCommand

func (x *WatchCommand) Execute(args []string) error {
bus := pmb.GetPMB(globalOptions.Primary)

if len(watchCommand.Message) == 0 && watchCommand.Pid == 0 && watchCommand.File == "" {
return fmt.Errorf("A message or pid or file is required")
}

// fail fast if pid isn't found
if watchCommand.Pid > 0 {
found, _ := findProcess(watchCommand.Pid)

if !found {
return fmt.Errorf("Process %d not found.", watchCommand.Pid)
}
}
if len(watchCommand.File) > 0 {
if _, err := os.Stat(watchCommand.File); os.IsNotExist(err) {
return fmt.Errorf("File %s not found.", watchCommand.File)
}
}

id := pmb.GenerateRandomID("watch")

conn, err := bus.ConnectClient(id, !globalOptions.TrustKey)
if err != nil {
return err
}

return runWatch(conn, id)
}

func init() {
parser.AddCommand("watch",
"Send a notification.",
"",
&watchCommand)
}

func runWatch(conn *pmb.Connection, id string) error {

message := watchCommand.Message

if watchCommand.Pid != 0 {

watchExecutable := ""
logrus.Infof("Waiting for pid %d to finish...\n", watchCommand.Pid)
for {
found, exec := findProcess(watchCommand.Pid)

// capture the name of the executable for the notification
if len(watchExecutable) == 0 {
watchExecutable = exec
}

if !found {
logrus.Infof("Process complete.")
break
} else {
time.Sleep(1 * time.Second)
}
}

if len(message) == 0 {
message = fmt.Sprintf("Command [%s] completed.", watchExecutable)
}
}
if len(watchCommand.File) > 0 {
var prevSize int64
prevSize = -1
for {
statInfo, _ := os.Stat(watchCommand.File)
if statInfo.Size() == prevSize {
logrus.Infof("File stabilized.")
break
} else {
prevSize = statInfo.Size()
time.Sleep(5 * time.Second)
}
}

if len(message) == 0 {
message = fmt.Sprintf("File [%s] stabilized.", watchCommand.File)
}
}

note := pmb.Notification{Message: message, Level: watchCommand.Level}
return pmb.SendNotification(conn, note)
}

// TODO: use a go-based library for this, maybe gopsutil
func findProcess(pid int) (bool, string) {

procCmd := exec.Command("/bin/ps", "-o", "pid=", "-p", strconv.Itoa(pid))

err := procCmd.Run()

if _, ok := err.(*exec.ExitError); ok {
return false, ""
} else if err != nil {
return false, ""
} else {
return true, fmt.Sprintf("pid %d", pid)
}

return false, ""
}

0 comments on commit b94c4eb

Please sign in to comment.