Skip to content

Commit 4504f1f

Browse files
committed
Command optional exec style
Based in goss-org#871
1 parent b563479 commit 4504f1f

File tree

5 files changed

+125
-33
lines changed

5 files changed

+125
-33
lines changed

resource/command.go

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"fmt"
77
"io"
8+
"log"
89
"strings"
910
"time"
1011

@@ -13,15 +14,15 @@ import (
1314
)
1415

1516
type Command struct {
16-
Title string `json:"title,omitempty" yaml:"title,omitempty"`
17-
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
18-
id string `json:"-" yaml:"-"`
19-
Exec string `json:"exec,omitempty" yaml:"exec,omitempty"`
20-
ExitStatus matcher `json:"exit-status" yaml:"exit-status"`
21-
Stdout matcher `json:"stdout" yaml:"stdout"`
22-
Stderr matcher `json:"stderr" yaml:"stderr"`
23-
Timeout int `json:"timeout" yaml:"timeout"`
24-
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
17+
Title string `json:"title,omitempty" yaml:"title,omitempty"`
18+
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
19+
id string `json:"-" yaml:"-"`
20+
Exec util.ExecCommand `json:"exec,omitempty" yaml:"exec,omitempty"`
21+
ExitStatus matcher `json:"exit-status" yaml:"exit-status"`
22+
Stdout matcher `json:"stdout" yaml:"stdout"`
23+
Stderr matcher `json:"stderr" yaml:"stderr"`
24+
Timeout int `json:"timeout" yaml:"timeout"`
25+
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
2526
}
2627

