diff --git a/go.mod b/go.mod index cb8242fc..4125507f 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.3 require ( al.essio.dev/pkg/shellescape v1.5.0 - github.com/bitfield/script v0.22.1 + github.com/bitfield/script v0.23.0 github.com/dustin/go-humanize v1.0.1 github.com/magefile/mage v1.15.0 github.com/matryer/is v1.4.1 diff --git a/go.sum b/go.sum index 052175ce..193c5480 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYew github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= -github.com/bitfield/script v0.22.1 h1:DphxoC5ssYciwd0ZS+N0Xae46geAD/0mVWh6a2NUxM4= -github.com/bitfield/script v0.22.1/go.mod h1:fv+6x4OzVsRs6qAlc7wiGq8fq1b5orhtQdtW0dwjUHI= +github.com/bitfield/script v0.23.0 h1:N0R5yLEl6wJIS9PR/A6xXwjMsplMubyxdi05N5l0X28= +github.com/bitfield/script v0.23.0/go.mod h1:fv+6x4OzVsRs6qAlc7wiGq8fq1b5orhtQdtW0dwjUHI= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= diff --git a/vendor/github.com/bitfield/script/README.md b/vendor/github.com/bitfield/script/README.md index a06a70c1..98e87ec4 100644 --- a/vendor/github.com/bitfield/script/README.md +++ b/vendor/github.com/bitfield/script/README.md @@ -1,7 +1,8 @@ [![Go Reference](https://pkg.go.dev/badge/github.com/bitfield/script.svg)](https://pkg.go.dev/github.com/bitfield/script) [![Go Report Card](https://goreportcard.com/badge/github.com/bitfield/script)](https://goreportcard.com/report/github.com/bitfield/script) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go) -![Tests](https://github.com/bitfield/script/actions/workflows/test.yml/badge.svg) +![CI](https://github.com/bitfield/script/actions/workflows/ci.yml/badge.svg) +![Audit](https://github.com/bitfield/script/actions/workflows/audit.yml/badge.svg) ```go import "github.com/bitfield/script" @@ -33,6 +34,7 @@ If you're already familiar with shell scripting and the Unix toolset, here is a | `>` | [`WriteFile`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WriteFile) | | `>>` | [`AppendFile`](https://pkg.go.dev/github.com/bitfield/script#Pipe.AppendFile) | | `$*` | [`Args`](https://pkg.go.dev/github.com/bitfield/script#Args) | +| `base64` | [`DecodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.DecodeBase64) / [`EncodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.EncodeBase64) | | `basename` | [`Basename`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Basename) | | `cat` | [`File`](https://pkg.go.dev/github.com/bitfield/script#File) / [`Concat`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Concat) | | `curl` | [`Do`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Do) / [`Get`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Get) / [`Post`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Post) | @@ -267,18 +269,31 @@ These are functions that create a pipe with a given contents: | Source | Contents | | -------- | ------------- | -| [`Args`](https://pkg.go.dev/github.com/bitfield/script#Args) | command-line arguments -| [`Do`](https://pkg.go.dev/github.com/bitfield/script#Do) | HTTP response -| [`Echo`](https://pkg.go.dev/github.com/bitfield/script#Echo) | a string -| [`Exec`](https://pkg.go.dev/github.com/bitfield/script#Exec) | command output -| [`File`](https://pkg.go.dev/github.com/bitfield/script#File) | file contents -| [`FindFiles`](https://pkg.go.dev/github.com/bitfield/script#FindFiles) | recursive file listing -| [`Get`](https://pkg.go.dev/github.com/bitfield/script#Get) | HTTP response -| [`IfExists`](https://pkg.go.dev/github.com/bitfield/script#IfExists) | do something only if some file exists -| [`ListFiles`](https://pkg.go.dev/github.com/bitfield/script#ListFiles) | file listing (including wildcards) -| [`Post`](https://pkg.go.dev/github.com/bitfield/script#Post) | HTTP response -| [`Slice`](https://pkg.go.dev/github.com/bitfield/script#Slice) | slice elements, one per line -| [`Stdin`](https://pkg.go.dev/github.com/bitfield/script#Stdin) | standard input +| [`Args`](https://pkg.go.dev/github.com/bitfield/script#Args) | command-line arguments | +| [`Do`](https://pkg.go.dev/github.com/bitfield/script#Do) | HTTP response | +| [`Echo`](https://pkg.go.dev/github.com/bitfield/script#Echo) | a string | +| [`Exec`](https://pkg.go.dev/github.com/bitfield/script#Exec) | command output | +| [`File`](https://pkg.go.dev/github.com/bitfield/script#File) | file contents | +| [`FindFiles`](https://pkg.go.dev/github.com/bitfield/script#FindFiles) | recursive file listing | +| [`Get`](https://pkg.go.dev/github.com/bitfield/script#Get) | HTTP response | +| [`IfExists`](https://pkg.go.dev/github.com/bitfield/script#IfExists) | do something only if some file exists | +| [`ListFiles`](https://pkg.go.dev/github.com/bitfield/script#ListFiles) | file listing (including wildcards) | +| [`Post`](https://pkg.go.dev/github.com/bitfield/script#Post) | HTTP response | +| [`Slice`](https://pkg.go.dev/github.com/bitfield/script#Slice) | slice elements, one per line | +| [`Stdin`](https://pkg.go.dev/github.com/bitfield/script#Stdin) | standard input | + +## Modifiers + +These are methods on a pipe that change its configuration: + +| Source | Modifies | +| -------- | ------------- | +| [`WithEnv`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithEnv) | environment for commands | +| [`WithError`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithError) | pipe error status | +| [`WithHTTPClient`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithHTTPClient) | client for HTTP requests | +| [`WithReader`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithReader) | pipe source | +| [`WithStderr`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithStderr) | standard error output stream for command | +| [`WithStdout`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithStdout) | standard output stream for pipe | ## Filters @@ -289,9 +304,11 @@ Filters are methods on an existing pipe that also return a pipe, allowing you to | [`Basename`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Basename) | removes leading path components from each line, leaving only the filename | | [`Column`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Column) | Nth column of input | | [`Concat`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Concat) | contents of multiple files | +| [`DecodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.DecodeBase64) | input decoded from base64 | | [`Dirname`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Dirname) | removes filename from each line, leaving only leading path components | | [`Do`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Do) | response to supplied HTTP request | | [`Echo`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Echo) | all input replaced by given string | +| [`EncodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.EncodeBase64) | input encoded to base64 | | [`Exec`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Exec) | filtered through external command | | [`ExecForEach`](https://pkg.go.dev/github.com/bitfield/script#Pipe.ExecForEach) | execute given command template for each line of input | | [`Filter`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Filter) | user-supplied function filtering a reader to a writer | @@ -329,13 +346,16 @@ Sinks are methods that return some data from a pipe, ending the pipeline and ext | [`Slice`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Slice) | | data as `[]string`, error | | [`Stdout`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Stdout) | standard output | bytes written, error | | [`String`](https://pkg.go.dev/github.com/bitfield/script#Pipe.String) | | data as `string`, error | -| [`Wait`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Wait) | | none | +| [`Wait`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Wait) | | error | | [`WriteFile`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WriteFile) | specified file, truncating if it exists | bytes written, error | # What's new | Version | New | | ----------- | ------- | +| 0.23.0 | [`WithEnv`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithEnv) | +| | [`DecodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.DecodeBase64) / [`EncodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.EncodeBase64) | +| | [`Wait`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Wait) returns error | | v0.22.0 | [`Tee`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Tee), [`WithStderr`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithStderr) | | v0.21.0 | HTTP support: [`Do`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Do), [`Get`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Get), [`Post`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Post) | | v0.20.0 | [`JQ`](https://pkg.go.dev/github.com/bitfield/script#Pipe.JQ) | @@ -346,7 +366,7 @@ See the [contributor's guide](CONTRIBUTING.md) for some helpful tips if you'd li # Links -- [Scripting with Go](https://bitfieldconsulting.com/golang/scripting) +- [Scripting with Go](https://bitfieldconsulting.com/posts/scripting) - [Code Club: Script](https://www.youtube.com/watch?v=6S5EqzVwpEg) - [Bitfield Consulting](https://bitfieldconsulting.com/) - [Go books by John Arundel](https://bitfieldconsulting.com/books) diff --git a/vendor/github.com/bitfield/script/script.go b/vendor/github.com/bitfield/script/script.go index c471f74b..e25b1c48 100644 --- a/vendor/github.com/bitfield/script/script.go +++ b/vendor/github.com/bitfield/script/script.go @@ -4,6 +4,7 @@ import ( "bufio" "container/ring" "crypto/sha256" + "encoding/base64" "encoding/hex" "encoding/json" "fmt" @@ -27,13 +28,14 @@ import ( // Pipe represents a pipe object with an associated [ReadAutoCloser]. type Pipe struct { // Reader is the underlying reader. - Reader ReadAutoCloser - stdout, stderr io.Writer - httpClient *http.Client + Reader ReadAutoCloser + stdout io.Writer + httpClient *http.Client - // because pipe stages are concurrent, protect 'err' - mu *sync.Mutex - err error + mu *sync.Mutex + err error + stderr io.Writer + env []string } // Args creates a pipe containing the program's command-line arguments from @@ -166,6 +168,7 @@ func NewPipe() *Pipe { mu: new(sync.Mutex), stdout: os.Stdout, httpClient: http.DefaultClient, + env: nil, } } @@ -275,6 +278,18 @@ func (p *Pipe) CountLines() (lines int, err error) { return lines, p.Error() } +// DecodeBase64 produces the string represented by the base64 encoded input. +func (p *Pipe) DecodeBase64() *Pipe { + return p.Filter(func(r io.Reader, w io.Writer) error { + decoder := base64.NewDecoder(base64.StdEncoding, r) + _, err := io.Copy(w, decoder) + if err != nil { + return err + } + return nil + }) +} + // Dirname reads paths from the pipe, one per line, and produces only the // parent directories of each path. For example, /usr/local/bin/foo would // become just /usr/local/bin. This is the complementary operation to @@ -347,7 +362,29 @@ func (p *Pipe) Echo(s string) *Pipe { return p.WithReader(strings.NewReader(s)) } +// EncodeBase64 produces the base64 encoding of the input. +func (p *Pipe) EncodeBase64() *Pipe { + return p.Filter(func(r io.Reader, w io.Writer) error { + encoder := base64.NewEncoder(base64.StdEncoding, w) + defer encoder.Close() + _, err := io.Copy(encoder, r) + if err != nil { + return err + } + return nil + }) +} + +func (p *Pipe) environment() []string { + p.mu.Lock() + defer p.mu.Unlock() + return p.env +} + // Error returns any error present on the pipe, or nil otherwise. +// Error is not a sink and does not wait until the pipe reaches +// completion. To wait for completion before returning the error, +// see [Pipe.Wait]. func (p *Pipe) Error() error { if p.mu == nil { // uninitialised pipe return nil @@ -362,6 +399,11 @@ func (p *Pipe) Error() error { // error output). The effect of this is to filter the contents of the pipe // through the external command. // +// # Environment +// +// The command inherits the current process's environment, optionally modified +// by [Pipe.WithEnv]. +// // # Error handling // // If the command had a non-zero exit status, the pipe's error status will also @@ -385,8 +427,13 @@ func (p *Pipe) Exec(cmdLine string) *Pipe { cmd.Stdin = r cmd.Stdout = w cmd.Stderr = w - if p.stderr != nil { - cmd.Stderr = p.stderr + pipeStderr := p.stdErr() + if pipeStderr != nil { + cmd.Stderr = pipeStderr + } + pipeEnv := p.environment() + if pipeEnv != nil { + cmd.Env = pipeEnv } err = cmd.Start() if err != nil { @@ -399,7 +446,8 @@ func (p *Pipe) Exec(cmdLine string) *Pipe { // ExecForEach renders cmdLine as a Go template for each line of input, running // the resulting command, and produces the combined output of all these -// commands in sequence. See [Pipe.Exec] for error handling details. +// commands in sequence. See [Pipe.Exec] for details on error handling and +// environment variables. // // This is mostly useful for substituting data into commands using Go template // syntax. For example: @@ -425,8 +473,12 @@ func (p *Pipe) ExecForEach(cmdLine string) *Pipe { cmd := exec.Command(args[0], args[1:]...) cmd.Stdout = w cmd.Stderr = w - if p.stderr != nil { - cmd.Stderr = p.stderr + pipeStderr := p.stdErr() + if pipeStderr != nil { + cmd.Stderr = pipeStderr + } + if p.env != nil { + cmd.Env = p.env } err = cmd.Start() if err != nil { @@ -810,6 +862,18 @@ func (p *Pipe) Slice() ([]string, error) { return result, p.Error() } +// stdErr returns the pipe's configured standard error writer for commands run +// via [Pipe.Exec] and [Pipe.ExecForEach]. The default is nil, which means that +// error output will go to the pipe. +func (p *Pipe) stdErr() io.Writer { + if p.mu == nil { // uninitialised pipe + return nil + } + p.mu.Lock() + defer p.mu.Unlock() + return p.stderr +} + // Stdout copies the pipe's contents to its configured standard output (using // [Pipe.WithStdout]), or to [os.Stdout] otherwise, and returns the number of // bytes successfully written, together with any error. @@ -848,14 +912,25 @@ func (p *Pipe) Tee(writers ...io.Writer) *Pipe { return p.WithReader(io.TeeReader(p.Reader, teeWriter)) } -// Wait reads the pipe to completion and discards the result. This is mostly -// useful for waiting until concurrent filters have completed (see -// [Pipe.Filter]). -func (p *Pipe) Wait() { +// Wait reads the pipe to completion and returns any error present on +// the pipe, or nil otherwise. This is mostly useful for waiting until +// concurrent filters have completed (see [Pipe.Filter]). +func (p *Pipe) Wait() error { _, err := io.Copy(io.Discard, p) if err != nil { p.SetError(err) } + return p.Error() +} + +// WithEnv sets the environment for subsequent [Pipe.Exec] and [Pipe.ExecForEach] +// commands to the string slice env, using the same format as [os/exec.Cmd.Env]. +// An empty slice unsets all existing environment variables. +func (p *Pipe) WithEnv(env []string) *Pipe { + p.mu.Lock() + defer p.mu.Unlock() + p.env = env + return p } // WithError sets the error err on the pipe. @@ -883,10 +958,11 @@ func (p *Pipe) WithReader(r io.Reader) *Pipe { return p } -// WithStderr redirects the standard error output for commands run via -// [Pipe.Exec] or [Pipe.ExecForEach] to the writer w, instead of going to the -// pipe as it normally would. +// WithStderr sets the standard error output for [Pipe.Exec] or +// [Pipe.ExecForEach] commands to w, instead of the pipe. func (p *Pipe) WithStderr(w io.Writer) *Pipe { + p.mu.Lock() + defer p.mu.Unlock() p.stderr = w return p } diff --git a/vendor/modules.txt b/vendor/modules.txt index c7c3f7c8..502599f2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -12,7 +12,7 @@ atomicgo.dev/keyboard/keys # atomicgo.dev/schedule v0.1.0 ## explicit; go 1.18 atomicgo.dev/schedule -# github.com/bitfield/script v0.22.1 +# github.com/bitfield/script v0.23.0 ## explicit; go 1.18 github.com/bitfield/script # github.com/containerd/console v1.0.4