Files
docker-cli/cli/command/inspect/inspector.go
Sebastiaan van Stijn 1d768f8983 update go:build tags to go1.23 to align with vendor.mod
Go maintainers started to unconditionally update the minimum go version
for golang.org/x/ dependencies to go1.23, which means that we'll no longer
be able to support any version below that when updating those dependencies;

> all: upgrade go directive to at least 1.23.0 [generated]
>
> By now Go 1.24.0 has been released, and Go 1.22 is no longer supported
> per the Go Release Policy (https://go.dev/doc/devel/release#policy).
>
> For golang/go#69095.

This updates our minimum version to go1.23, as we won't be able to maintain
compatibility with older versions because of the above.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-04-17 10:43:47 +02:00

225 lines
5.9 KiB
Go

// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
package inspect
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"text/template"
"github.com/docker/cli/cli"
"github.com/docker/cli/templates"
"github.com/sirupsen/logrus"
)
// Inspector defines an interface to implement to process elements
type Inspector interface {
// Inspect writes the raw element in JSON format.
Inspect(typedElement any, rawElement []byte) error
// Flush writes the result of inspecting all elements into the output stream.
Flush() error
}
// TemplateInspector uses a text template to inspect elements.
type TemplateInspector struct {
outputStream io.Writer
buffer *bytes.Buffer
tmpl *template.Template
}
// NewTemplateInspector creates a new inspector with a template.
func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
return &TemplateInspector{
outputStream: outputStream,
buffer: new(bytes.Buffer),
tmpl: tmpl,
}
}
// NewTemplateInspectorFromString creates a new TemplateInspector from a string
// which is compiled into a template.
func NewTemplateInspectorFromString(out io.Writer, tmplStr string) (Inspector, error) {
if tmplStr == "" {
return NewIndentedInspector(out), nil
}
if tmplStr == "json" {
return NewJSONInspector(out), nil
}
tmpl, err := templates.Parse(tmplStr)
if err != nil {
return nil, fmt.Errorf("template parsing error: %w", err)
}
return NewTemplateInspector(out, tmpl), nil
}
// GetRefFunc is a function which used by Inspect to fetch an object from a
// reference
type GetRefFunc func(ref string) (any, []byte, error)
// Inspect fetches objects by reference using GetRefFunc and writes the json
// representation to the output writer.
func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFunc) error {
inspector, err := NewTemplateInspectorFromString(out, tmplStr)
if err != nil {
return cli.StatusError{StatusCode: 64, Status: err.Error()}
}
var errs []error
for _, ref := range references {
element, raw, err := getRef(ref)
if err != nil {
errs = append(errs, err)
continue
}
if err := inspector.Inspect(element, raw); err != nil {
errs = append(errs, err)
}
}
if err := inspector.Flush(); err != nil {
logrus.Error(err)
}
if err := errors.Join(errs...); err != nil {
return cli.StatusError{
StatusCode: 1,
Status: err.Error(),
}
}
return nil
}
// Inspect executes the inspect template.
// It decodes the raw element into a map if the initial execution fails.
// This allows docker cli to parse inspect structs injected with Swarm fields.
func (i *TemplateInspector) Inspect(typedElement any, rawElement []byte) error {
buffer := new(bytes.Buffer)
if err := i.tmpl.Execute(buffer, typedElement); err != nil {
if rawElement == nil {
return fmt.Errorf("template parsing error: %w", err)
}
return i.tryRawInspectFallback(rawElement)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}
// tryRawInspectFallback executes the inspect template with a raw interface.
// This allows docker cli to parse inspect structs injected with Swarm fields.
func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
var raw any
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
dec.UseNumber()
if err := dec.Decode(&raw); err != nil {
return fmt.Errorf("unable to read inspect data: %w", err)
}
tmplMissingKey := i.tmpl.Option("missingkey=error")
if err := tmplMissingKey.Execute(buffer, raw); err != nil {
return fmt.Errorf("template parsing error: %w", err)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}
// Flush writes the result of inspecting all elements into the output stream.
func (i *TemplateInspector) Flush() error {
if i.buffer.Len() == 0 {
_, err := io.WriteString(i.outputStream, "\n")
return err
}
_, err := io.Copy(i.outputStream, i.buffer)
return err
}
// NewIndentedInspector generates a new inspector with an indented representation
// of elements.
func NewIndentedInspector(outputStream io.Writer) Inspector {
return &elementsInspector{
outputStream: outputStream,
raw: func(dst *bytes.Buffer, src []byte) error {
return json.Indent(dst, src, "", " ")
},
el: func(v any) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
},
}
}
// NewJSONInspector generates a new inspector with a compact representation
// of elements.
func NewJSONInspector(outputStream io.Writer) Inspector {
return &elementsInspector{
outputStream: outputStream,
raw: json.Compact,
el: json.Marshal,
}
}
type elementsInspector struct {
outputStream io.Writer
elements []any
rawElements [][]byte
raw func(dst *bytes.Buffer, src []byte) error
el func(v any) ([]byte, error)
}
func (e *elementsInspector) Inspect(typedElement any, rawElement []byte) error {
if rawElement != nil {
e.rawElements = append(e.rawElements, rawElement)
} else {
e.elements = append(e.elements, typedElement)
}
return nil
}
func (e *elementsInspector) Flush() error {
if len(e.elements) == 0 && len(e.rawElements) == 0 {
_, err := io.WriteString(e.outputStream, "[]\n")
return err
}
var buffer io.Reader
if len(e.rawElements) > 0 {
bytesBuffer := new(bytes.Buffer)
bytesBuffer.WriteString("[")
for idx, r := range e.rawElements {
bytesBuffer.Write(r)
if idx < len(e.rawElements)-1 {
bytesBuffer.WriteString(",")
}
}
bytesBuffer.WriteString("]")
output := new(bytes.Buffer)
if err := e.raw(output, bytesBuffer.Bytes()); err != nil {
return err
}
buffer = output
} else {
b, err := e.el(e.elements)
if err != nil {
return err
}
buffer = bytes.NewReader(b)
}
if _, err := io.Copy(e.outputStream, buffer); err != nil {
return err
}
_, err := io.WriteString(e.outputStream, "\n")
return err
}