diff --git a/Makefile b/Makefile index fe890ce..5988e96 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ $(TARGETS): run: go run main.go task.go config.go screen.go download.go log.go \ - run example/13-single-line.yml + run example/14-sudo.yml examples: clean build ./dist/bashful run example/00-demo.yml diff --git a/config.go b/config.go index a4c7644..84b6b45 100644 --- a/config.go +++ b/config.go @@ -198,6 +198,9 @@ type TaskConfig struct { // StopOnFailure indicates to halt further program execution if a task command has a non-zero return code StopOnFailure bool `yaml:"stop-on-failure"` + // Sudo indicates that the given command should be run with the given sudo credentials + Sudo bool `yaml:"sudo"` + // Tags is a list of strings that is used to filter down which task are run at runtime Tags stringArray `yaml:"tags"` TagSet mapset.Set diff --git a/example/14-sudo.yml b/example/14-sudo.yml new file mode 100644 index 0000000..4f9e956 --- /dev/null +++ b/example/14-sudo.yml @@ -0,0 +1,9 @@ +tasks: + # if a command shold be run with sudo, flag it as so... + - cmd: touch /bin/true + sudo: true + + #... *don't* put sudo in your cmd + - cmd: sudo touch /bin/true + stop-on-failure: false + diff --git a/main.go b/main.go index c1532e9..149ad09 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "math/rand" "os" + "os/exec" "os/signal" "path/filepath" "runtime" @@ -16,6 +17,7 @@ import ( "text/template" "time" + "github.com/howeyc/gopass" color "github.com/mgutz/ansi" "github.com/mholt/archiver" "github.com/urfave/cli" @@ -36,6 +38,7 @@ var ( ticker *time.Ticker exitSignaled bool startTime time.Time + sudoPassword string purple = color.ColorFunc("magenta+h") red = color.ColorFunc("red+h") blue = color.ColorFunc("blue+h") @@ -200,6 +203,51 @@ __BASHFUL_ARCHIVE__ } +func storeSudoPasswd() { + var sout bytes.Buffer + + // check if there is a task that requires sudo + requireSudo := false + for _, task := range allTasks { + if task.Config.Sudo { + requireSudo = true + break + } + for _, subTask := range task.Children { + if subTask.Config.Sudo { + requireSudo = true + break + } + } + } + + if !requireSudo { + return + } + + // test if a password is even required for sudo + cmd := exec.Command("/bin/sh", "-c", "sudo -Sn /bin/true") + cmd.Stderr = &sout + err := cmd.Run() + requiresPassword := sout.String() == "sudo: a password is required\n" + + if requiresPassword { + fmt.Print("[bashful] sudo password required: ") + sudoPassword, err := gopass.GetPasswd() + checkError(err, "Could get sudo password from user.") + + // test the given password + cmdTest := exec.Command("/bin/sh", "-c", "sudo -S /bin/true") + cmdTest.Stdin = strings.NewReader(string(sudoPassword) + "\n") + err = cmdTest.Run() + if err != nil { + exitWithErrorMessage("Given sudo password did not work.") + } + } else { + checkError(err, "Could not determine sudo access for user.") + } +} + func run(yamlString []byte, environment map[string]string) []*Task { var err error @@ -208,6 +256,7 @@ func run(yamlString []byte, environment map[string]string) []*Task { ParseConfig(yamlString) allTasks = CreateTasks() + storeSudoPasswd() DownloadAssets(allTasks) diff --git a/task.go b/task.go index 76ce3f8..5fef89e 100644 --- a/task.go +++ b/task.go @@ -261,7 +261,12 @@ func (task *Task) inflateCmd() { readFd, writeFd, err := os.Pipe() checkError(err, "Could not open env pipe for child shell") - task.Command.Cmd = exec.Command(shell, "-c", task.Config.CmdString+"; BASHFUL_RC=$?; env >&3; exit $BASHFUL_RC") + sudoCmd := "" + if task.Config.Sudo { + sudoCmd = "sudo -S " + } + task.Command.Cmd = exec.Command(shell, "-c", sudoCmd+task.Config.CmdString+"; BASHFUL_RC=$?; env >&3; exit $BASHFUL_RC") + task.Command.Cmd.Stdin = strings.NewReader(string(sudoPassword) + "\n") // allow the child process to provide env vars via a pipe (FD3) task.Command.Cmd.ExtraFiles = []*os.File{writeFd}