File: run.go

package info (click to toggle)
incus 6.0.4-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 23,864 kB
  • sloc: sh: 16,015; ansic: 3,121; python: 456; makefile: 321; ruby: 51; sql: 50; lisp: 6
file content (162 lines) | stat: -rw-r--r-- 5,099 bytes parent folder | download | duplicates (3)
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package subprocess

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"time"
)

// RunError is the error from the RunCommand family of functions.
type RunError struct {
	cmd    string
	args   []string
	err    error
	stdout *bytes.Buffer
	stderr *bytes.Buffer
}

func (e RunError) Error() string {
	if e.stderr.Len() == 0 {
		return fmt.Sprintf("Failed to run: %s %s: %v", e.cmd, strings.Join(e.args, " "), e.err)
	}

	return fmt.Sprintf("Failed to run: %s %s: %v (%s)", e.cmd, strings.Join(e.args, " "), e.err, strings.TrimSpace(e.stderr.String()))
}

func (e RunError) Unwrap() error {
	return e.err
}

// StdOut returns the stdout buffer.
func (e RunError) StdOut() *bytes.Buffer {
	return e.stdout
}

// StdErr returns the stdout buffer.
func (e RunError) StdErr() *bytes.Buffer {
	return e.stderr
}

// NewRunError returns new RunError.
func NewRunError(cmd string, args []string, err error, stdout *bytes.Buffer, stderr *bytes.Buffer) error {
	return RunError{
		cmd:    cmd,
		args:   args,
		err:    err,
		stdout: stdout,
		stderr: stderr,
	}
}

// RunCommandSplit runs a command with a supplied environment and optional arguments and returns the
// resulting stdout and stderr output as separate variables. If the supplied environment is nil then
// the default environment is used. If the command fails to start or returns a non-zero exit code
// then an error is returned containing the output of stderr too.
func RunCommandSplit(ctx context.Context, env []string, filesInherit []*os.File, name string, arg ...string) (string, string, error) {
	cmd := exec.CommandContext(ctx, name, arg...)

	if env != nil {
		cmd.Env = env
	}

	if filesInherit != nil {
		cmd.ExtraFiles = filesInherit
	}

	var stdout bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()
	if err != nil {
		return stdout.String(), stderr.String(), NewRunError(name, arg, err, &stdout, &stderr)
	}

	return stdout.String(), stderr.String(), nil
}

// RunCommandContext runs a command with optional arguments and returns stdout. If the command fails to
// start or returns a non-zero exit code then an error is returned containing the output of stderr.
func RunCommandContext(ctx context.Context, name string, arg ...string) (string, error) {
	stdout, _, err := RunCommandSplit(ctx, nil, nil, name, arg...)
	return stdout, err
}

// RunCommand runs a command with optional arguments and returns stdout. If the command fails to
// start or returns a non-zero exit code then an error is returned containing the output of stderr.
// Deprecated: Use RunCommandContext.
func RunCommand(name string, arg ...string) (string, error) {
	stdout, _, err := RunCommandSplit(context.TODO(), nil, nil, name, arg...)
	return stdout, err
}

// RunCommandInheritFds runs a command with optional arguments and passes a set
// of file descriptors to the newly created process, returning stdout. If the
// command fails to start or returns a non-zero exit code then an error is
// returned containing the output of stderr.
func RunCommandInheritFds(ctx context.Context, filesInherit []*os.File, name string, arg ...string) (string, error) {
	stdout, _, err := RunCommandSplit(ctx, nil, filesInherit, name, arg...)
	return stdout, err
}

// RunCommandCLocale runs a command with a LC_ALL=C.UTF-8 and LANGUAGE=en environment set with optional arguments and
// returns stdout. If the command fails to start or returns a non-zero exit code then an error is
// returned containing the output of stderr.
func RunCommandCLocale(name string, arg ...string) (string, error) {
	stdout, _, err := RunCommandSplit(context.TODO(), append(os.Environ(), "LC_ALL=C.UTF-8", "LANGUAGE=en"), nil, name, arg...)
	return stdout, err
}

// RunCommandWithFds runs a command with supplied file descriptors.
func RunCommandWithFds(ctx context.Context, stdin io.Reader, stdout io.Writer, name string, arg ...string) error {
	cmd := exec.CommandContext(ctx, name, arg...)

	if stdin != nil {
		cmd.Stdin = stdin
	}

	if stdout != nil {
		cmd.Stdout = stdout
	}

	var buffer bytes.Buffer
	cmd.Stderr = &buffer

	err := cmd.Run()
	if err != nil {
		return NewRunError(name, arg, err, nil, &buffer)
	}

	return nil
}

// TryRunCommand runs the specified command up to 20 times with a 500ms delay between each call
// until it runs without an error. If after 20 times it is still failing then returns the error.
func TryRunCommand(name string, arg ...string) (string, error) {
	return TryRunCommandAttemptsDuration(20, 500*time.Millisecond, name, arg...)
}

// TryRunCommandAttemptsDuration runs the specified command up to a specified number times with a
// specified delay between each call until it runs without an error. If after the number of times
// it is still failing then returns the error.
func TryRunCommandAttemptsDuration(attempts int, delay time.Duration, name string, arg ...string) (string, error) {
	var err error
	var output string

	for i := 0; i < attempts; i++ {
		output, err = RunCommand(name, arg...)
		if err == nil {
			break
		}

		time.Sleep(delay)
	}

	return output, err
}