github.com/clusterize-io/tusk@v0.6.3-0.20211001020217-cfe8a8cd0d4a/runner/command.go (about)

     1  package runner
     2  
     3  import (
     4  	"os"
     5  	"os/exec"
     6  
     7  	"github.com/clusterize-io/tusk/marshal"
     8  	"github.com/clusterize-io/tusk/ui"
     9  )
    10  
    11  var defaultInterpreter = []string{"sh", "-c"}
    12  
    13  // execCommand allows overwriting during tests.
    14  var execCommand = exec.Command
    15  
    16  // Command is a command passed to the shell.
    17  type Command struct {
    18  	// Exec is the script to execute.
    19  	Exec string `yaml:"exec"`
    20  
    21  	// Print is the text that will be printed when the command is executed.
    22  	Print string `yaml:"print"`
    23  
    24  	// Dir is the directory of the command.
    25  	Dir string `yaml:"dir"`
    26  }
    27  
    28  // UnmarshalYAML allows strings to be interpreted as Do actions.
    29  func (c *Command) UnmarshalYAML(unmarshal func(interface{}) error) error {
    30  	var do string
    31  	doCandidate := marshal.UnmarshalCandidate{
    32  		Unmarshal: func() error { return unmarshal(&do) },
    33  		Assign: func() {
    34  			*c = Command{
    35  				Exec:  do,
    36  				Print: do,
    37  			}
    38  		},
    39  	}
    40  
    41  	type commandType Command // Use new type to avoid recursion
    42  	var commandItem commandType
    43  	commandCandidate := marshal.UnmarshalCandidate{
    44  		Unmarshal: func() error { return unmarshal(&commandItem) },
    45  		Assign: func() {
    46  			*c = Command(commandItem)
    47  			if c.Print == "" {
    48  				c.Print = c.Exec
    49  			}
    50  		},
    51  	}
    52  
    53  	return marshal.UnmarshalOneOf(doCandidate, commandCandidate)
    54  }
    55  
    56  // newCmd creates an exec.Cmd that uses the interpreter and the script passed.
    57  func newCmd(ctx Context, script string) *exec.Cmd {
    58  	interpreter := defaultInterpreter
    59  	if len(ctx.Interpreter) > 0 {
    60  		interpreter = ctx.Interpreter
    61  	}
    62  
    63  	path := interpreter[0]
    64  	args := []string{script}
    65  	if len(interpreter) > 1 {
    66  		args = append(interpreter[1:], args...)
    67  	}
    68  
    69  	return execCommand(path, args...)
    70  }
    71  
    72  // execCommand executes a shell command.
    73  func (c *Command) exec(ctx Context) error {
    74  	cmd := newCmd(ctx, c.Exec)
    75  
    76  	cmd.Dir = c.Dir
    77  	cmd.Stdin = os.Stdin
    78  	if ctx.Logger.Verbosity > ui.VerbosityLevelSilent {
    79  		cmd.Stdout = os.Stdout
    80  		cmd.Stderr = os.Stderr
    81  	}
    82  
    83  	return cmd.Run()
    84  }
    85  
    86  // CommandList is a list of commands with custom yaml unamrshaling.
    87  type CommandList []Command
    88  
    89  // UnmarshalYAML allows single items to be used as lists.
    90  func (cl *CommandList) UnmarshalYAML(unmarshal func(interface{}) error) error {
    91  	var commandSlice []Command
    92  	sliceCandidate := marshal.UnmarshalCandidate{
    93  		Unmarshal: func() error { return unmarshal(&commandSlice) },
    94  		Assign:    func() { *cl = commandSlice },
    95  	}
    96  
    97  	var commandItem Command
    98  	itemCandidate := marshal.UnmarshalCandidate{
    99  		Unmarshal: func() error { return unmarshal(&commandItem) },
   100  		Assign:    func() { *cl = CommandList{commandItem} },
   101  	}
   102  
   103  	return marshal.UnmarshalOneOf(sliceCandidate, itemCandidate)
   104  }