2022-07-18 10:10:24 -05:00
|
|
|
/*
|
2022-08-17 02:24:48 -05:00
|
|
|
Package command is used to manage the container management instructions required to carry out
|
|
|
|
tasks desired by the user, such as "create"
|
|
|
|
|
2022-07-18 10:10:24 -05:00
|
|
|
Copyright © 2022 Joel D. Elkins <joel@elkins.co>
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
|
|
all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os/exec"
|
|
|
|
)
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// Type is one of the Ct... constants, indicating the underlying method
|
|
|
|
// for executing a command.
|
|
|
|
type cType int
|
|
|
|
|
|
|
|
// Types of commands:
|
|
|
|
// - CtNop: No operation; an empty placeholder
|
|
|
|
// - CtShell: A shell command -- string to be fed to /bin/sh -c
|
|
|
|
// - CtFunc: A go function of type `func() error`
|
|
|
|
// - CtIndirect: A reference to another command. When executed, the underlying command is executed.
|
|
|
|
// - CtSet: An ordered list of commands
|
|
|
|
// - CtDebug: A debug message is printed upon execution, but does no other action
|
|
|
|
// - CtConditional: A function and two commands are provided. When evaluated,
|
|
|
|
// the function, having signature `func() bool`, is executed. If the result is
|
|
|
|
// true, then the first command is executed, and if false the second one.
|
2022-07-18 19:47:05 -05:00
|
|
|
const (
|
2022-08-17 02:24:48 -05:00
|
|
|
ctNop cType = iota
|
|
|
|
ctShell
|
|
|
|
ctFunc
|
|
|
|
ctIndirect
|
|
|
|
ctSet
|
|
|
|
ctDebug
|
|
|
|
ctConditional
|
2022-07-18 19:47:05 -05:00
|
|
|
)
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
func (ct cType) String() string {
|
2022-07-18 10:10:24 -05:00
|
|
|
switch ct {
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctNop:
|
2022-07-18 19:47:05 -05:00
|
|
|
return "NOP"
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctShell:
|
2022-07-18 10:10:24 -05:00
|
|
|
return "SHELL"
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctFunc:
|
2022-07-18 10:10:24 -05:00
|
|
|
return "FUNC"
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctIndirect:
|
2022-07-18 10:10:24 -05:00
|
|
|
return "INDIRECT"
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctSet:
|
2022-07-18 10:10:24 -05:00
|
|
|
return "SET"
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctDebug:
|
2022-07-18 10:10:24 -05:00
|
|
|
return "DEBUG"
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctConditional:
|
2022-07-18 19:47:05 -05:00
|
|
|
return "CONDITIONAL"
|
2022-07-18 10:10:24 -05:00
|
|
|
default:
|
|
|
|
return "UNKOWN"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// Command combines a Type with an `interface{}`, representing a task (or a set
|
|
|
|
// of tasks, in the case of CtSet) to be executed. Create a Command with one of
|
|
|
|
// the `NewXxxCommand` functions.
|
2022-07-18 10:10:24 -05:00
|
|
|
type Command struct {
|
2022-08-17 02:24:48 -05:00
|
|
|
Type cType
|
2022-07-18 10:10:24 -05:00
|
|
|
Command interface{}
|
|
|
|
}
|
2022-08-17 02:24:48 -05:00
|
|
|
|
|
|
|
// Commands reflects an ordered grouping of `Command`s
|
2022-07-29 19:42:46 -05:00
|
|
|
type Commands []Command
|
2022-07-18 10:10:24 -05:00
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// Set bundes a `Commands` object with an arbitrary ID for verbose output
|
|
|
|
type Set struct {
|
2022-07-30 14:26:23 -05:00
|
|
|
ID string
|
|
|
|
Commands
|
|
|
|
}
|
|
|
|
|
2022-07-25 10:13:36 -05:00
|
|
|
type errFunc struct {
|
|
|
|
Name string
|
|
|
|
Func func() error
|
|
|
|
}
|
|
|
|
|
2022-07-18 19:47:05 -05:00
|
|
|
type conditional struct {
|
|
|
|
Name string
|
|
|
|
Condition func() bool
|
|
|
|
ThenCmd Command
|
|
|
|
ElseCmd Command
|
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// NewShell creates a new shell-type command from a string. Upon execution, the
|
|
|
|
// string will be fed to `/bin/sh -c`
|
2022-07-18 10:10:24 -05:00
|
|
|
func NewShell(cmd string) Command {
|
2022-08-17 02:24:48 -05:00
|
|
|
return Command{ctShell, cmd}
|
2022-07-18 10:10:24 -05:00
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// NewFunc creates a command consisting of go function of type `func() error`
|
2022-07-29 18:58:52 -05:00
|
|
|
func NewFunc(name string, f func() error) Command {
|
2022-08-17 02:24:48 -05:00
|
|
|
return Command{ctFunc, errFunc{name, f}}
|
2022-07-18 10:10:24 -05:00
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// NewIndirect creates a reference to another command. When executed, the underlying
|
|
|
|
// command is executed.
|
2022-07-18 10:10:24 -05:00
|
|
|
func NewIndirect(c Command) Command {
|
2022-08-17 02:24:48 -05:00
|
|
|
return Command{ctIndirect, c}
|
2022-07-18 10:10:24 -05:00
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// NewSet creates a single command containing an ordered list of commands. These
|
|
|
|
// commands will be executed in order with this set-type command is executed.
|
|
|
|
func NewSet(cs Set) Command {
|
2022-09-05 23:51:29 -05:00
|
|
|
return Command{ctSet, cs}
|
2022-07-18 10:10:24 -05:00
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// NewDebug returns a command that prints a debug message upon execution, but
|
|
|
|
// does no other action
|
2022-07-18 10:10:24 -05:00
|
|
|
func NewDebug(msg string) Command {
|
2022-08-17 02:24:48 -05:00
|
|
|
return Command{ctDebug, msg}
|
2022-07-18 10:10:24 -05:00
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// NewNop returns a placeholder command. When executed, nothing is done other
|
|
|
|
// than to note that it was encountered in the output.
|
2022-07-18 19:47:05 -05:00
|
|
|
func NewNop() Command {
|
2022-08-17 02:24:48 -05:00
|
|
|
return Command{ctNop, nil}
|
2022-07-18 19:47:05 -05:00
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// NewConditional takes a function and two commands as arguments. When evaluated,
|
|
|
|
// the function, having signature `func() bool`, is executed. If the result is
|
|
|
|
// true, then the first command is executed, and if false the second one.
|
2022-07-18 19:47:05 -05:00
|
|
|
func NewConditional(name string, ifPart func() bool, thenPart Command, elsePart Command) Command {
|
2022-08-17 02:24:48 -05:00
|
|
|
return Command{ctConditional, conditional{
|
2022-07-18 19:47:05 -05:00
|
|
|
Name: name,
|
|
|
|
Condition: ifPart,
|
|
|
|
ThenCmd: thenPart,
|
|
|
|
ElseCmd: elsePart,
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// Execute the Set with the privileges of the user running the process.
|
|
|
|
// Warning: in the case of a shell command, this could do anything.
|
|
|
|
func (cmds Set) Execute(output io.Writer, fake bool) error {
|
2022-07-30 14:26:23 -05:00
|
|
|
for _, c := range cmds.Commands {
|
|
|
|
if err := c.Execute(output, fake, cmds.ID); err != nil {
|
2022-07-29 19:42:46 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-17 02:24:48 -05:00
|
|
|
// Execute the Command with the privileges of the user running the process.
|
|
|
|
// Warning: in the case of a shell command, this could do anything.
|
2022-07-30 14:26:23 -05:00
|
|
|
func (c Command) Execute(output io.Writer, fake bool, commandSetID string) error {
|
2022-07-18 10:10:24 -05:00
|
|
|
switch c.Type {
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctNop:
|
2022-07-30 14:26:23 -05:00
|
|
|
fmt.Fprintf(output, "%s: %s\n", commandSetID, c.Type)
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctShell:
|
2022-07-30 14:26:23 -05:00
|
|
|
cmd := c.Command.(string)
|
|
|
|
fmt.Fprintf(output, "%s: %s sh -c \"%s\"\n", commandSetID, c.Type, cmd)
|
2022-07-19 01:38:25 -05:00
|
|
|
if !fake {
|
2022-07-18 19:47:05 -05:00
|
|
|
out, err := exec.Command("sh", "-c", cmd).CombinedOutput()
|
2022-07-19 19:05:42 -05:00
|
|
|
fmt.Fprint(output, string(out))
|
2022-07-18 19:47:05 -05:00
|
|
|
return err
|
|
|
|
}
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctFunc:
|
2022-07-25 10:13:36 -05:00
|
|
|
ef := c.Command.(errFunc)
|
2022-07-30 14:26:23 -05:00
|
|
|
fmt.Fprintf(output, "%s: %s %s\n", commandSetID, c.Type, ef.Name)
|
2022-07-25 10:13:36 -05:00
|
|
|
if !fake {
|
|
|
|
if err := ef.Func(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctIndirect:
|
2022-07-30 14:26:23 -05:00
|
|
|
ct := c.Command.(Command)
|
2022-09-05 23:51:29 -05:00
|
|
|
return ct.Execute(output, fake, commandSetID+"/"+c.Type.String())
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctSet:
|
2022-09-05 23:51:29 -05:00
|
|
|
cs := c.Command.(Set)
|
|
|
|
for i := range cs.Commands {
|
|
|
|
err := cs.Commands[i].Execute(output, fake, commandSetID+"/"+cs.ID)
|
2022-07-18 10:10:24 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctConditional:
|
2022-07-30 14:26:23 -05:00
|
|
|
cond := c.Command.(conditional)
|
2022-07-18 19:47:05 -05:00
|
|
|
if fake {
|
2022-07-30 14:26:23 -05:00
|
|
|
// in a fake setting, we don't know which branch will be followed,
|
|
|
|
// so show what we would do in either branch
|
|
|
|
fmt.Fprintf(output, "%s: %s %s\n", commandSetID, c.Type, cond.Name)
|
2022-09-05 23:51:29 -05:00
|
|
|
cond.ThenCmd.Execute(output, fake, commandSetID+"/"+cond.Name+"/true")
|
|
|
|
cond.ElseCmd.Execute(output, fake, commandSetID+"/"+cond.Name+"/false")
|
2022-07-18 19:47:05 -05:00
|
|
|
} else {
|
|
|
|
branch := cond.Condition()
|
2022-07-30 14:26:23 -05:00
|
|
|
fmt.Fprintf(output, "%s: %s %s: %v\n", commandSetID, c.Type, cond.Name, branch)
|
2022-07-18 19:47:05 -05:00
|
|
|
if branch {
|
2022-09-05 23:51:29 -05:00
|
|
|
return cond.ThenCmd.Execute(output, fake, commandSetID+"/"+cond.Name+"/true")
|
2022-07-18 19:47:05 -05:00
|
|
|
}
|
2022-09-05 23:51:29 -05:00
|
|
|
return cond.ElseCmd.Execute(output, fake, commandSetID+"/"+cond.Name+"/false")
|
2022-07-18 19:47:05 -05:00
|
|
|
}
|
2022-08-17 02:24:48 -05:00
|
|
|
case ctDebug:
|
2022-07-30 14:26:23 -05:00
|
|
|
msg := c.Command.(string)
|
|
|
|
fmt.Fprintf(output, "%s: %s %s\n", commandSetID, c.Type, msg)
|
2022-07-18 10:10:24 -05:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|