• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openshift-kni / lifecycle-agent / 567839dabd85027fcc379712ab73d3ce3dd9d8a8

20 May 2026 04:08PM UTC coverage: 30.271% (+0.5%) from 29.8%
567839dabd85027fcc379712ab73d3ce3dd9d8a8

push

web-flow
Merge pull request #6318 from sebrandon1/CNF-23417

CNF-23417: Add unit tests for rollback handlers

4920 of 16253 relevant lines covered (30.27%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lca-cli/ops/execute.go
1
package ops
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "io"
7
        "os/exec"
8
        "strings"
9
        "syscall"
10

11
        "github.com/sirupsen/logrus"
12
)
13

14
// Execute is an interface for executing external commands and capturing their output.
15
//
16
//go:generate mockgen -source=execute.go -package=ops -destination=mock_execute.go
17

18
const nsenter = "nsenter"
19

20
type Execute interface {
21
        Execute(command string, args ...string) (string, error)
22
        ExecuteWithLiveLogger(command string, args ...string) (string, error)
23
}
24

25
type executor struct {
26
        log     *logrus.Logger
27
        verbose bool
28
}
29

30
func (e *executor) execute(liveLogger io.Writer, root, command string, args ...string) (string, error) {
×
31
        e.log.Infof("Executing %s with args %s", command, args)
×
32
        cmd := exec.Command(command, args...) //nolint:gosec // command is validated by caller
×
33
        var stdoutBytes bytes.Buffer
×
34
        if liveLogger != nil {
×
35
                cmd.Stdout = io.MultiWriter(liveLogger, &stdoutBytes)
×
36
                cmd.Stderr = io.MultiWriter(liveLogger, &stdoutBytes)
×
37
        } else {
×
38
                cmd.Stdout = &stdoutBytes
×
39
                cmd.Stderr = &stdoutBytes
×
40
        }
×
41
        if root != "" {
×
42
                cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: root}
×
43
                cmd.Dir = "/"
×
44
        }
×
45

46
        err := cmd.Run()
×
47
        stdoutBytesTrimmed := strings.TrimSpace(stdoutBytes.String())
×
48
        if err != nil {
×
49
                return stdoutBytesTrimmed, fmt.Errorf("%s: %w", stdoutBytes.String(), err)
×
50
        }
×
51
        return stdoutBytesTrimmed, nil
×
52
}
53

54
type regularExecutor struct {
55
        executor
56
}
57

58
func NewRegularExecutor(logger *logrus.Logger, verbose bool) Execute {
×
59
        return &regularExecutor{executor: executor{logger, verbose}}
×
60
}
×
61

62
func (e *regularExecutor) Execute(command string, args ...string) (string, error) {
×
63
        return e.execute(nil, "", command, args...)
×
64
}
×
65

66
func (e *regularExecutor) ExecuteWithLiveLogger(command string, args ...string) (string, error) {
×
67
        return e.execute(e.log.Writer(), "", command, args...)
×
68
}
×
69

70
type nsenterExecutor struct {
71
        executor
72
}
73

74
func NewNsenterExecutor(logger *logrus.Logger, verbose bool) Execute {
×
75
        return &nsenterExecutor{executor: executor{logger, verbose}}
×
76
}
×
77

78
func (e *nsenterExecutor) ExecuteWithLiveLogger(command string, args ...string) (string, error) {
×
79
        return e.baseExecute(e.log.Writer(), command, args...)
×
80
}
×
81

82
func (e *nsenterExecutor) baseExecute(writer io.Writer, command string, args ...string) (string, error) {
×
83
        // nsenter is used here to launch processes inside the container in a way that makes said processes feel
×
84
        // and behave as if they're running on the host directly rather than inside the container
×
85
        arguments := append(nsenterArgs(), command)
×
86
        arguments = append(arguments, args...)
×
87
        return e.execute(writer, "", nsenter, arguments...)
×
88
}
×
89

90
func (e *nsenterExecutor) Execute(command string, args ...string) (string, error) {
×
91
        return e.baseExecute(nil, command, args...)
×
92
}
×
93

94
type chrootExecutor struct {
95
        executor
96
        root string
97
}
98

99
func NewChrootExecutor(logger *logrus.Logger, verbose bool, root string) Execute {
×
100
        return &chrootExecutor{executor: executor{logger, verbose}, root: root}
×
101
}
×
102

103
// Running a command with chroot using exec.Command runs into issues with exec.LookPath,
104
// if an absolute path is not used for the "command", as it does not account for the chroot dir.
105
// To workaround this issue, prefix the command with /usr/bin/env.
106
func (e *chrootExecutor) baseExecute(writer io.Writer, command string, args ...string) (string, error) {
×
107
        commandBase := "/usr/bin/env"
×
108
        args = append([]string{"--", command}, args...)
×
109
        return e.execute(writer, e.root, commandBase, args...)
×
110
}
×
111

112
func (e *chrootExecutor) Execute(command string, args ...string) (string, error) {
×
113
        return e.baseExecute(nil, command, args...)
×
114
}
×
115

116
func (e *chrootExecutor) ExecuteWithLiveLogger(command string, args ...string) (string, error) {
×
117
        return e.baseExecute(e.log.Writer(), command, args...)
×
118
}
×
119

120
func nsenterArgs() []string {
×
121
        return []string{
×
122
                "--target", "1",
×
123
                // Entering the cgroup namespace is not required for podman on CoreOS (where the
×
124
                // agent typically runs), but it's needed on some Fedora versions and
×
125
                // some other systemd based systems. Those systems are used to run dry-mode
×
126
                // agents for load testing. If this flag is not used, Podman will sometimes
×
127
                // have trouble creating a systemd cgroup slice for new containers.
×
128
                "--cgroup",
×
129
                // The mount namespace is required for podman to access the host's container
×
130
                // storage
×
131
                "--mount",
×
132
                // TODO: Document why we need the IPC namespace
×
133
                "--ipc",
×
134
                // Enter the host network namespace so networking tools see host routes/links
×
135
                "--net",
×
136
                "--pid",
×
137
                "--",
×
138
        }
×
139
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc