Skip to content

Commit

Permalink
Fix correct exit code on remote/local task error (#27)
Browse files Browse the repository at this point in the history
- Allow duplicate hosts
- Fix correct exit code on remote/local task errors
- Fix local WorkDir when it's not explicitly set
  • Loading branch information
alajmo committed Jul 30, 2022
1 parent 47cdac6 commit f88a63a
Show file tree
Hide file tree
Showing 17 changed files with 86 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ NAME := sake
PACKAGE := github.com/alajmo/$(NAME)
DATE := $(shell date +%FT%T%Z)
GIT := $(shell [ -d .git ] && git rev-parse --short HEAD)
VERSION := v0.10.1
VERSION := v0.10.2

default: build

Expand Down
14 changes: 13 additions & 1 deletion core/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,30 @@ func (f *TemplateParseError) Error() string {
return fmt.Sprintf("failed to parse %s", f.Msg)
}

type ExecError struct {
Err error
ExitCode int
}

func (e *ExecError) Error() string {
return ""
}

func CheckIfError(err error) {
if err != nil {
Exit(err)
}
}

func Exit(err error) {
switch err.(type) {
switch err := err.(type) {
case *ConfigErr:
// Errors are already mapped with `error:` prefix
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(1)
case *ExecError:
// Don't print anything when there's a ExecError: server execution failed
os.Exit(err.ExitCode)
default:
fmt.Fprintf(os.Stderr, "%s: %v\n", text.FgRed.Sprintf("error"), err)
os.Exit(1)
Expand Down
11 changes: 7 additions & 4 deletions core/run/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (run *Run) RunTask(
print.PrintTable("Unreachable Hosts", unreachableOutput.Rows, options, unreachableOutput.Headers[0:1], unreachableOutput.Headers[1:])

if !task.Spec.IgnoreUnreachable {
return nil
return &core.ExecError{Err: err, ExitCode: 4}
}
}

Expand Down Expand Up @@ -119,16 +119,19 @@ func (run *Run) RunTask(
spinner := core.GetSpinner()
spinner.Start(" Running", 500)

data := run.Table(runFlags.DryRun)
data, err := run.Table(runFlags.DryRun)
options := print.PrintTableOptions{Theme: task.Theme, OmitEmpty: task.Spec.OmitEmpty, Output: task.Spec.Output, SuppressEmptyColumns: false}
run.CleanupClients()

spinner.Stop()

print.PrintTable("", data.Rows, options, data.Headers[0:1], data.Headers[1:])

if err != nil {
return err
}
default:
err := run.Text(runFlags.DryRun)
run.CleanupClients()

if err != nil {
return err
}
Expand Down
22 changes: 16 additions & 6 deletions core/run/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ package run

import (
"fmt"
"golang.org/x/crypto/ssh"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"sync"

"github.com/alajmo/sake/core"
"github.com/alajmo/sake/core/dao"
)

func (run *Run) Table(dryRun bool) dao.TableOutput {
func (run *Run) Table(dryRun bool) (dao.TableOutput, error) {
task := run.Task
servers := run.Servers

Expand Down Expand Up @@ -51,7 +53,7 @@ func (run *Run) Table(dryRun bool) dao.TableOutput {
if task.Spec.Parallel {
go func(i int, wg *sync.WaitGroup) {
defer wg.Done()
// TODO
// TODO: Handle errors when running tasks in parallel
_ = run.TableWork(i, dryRun, data, &dataMutex)
}(i, &wg)
} else {
Expand All @@ -62,15 +64,23 @@ func (run *Run) Table(dryRun bool) dao.TableOutput {
return err
}(i, &wg)

if run.Task.Spec.AnyErrorsFatal && err != nil {
break
if err != nil && run.Task.Spec.AnyErrorsFatal {
// Return proper exit code for failed tasks
switch err := err.(type) {
case *ssh.ExitError:
return data, &core.ExecError{Err: err, ExitCode: err.ExitStatus()}
case *exec.ExitError:
return data, &core.ExecError{Err: err, ExitCode: err.ExitCode()}
default:
return data, err
}
}
}

}
wg.Wait()

return data
return data, nil
}

func (run *Run) TableWork(rIndex int, dryRun bool, data dao.TableOutput, dataMutex *sync.RWMutex) error {
Expand Down Expand Up @@ -104,7 +114,7 @@ func (run *Run) TableWork(rIndex int, dryRun bool, data dao.TableOutput, dataMut
}

err := RunTableCmd(tableCmd, data, dataMutex, &wg)
if err != nil && !task.Spec.IgnoreErrors {
if !task.Spec.IgnoreErrors && err != nil {
return err
}
}
Expand Down
34 changes: 22 additions & 12 deletions core/run/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"bytes"
"fmt"
"github.com/jedib0t/go-pretty/v6/text"
"golang.org/x/crypto/ssh"
"golang.org/x/exp/slices"
"golang.org/x/term"
"io"
"os"
"os/exec"
"strings"
"sync"
"text/template"
Expand All @@ -29,7 +31,7 @@ func (run *Run) Text(dryRun bool) error {
if run.Task.Spec.Parallel {
go func(i int, wg *sync.WaitGroup) {
defer wg.Done()
// TODO
// TODO: Handle errors when running tasks in parallel
_ = run.TextWork(i, prefixMaxLen, dryRun)
}(i, &wg)
} else {
Expand All @@ -39,16 +41,24 @@ func (run *Run) Text(dryRun bool) error {
return err
}(i, &wg)

switch err.(type) {
case *template.ExecError:
return err
case *core.TemplateParseError:
return err
default:
if run.Task.Spec.AnyErrorsFatal && err != nil {
// The error is printed for each server in method RunTextCmd.
// We just return early so other tasks are not executed.
return nil
if err != nil {
switch err.(type) {
case *template.ExecError:
return err
case *core.TemplateParseError:
return err
default:
if run.Task.Spec.AnyErrorsFatal {
// Return proper exit code for failed tasks
switch err := err.(type) {
case *ssh.ExitError:
return &core.ExecError{Err: err, ExitCode: err.ExitStatus()}
case *exec.ExitError:
return &core.ExecError{Err: err, ExitCode: err.ExitCode()}
default:
return err
}
}
}
}
}
Expand Down Expand Up @@ -102,7 +112,7 @@ func (run *Run) TextWork(rIndex int, prefixMaxLen int, dryRun bool) error {
case *core.TemplateParseError:
return err
default:
if err != nil && !task.Spec.IgnoreErrors {
if !task.Spec.IgnoreErrors && err != nil {
return err
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/run/unix.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows

package run
Expand Down Expand Up @@ -46,4 +47,3 @@ func ExecTTY(cmd string, envs []string) error {

return nil
}

1 change: 1 addition & 0 deletions core/run/windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build windows
// +build windows

package run
Expand Down
2 changes: 1 addition & 1 deletion core/sake.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.TH "SAKE" "1" "2022-06-26T09:26:27CEST" "v0.10.0" "Sake Manual" "sake"
.TH "SAKE" "1" "2022-07-30T22:44:33CEST" "v0.10.2" "Sake Manual" "sake"
.SH NAME
sake - sake is a command runner for local and remote hosts

Expand Down
5 changes: 4 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

## Unreleased

## 0.10.2

### Fixes

- Fix local WorkDir when it's not set
- Allow duplicate hosts
- Fix correct exit code on remote/local task errors (#27)
- Fix local WorkDir when it's not explicitly set

## 0.10.1

Expand Down
2 changes: 1 addition & 1 deletion test/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ubuntu:21.10
FROM ubuntu:latest

RUN apt-get update && apt-get install openssh-server sudo -y

Expand Down
3 changes: 2 additions & 1 deletion test/integration/golden/golden-15.stdout
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Index: 15
Name: fatal true
Cmd: go run ../../main.go run fatal-true -t reachable
WantErr: false
WantErr: true

---

Expand All @@ -19,3 +19,4 @@ WantErr: false
server-8 |
server-9 |

exit status 1
3 changes: 2 additions & 1 deletion test/integration/golden/golden-18.stdout
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Index: 18
Name: unreachable false
Cmd: go run ../../main.go run unreachable -a
WantErr: false
WantErr: true

---

Expand All @@ -11,3 +11,4 @@ Unreachable Hosts
-------------+-----------------+------+------+------------------------------------------------
unreachable | unreachable.lan | test | 22 | dial tcp: lookup unreachable.lan: no such host

exit status 4
4 changes: 2 additions & 2 deletions test/integration/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ var cases = []TemplateTest{
{
TestName: "fatal true",
TestCmd: "go run ../../main.go run fatal-true -t reachable",
WantErr: false,
WantErr: true,
},
{
TestName: "ignore_errors false",
Expand All @@ -110,7 +110,7 @@ var cases = []TemplateTest{
{
TestName: "unreachable false",
TestCmd: "go run ../../main.go run unreachable -a",
WantErr: false,
WantErr: true,
},
{
TestName: "unreachable true",
Expand Down
4 changes: 4 additions & 0 deletions test/playground/sake.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ env:
DATE: $(date -u +"%Y-%m-%dT%H:%M:%S%Z")

tasks:
exit:
local: true
cmd: exit 3

ping:
target: all
desc: ping server
Expand Down
9 changes: 8 additions & 1 deletion test/sake.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
disable_verify_host: true

import:
- ./sake-1.yaml
- ./servers.yaml

env:
NO_COLOR: true
Expand All @@ -24,6 +24,13 @@ specs:
ignore_unreachable: true
any_errors_fatal: false

themes:
default:
text:
prefix: true
header: '{{ .Style "TASK" "bold" }}{{ if ne .NumTasks 1 }} ({{ .Index }}/{{ .NumTasks }}){{end}}{{ if and .Name .Desc }} [{{.Style .Name "bold"}}: {{ .Desc }}] {{ else if .Name }} [{{ .Name }}] {{ else if .Desc }} [{{ .Desc }}] {{end}}'
table:

tasks:
ping:
target: all
Expand Down
2 changes: 1 addition & 1 deletion test/sake-1.yaml → test/servers.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import:
- ./sake-2.yaml
- ./tasks.yaml

servers:
localhost:
Expand Down
File renamed without changes.

0 comments on commit f88a63a

Please sign in to comment.