File: command.go

package info (click to toggle)
golang-github-smallstep-cli 0.15.16%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 4,404 kB
  • sloc: sh: 512; makefile: 99
file content (147 lines) | stat: -rw-r--r-- 3,894 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package integration

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"regexp"
	"strings"
	"testing"

	"github.com/ThomasRooney/gexpect"
	"github.com/smallstep/assert"
)

// CleanOutput returns the output from the cursor character.
func CleanOutput(str string) string {
	if i := strings.Index(str, "?25h"); i > 0 {
		return str[i+4:]
	}
	return str
}

// Command executes a shell command.
func Command(command string) *exec.Cmd {
	return exec.Command("sh", "-c", command)
}

// ExitError converts an error to an exec.ExitError.
func ExitError(err error) (*exec.ExitError, bool) {
	v, ok := err.(*exec.ExitError)
	return v, ok
}

// Output executes a shell command and returns output from stdout.
func Output(command string) ([]byte, error) {
	return Command(command).Output()
}

// CombinedOutput executes a shell command and returns combined output from
// stdout and stderr.
func CombinedOutput(command string) ([]byte, error) {
	return Command(command).CombinedOutput()
}

// WithStdin executes a shell command with a provided reader used for stdin.
func WithStdin(command string, r io.Reader) ([]byte, error) {
	cmd := Command(command)
	cmd.Stdin = r
	return cmd.Output()
}

// CLICommand repreents a command-line command to execute.
type CLICommand struct {
	command   string
	arguments string
	flags     map[string]string
	stdin     io.Reader
}

// CLIOutput represents the output from executing a CLICommand.
// nolint:unused
type CLIOutput struct {
	stdout   string
	stderr   string
	combined string
}

// NewCLICommand generates a new CLICommand.
func NewCLICommand() CLICommand {
	return CLICommand{"", "", make(map[string]string), nil}
}

func (c CLICommand) setFlag(flag, value string) CLICommand {
	flags := make(map[string]string)
	for k, v := range c.flags {
		flags[k] = v
	}
	flags[flag] = value
	return CLICommand{c.command, c.arguments, flags, c.stdin}
}

func (c CLICommand) setCommand(command string) CLICommand {
	return CLICommand{command, c.arguments, c.flags, c.stdin}
}

func (c CLICommand) setArguments(arguments string) CLICommand {
	return CLICommand{c.command, arguments, c.flags, c.stdin}
}

func (c CLICommand) setStdin(stdin string) CLICommand {
	return CLICommand{c.command, c.arguments, c.flags, strings.NewReader(stdin)}
}

func (c CLICommand) cmd() string {
	flags := ""
	for key, value := range c.flags {
		if strings.Contains(value, " ") {
			value = "\"" + value + "\""
		}
		flags += fmt.Sprintf("--%s %s ", key, value)
	}
	return fmt.Sprintf("%s %s %s", c.command, c.arguments, flags)
}

func (c CLICommand) run() (CLIOutput, error) {
	var stdout, stderr, combined bytes.Buffer
	cmd := Command(c.cmd())
	cmd.Stdout = io.MultiWriter(&stdout, &combined)
	cmd.Stderr = io.MultiWriter(&stderr, &combined)
	cmd.Stdin = c.stdin
	err := cmd.Run()
	return CLIOutput{stdout.String(), stderr.String(), combined.String()}, err
}

func (c CLICommand) spawn() (*gexpect.ExpectSubprocess, error) {
	return gexpect.Spawn(c.cmd())
}

func (c CLICommand) test(t *testing.T, name string, expected string, msg ...interface{}) {
	t.Run(name, func(t *testing.T) {
		out, err := c.run()
		assert.FatalError(t, err, fmt.Sprintf("`%s`: returned error '%s'\n\nOutput:\n%s", c.cmd(), err, out.combined))
		assert.Equals(t, out.combined, expected, msg...)
	})
}

func (c CLICommand) fail(t *testing.T, name string, expected interface{}, msg ...interface{}) {
	t.Run(name, func(t *testing.T) {
		out, err := c.run()
		if assert.NotNil(t, err) {
			assert.Equals(t, err.Error(), "exit status 1")
		}
		switch v := expected.(type) {
		case string:
			assert.Equals(t, expected, out.stderr)
		case *regexp.Regexp:
			re := expected.(*regexp.Regexp)
			if !re.MatchString(out.stderr) {
				t.Errorf("Error message did not match regex:\n  Regex: %s\n\n  Output:\n%s", re.String(), out.stderr)
			}
		default:
			t.Errorf("unexpected type %T", v)
		}
		assert.Equals(t, "", out.stdout)
	})
}