diff --git a/go.mod b/go.mod index 526713514f99e71f0ea700ad04d73da5cb59c06b..b0563367ec5b85371a6fffd2545ab544d06f3416 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/vishvananda/netlink v1.3.0 github.com/vishvananda/netns v0.0.5 - github.com/vladimirvivien/gexe v0.4.1 + github.com/vladimirvivien/gexe v0.5.0 github.com/vmware/go-ipfix v0.13.0 golang.org/x/sys v0.31.0 google.golang.org/grpc v1.71.0 diff --git a/go.sum b/go.sum index fb092baabb2f811aaf51c62f21c55d68bef21ce9..8c1104e2be5ed710986f2cab2cae7bf945e255a7 100644 --- a/go.sum +++ b/go.sum @@ -941,8 +941,8 @@ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/vladimirvivien/gexe v0.4.1 h1:W9gWkp8vSPjDoXDu04Yp4KljpVMaSt8IQuHswLDd5LY= -github.com/vladimirvivien/gexe v0.4.1/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= +github.com/vladimirvivien/gexe v0.5.0 h1:AWBVaYnrTsGYBktXvcO0DfWPeSiZxn6mnQ5nvL+A1/A= +github.com/vladimirvivien/gexe v0.5.0/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= github.com/vmware/go-ipfix v0.13.0 h1:v3paBzd7oq7LEU1SzDwD5RGoYcGROLQycYyN3EzLvDk= github.com/vmware/go-ipfix v0.13.0/go.mod h1:UTIR38AuEePzrWYjQOvnORCYRG33xZJ56E0K75mSosM= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= diff --git a/vendor/github.com/vladimirvivien/gexe/exec/builder.go b/vendor/github.com/vladimirvivien/gexe/exec/builder.go index c5395c88835167b0d054c6dbd8b5953b7f96ba55..75e27ea78c689bbba84c455ed8d3c33aad2b2425 100644 --- a/vendor/github.com/vladimirvivien/gexe/exec/builder.go +++ b/vendor/github.com/vladimirvivien/gexe/exec/builder.go @@ -105,12 +105,14 @@ func (cr *PipedCommandResult) LastProc() *Proc { // CommandBuilder is a batch command builder that // can execute commands using different execution policies (i.e. serial, piped, concurrent) type CommandBuilder struct { - cmdPolicy CommandPolicy - procs []*Proc - vars *vars.Variables - err error - stdout io.Writer - stderr io.Writer + cmdPolicy CommandPolicy + procs []*Proc + vars *vars.Variables + err error + stdout io.Writer + stderr io.Writer + shellStr string + cmdStrings []string } // CommandsWithContextVars creates a *CommandBuilder with the specified context and session variables. @@ -118,6 +120,7 @@ type CommandBuilder struct { func CommandsWithContextVars(ctx context.Context, variables *vars.Variables, cmds ...string) *CommandBuilder { cb := new(CommandBuilder) cb.vars = variables + cb.cmdStrings = cmds for _, cmd := range cmds { cb.procs = append(cb.procs, NewProcWithContextVars(ctx, cmd, variables)) } @@ -175,6 +178,12 @@ func (cb *CommandBuilder) WithWorkDir(dir string) *CommandBuilder { return cb } +// WithShell sets the shell to use for all commands +func (cb *CommandBuilder) WithShell(shell string) *CommandBuilder { + cb.shellStr = shell + return cb +} + // Run executes all commands successively and waits for all of the result. The result of each individual // command can be accessed from CommandResult.Procs[] after the execution completes. If policy == ExitOnErrPolicy, the // execution will stop on the first error encountered, otherwise it will continue. Processes with errors can be accessed @@ -281,65 +290,6 @@ func (cb *CommandBuilder) Concurr() *CommandResult { return cb.Start() } -// Pipe executes each command serially chaining the combinedOutput of previous command to the inputPipe of next command. -func (cb *CommandBuilder) Pipe() *PipedCommandResult { - if cb.err != nil { - return &PipedCommandResult{err: cb.err} - } - - var result PipedCommandResult - procLen := len(cb.procs) - if procLen == 0 { - return &PipedCommandResult{} - } - - // wire last proc to combined output - last := procLen - 1 - result.lastProc = cb.procs[last] - - // setup standard output/err for last proc in pipe - result.lastProc.cmd.Stdout = cb.stdout - if cb.stdout == nil { - result.lastProc.cmd.Stdout = result.lastProc.result - } - - result.lastProc.cmd.Stderr = cb.stderr - if cb.stderr == nil { - result.lastProc.cmd.Stderr = result.lastProc.result - } - - result.lastProc.cmd.Stdout = result.lastProc.result - for i, p := range cb.procs[:last] { - pipeout, err := p.cmd.StdoutPipe() - if err != nil { - p.err = err - return &PipedCommandResult{err: err, errProcs: []*Proc{p}} - } - - cb.procs[i+1].cmd.Stdin = pipeout - } - - // start each process (but, not wait for result) - // to ensure data flow between successive processes start - for _, p := range cb.procs { - result.procs = append(result.procs, p) - if err := p.Start().Err(); err != nil { - result.errProcs = append(result.errProcs, p) - return &result - } - } - - // wait and access processes result - for _, p := range cb.procs { - if err := p.Wait().Err(); err != nil { - result.errProcs = append(result.errProcs, p) - break - } - } - - return &result -} - func (cb *CommandBuilder) runCommand(proc *Proc) error { // setup standard out and standard err diff --git a/vendor/github.com/vladimirvivien/gexe/exec/pipe_unix.go b/vendor/github.com/vladimirvivien/gexe/exec/pipe_unix.go new file mode 100644 index 0000000000000000000000000000000000000000..8b07a53bc0f0669ec4aee0de1389836dd6ccb4e4 --- /dev/null +++ b/vendor/github.com/vladimirvivien/gexe/exec/pipe_unix.go @@ -0,0 +1,81 @@ +//go:build !windows + +package exec + +import "errors" + +// Pipe executes each command serially chaining the combinedOutput +// of previous command to the input Pipe of next command. +func (cb *CommandBuilder) Pipe() *PipedCommandResult { + if cb.err != nil { + return &PipedCommandResult{err: cb.err} + } + + result := cb.connectProcPipes() + + // check for structural errors + if result.err != nil { + return result + } + + // start each process (but, not wait for result) + // to ensure data flow between successive processes start + for _, p := range cb.procs { + result.procs = append(result.procs, p) + if err := p.Start().Err(); err != nil { + result.errProcs = append(result.errProcs, p) + return result + } + } + + // wait and access processes result + for _, p := range cb.procs { + if err := p.Wait().Err(); err != nil { + result.errProcs = append(result.errProcs, p) + break + } + } + + return result +} + +// connectProcPipes connects the output of each process to the input of the next process in the chain. +// It returns a PipedCommandResult containing the connected processes and any errors encountered. +func (cb *CommandBuilder) connectProcPipes() *PipedCommandResult { + var result PipedCommandResult + + procLen := len(cb.procs) + if procLen == 0 { + return &PipedCommandResult{err: errors.New("no processes to connect")} + } + + // wire last proc to combined output + last := procLen - 1 + result.lastProc = cb.procs[last] + + // setup standard output/err of last proc in pipe + result.lastProc.cmd.Stdout = cb.stdout + if cb.stdout == nil { + result.lastProc.cmd.Stdout = result.lastProc.result + } + + // Wire standard error of last proc in pipe + result.lastProc.cmd.Stderr = cb.stderr + if cb.stderr == nil { + result.lastProc.cmd.Stderr = result.lastProc.result + } + + // setup pipes for inner procs in the pipe chain + result.lastProc.cmd.Stdout = result.lastProc.result + for i, p := range cb.procs[:last] { + pipeout, err := p.cmd.StdoutPipe() + if err != nil { + p.err = err + return &PipedCommandResult{err: err, errProcs: []*Proc{p}} + } + + cb.procs[i+1].cmd.Stdin = pipeout + } + + return &result +} diff --git a/vendor/github.com/vladimirvivien/gexe/exec/pipe_windows.go b/vendor/github.com/vladimirvivien/gexe/exec/pipe_windows.go new file mode 100644 index 0000000000000000000000000000000000000000..dde8ca1323fcbae3d3d8cae83e65b71e654580c9 --- /dev/null +++ b/vendor/github.com/vladimirvivien/gexe/exec/pipe_windows.go @@ -0,0 +1,80 @@ +//go:build windows + +package exec + +import ( + "bytes" + "errors" + "strings" +) + +// Pipe executes each Windows command serially. Windows, however, does not support +// OS pipes like {Li|U}nix. Instead, pipes use a single command string, with | delimiters, +// passed to powershell. So prior to calling Pipe(), call CommandBulider.WithShell() +// to specify "powershell.exe -c" as the shell. +// (See tests for examples.) +func (cb *CommandBuilder) Pipe() *PipedCommandResult { + if cb.err != nil { + return &PipedCommandResult{err: cb.err} + } + + result := new(PipedCommandResult) + + // setup a single command string with pipe delimiters + cmd := strings.Join(cb.cmdStrings, " | ") + + // Prepend shell command if specified + if cb.shellStr != "" { + cmd = cb.shellStr + " " + cmd + } + + proc := NewProcWithVars(cmd, cb.vars) + result.procs = append(result.procs, proc) + result.lastProc = proc + + // execute the piped commands + if err := cb.runCommand(proc); err != nil { + return &PipedCommandResult{err: err, errProcs: []*Proc{proc}} + } + + return result +} + +// connectProcPipes connects the output of each process to the input of the next process in the chain. +// It returns a PipedCommandResult containing the connected processes and any errors encountered. +func (cb *CommandBuilder) connectProcPipes() *PipedCommandResult { + var result PipedCommandResult + + procLen := len(cb.procs) + if procLen == 0 { + return &PipedCommandResult{err: errors.New("no processes to connect")} + } + + // wire last proc to combined output + last := procLen - 1 + result.lastProc = cb.procs[last] + + // setup standard output/err for last proc in pipe + result.lastProc.cmd.Stdout = cb.stdout + if cb.stdout == nil { + result.lastProc.cmd.Stdout = result.lastProc.result + } + + // Wire the remainder procs + result.lastProc.cmd.Stderr = cb.stderr + if cb.stderr == nil { + result.lastProc.cmd.Stderr = result.lastProc.result + } + + // exec.Command.StdoutPipe() uses OS pipes, which are not supported on Windows. + // Instead, this uses an in-memory pipe and set the command's stdin to the write end of the pipe. + result.lastProc.cmd.Stdout = result.lastProc.result + for i := range cb.procs[:last] { + // Create an in-memory pipe for the command's stdout + pipe := new(bytes.Buffer) + cb.procs[i].cmd.Stdout = pipe + cb.procs[i+1].cmd.Stdin = pipe + } + + return &result +} diff --git a/vendor/github.com/vladimirvivien/gexe/exec/proc.go b/vendor/github.com/vladimirvivien/gexe/exec/proc.go index f9b6a520edd66dc04021506771b6ec0db1a1a372..65439ee48384d057c92a07543a284b6f3231cd58 100644 --- a/vendor/github.com/vladimirvivien/gexe/exec/proc.go +++ b/vendor/github.com/vladimirvivien/gexe/exec/proc.go @@ -10,7 +10,6 @@ import ( "os/user" "strconv" "strings" - "syscall" "time" "github.com/vladimirvivien/gexe/vars" @@ -189,24 +188,7 @@ func (p *Proc) Start() *Proc { } // apply user id and user grp - var procCred *syscall.Credential - if p.userid != nil { - procCred = &syscall.Credential{ - Uid: uint32(*p.userid), - } - } - if p.groupid != nil { - if procCred == nil { - procCred = new(syscall.Credential) - } - procCred.Uid = uint32(*p.groupid) - } - if procCred != nil { - if p.cmd.SysProcAttr == nil { - p.cmd.SysProcAttr = new(syscall.SysProcAttr) - } - p.cmd.SysProcAttr.Credential = procCred - } + p.applyCredentials() if err := p.cmd.Start(); err != nil { p.err = err diff --git a/vendor/github.com/vladimirvivien/gexe/exec/proc_unix.go b/vendor/github.com/vladimirvivien/gexe/exec/proc_unix.go new file mode 100644 index 0000000000000000000000000000000000000000..cdea947a1b1acf900eaec2408969ae50b2d06ac5 --- /dev/null +++ b/vendor/github.com/vladimirvivien/gexe/exec/proc_unix.go @@ -0,0 +1,30 @@ +//go:build !windows + +package exec + +import ( + "syscall" +) + +// applyCredentials applies the user and group IDs to the command. +func (p *Proc) applyCredentials() { + // apply user id and user grp + var procCred *syscall.Credential + if p.userid != nil { + procCred = &syscall.Credential{ + Uid: uint32(*p.userid), + } + } + if p.groupid != nil { + if procCred == nil { + procCred = new(syscall.Credential) + } + procCred.Gid = uint32(*p.groupid) + } + if procCred != nil { + if p.cmd.SysProcAttr == nil { + p.cmd.SysProcAttr = new(syscall.SysProcAttr) + } + p.cmd.SysProcAttr.Credential = procCred + } +} diff --git a/vendor/github.com/vladimirvivien/gexe/exec/proc_windows.go b/vendor/github.com/vladimirvivien/gexe/exec/proc_windows.go new file mode 100644 index 0000000000000000000000000000000000000000..88ad91b9945fad839309f553f7d886c4c4e4c7bf --- /dev/null +++ b/vendor/github.com/vladimirvivien/gexe/exec/proc_windows.go @@ -0,0 +1,9 @@ +//go:build windows + +package exec + +// applyCredentials is a no-op as this works vastly different on Windows. +func (p *Proc) applyCredentials() { + // Windows doesn't support user/group IDs in the same way {Li|U}nix does. + // Windows impersonation will not be supported in this package a this time. +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8ccdd81e7c795fafc96988efe87147be6d5759cb..ee9a7877ccb6371e9ea8084f3d34fe17fcf0c6e0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -549,7 +549,7 @@ github.com/vishvananda/netlink/nl # github.com/vishvananda/netns v0.0.5 ## explicit; go 1.17 github.com/vishvananda/netns -# github.com/vladimirvivien/gexe v0.4.1 +# github.com/vladimirvivien/gexe v0.5.0 ## explicit; go 1.23 github.com/vladimirvivien/gexe github.com/vladimirvivien/gexe/exec