2728
const (
@@ -41,11 +42,14 @@ func (c *Command) TypeName() string { return CommandResourceName }
4142

4243
func (c *Command) GetTitle() string { return c.Title }
4344
func (c *Command) GetMeta() meta { return c.Meta }
44-
func (c *Command) GetExec() string {
45-
if c.Exec != "" {
46-
return c.Exec
45+
func (c *Command) GetExec() any {
46+
if c.Exec.CmdStr != "" {
47+
return c.Exec.CmdStr
48+
} else if len(c.Exec.CmdSlice) > 0 {
49+
return c.Exec.CmdSlice
50+
} else {
51+
return c.id
4752
}
48-
return c.id
4953
}
5054

5155
func (c *Command) Validate(sys *system.System) []TestResult {
@@ -57,24 +61,48 @@ func (c *Command) Validate(sys *system.System) []TestResult {
5761
}
5862

5963
var results []TestResult
60-
sysCommand := sys.NewCommand(ctx, c.GetExec(), sys, util.Config{Timeout: time.Duration(c.Timeout) * time.Millisecond})
61-
62-
cExitStatus := deprecateAtoI(c.ExitStatus, fmt.Sprintf("%s: command.exit-status", c.ID()))
63-
results = append(results, ValidateValue(c, "exit-status", cExitStatus, sysCommand.ExitStatus, skip))
64-
if isSet(c.Stdout) {
65-
results = append(results, ValidateValue(c, "stdout", c.Stdout, sysCommand.Stdout, skip))
66-
}
67-
if isSet(c.Stderr) {
68-
results = append(results, ValidateValue(c, "stderr", c.Stderr, sysCommand.Stderr, skip))
64+
sysCommand, err := sys.NewCommand(ctx, c.GetExec(), sys, util.Config{Timeout: time.Duration(c.Timeout) * time.Millisecond})
65+
if err != nil {
66+
log.Printf("[ERROR] Could not create new command: %v", err)
67+
startTime := time.Now()
68+
results = append(
69+
results,
70+
TestResult{
71+
Result: FAIL,
72+
ResourceType: "Command",
73+
ResourceId: c.id,
74+
Title: c.Title,
75+
Meta: c.Meta,
76+
Property: "type",
77+
Err: toValidateError(err),
78+
StartTime: startTime,
79+
EndTime: startTime,
80+
Duration: startTime.Sub(startTime),
81+
},
82+
)
83+
} else {
84+
cExitStatus := deprecateAtoI(c.ExitStatus, fmt.Sprintf("%s: command.exit-status", c.ID()))
85+
results = append(results, ValidateValue(c, "exit-status", cExitStatus, sysCommand.ExitStatus, skip))
86+
if isSet(c.Stdout) {
87+
results = append(results, ValidateValue(c, "stdout", c.Stdout, sysCommand.Stdout, skip))
88+
}
89+
if isSet(c.Stderr) {
90+
results = append(results, ValidateValue(c, "stderr", c.Stderr, sysCommand.Stderr, skip))
91+
}
6992
}
7093
return results
7194
}
7295

7396
func NewCommand(sysCommand system.Command, config util.Config) (*Command, error) {
74-
command := sysCommand.Command()
97+
var id string
98+
if sysCommand.Command().CmdStr != "" {
99+
id = sysCommand.Command().CmdStr
100+
} else {
101+
id = sysCommand.Command().CmdSlice[0]
102+
}
75103
exitStatus, err := sysCommand.ExitStatus()
76104
c := &Command{
77-
id: command,
105+
id: id,
78106
ExitStatus: exitStatus,
79107
Stdout: "",
80108
Stderr: "",

resource/resource_list.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ type CommandMap map[string]*Command
120120

121121
func (r CommandMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Command, error) {
122122
ctx := context.WithValue(context.Background(), idKey{}, sr)
123-
sysres := sys.NewCommand(ctx, sr, sys, config)
123+
sysres, _ := sys.NewCommand(ctx, sr, sys, config)
124124
res, err := NewCommand(sysres, config)
125125
if err != nil {
126126
return nil, err
@@ -135,7 +135,7 @@ func (r CommandMap) AppendSysResource(sr string, sys *system.System, config util
135135

136136
func (r CommandMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Command, system.Command, bool, error) {
137137
ctx := context.WithValue(context.Background(), idKey{}, sr)
138-
sysres := sys.NewCommand(ctx, sr, sys, util.Config{})
138+
sysres, _ := sys.NewCommand(ctx, sr, sys, util.Config{})
139139
res, err := NewCommand(sysres, util.Config{})
140140
if err != nil {
141141
return nil, nil, false, err

system/command.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
type Command interface {
15-
Command() string
15+
Command() util.ExecCommand
1616
Exists() (bool, error)
1717
ExitStatus() (int, error)
1818
Stdout() (io.Reader, error)
@@ -21,7 +21,7 @@ type Command interface {
2121

2222
type DefCommand struct {
2323
Ctx context.Context
24-
command string
24+
command util.ExecCommand
2525
exitStatus int
2626
stdout io.Reader
2727
stderr io.Reader
@@ -30,10 +30,29 @@ type DefCommand struct {
3030
err error
3131
}
3232

33-
func NewDefCommand(ctx context.Context, command string, system *System, config util.Config) Command {
33+
func NewDefCommand(ctx context.Context, command any, system *System, config util.Config) (Command, error) {
34+
switch cmd := command.(type) {
35+
case string:
36+
return newDefCommand(ctx, cmd, config), nil
37+
case []string:
38+
return newDefExecCommand(ctx, cmd, config), nil
39+
default:
40+
return nil, fmt.Errorf("command type must be either string or []string")
41+
}
42+
}
43+
44+
func newDefCommand(ctx context.Context, command string, config util.Config) Command {
45+
return &DefCommand{
46+
Ctx: ctx,
47+
command: util.ExecCommand{CmdStr: command},
48+
Timeout: config.TimeOutMilliSeconds(),
49+
}
50+
}
51+
52+
func newDefExecCommand(ctx context.Context, command []string, config util.Config) Command {
3453
return &DefCommand{
3554
Ctx: ctx,
36-
command: command,
55+
command: util.ExecCommand{CmdSlice: command},
3756
Timeout: config.TimeOutMilliSeconds(),
3857
}
3958
}
@@ -44,7 +63,12 @@ func (c *DefCommand) setup() error {
4463
}
4564
c.loaded = true
4665

47-
cmd := commandWrapper(c.command)
66+
var cmd *util.Command
67+
if c.command.CmdStr != "" {
68+
cmd = commandWrapper(c.command.CmdStr)
69+
} else {
70+
cmd = util.NewCommand(c.command.CmdSlice[0], c.command.CmdSlice[1:]...)
71+
}
4872
err := runCommand(cmd, c.Timeout)
4973

5074
// We don't care about ExitError since it's covered by status
@@ -63,7 +87,7 @@ func (c *DefCommand) setup() error {
6387
return c.err
6488
}
6589

66-
func (c *DefCommand) Command() string {
90+
func (c *DefCommand) Command() util.ExecCommand {
6791
return c.command
6892
}
6993

system/system.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type System struct {
2727
NewService func(context.Context, string, *System, util2.Config) Service
2828
NewUser func(context.Context, string, *System, util2.Config) User
2929
NewGroup func(context.Context, string, *System, util2.Config) Group
30-
NewCommand func(context.Context, string, *System, util2.Config) Command
30+
NewCommand func(context.Context, any, *System, util2.Config) (Command, error)
3131
NewDNS func(context.Context, string, *System, util2.Config) DNS
3232
NewProcess func(context.Context, string, *System, util2.Config) Process
3333
NewGossfile func(context.Context, string, *System, util2.Config) Gossfile

util/command.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,52 @@ package util
22

33
import (
44
"bytes"
5+
"encoding/json"
56

67
//"fmt"
78
"os/exec"
89
"syscall"
910
)
1011

12+
// Allows passing a shell style command string
13+
// or an exec style slice of strings.
14+
type ExecCommand struct {
15+
CmdStr string
16+
CmdSlice []string
17+
}
18+
19+
func (e *ExecCommand) UnmarshalJSON(data []byte) error {
20+
// Try to unmarshal as a string
21+
if err := json.Unmarshal(data, &e.CmdStr); err != nil {
22+
// If string unmarshalling fails, try as a slice
23+
return json.Unmarshal(data, &e.CmdSlice)
24+
}
25+
return nil
26+
}
27+
28+
func (e *ExecCommand) UnmarshalYAML(unmarshal func(any) error) error {
29+
// Try to unmarshal as a string
30+
if err := unmarshal(&e.CmdStr); err != nil {
31+
// If string unmarshalling fails, try as a slice
32+
return unmarshal(&e.CmdSlice)
33+
}
34+
return nil
35+
}
36+
37+
func (e ExecCommand) MarshalJSON() ([]byte, error) {
38+
if e.CmdStr != "" {
39+
return json.Marshal(e.CmdStr)
40+
}
41+
return json.Marshal(e.CmdSlice)
42+
}
43+
44+
func (e ExecCommand) MarshalYAML() (any, error) {
45+
if e.CmdStr != "" {
46+
return e.CmdStr, nil
47+
}
48+
return e.CmdSlice, nil
49+
}
50+
1151
type Command struct {
1252
name string
1353
Cmd *exec.Cmd

0 commit comments

Comments
 (0)