github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/inspect/inspector.go (about)

     1  // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
     2  //go:build go1.19
     3  
     4  package inspect
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"io"
    10  	"strings"
    11  	"text/template"
    12  
    13  	"github.com/docker/cli/cli"
    14  	"github.com/docker/cli/templates"
    15  	"github.com/pkg/errors"
    16  	"github.com/sirupsen/logrus"
    17  )
    18  
    19  // Inspector defines an interface to implement to process elements
    20  type Inspector interface {
    21  	// Inspect writes the raw element in JSON format.
    22  	Inspect(typedElement any, rawElement []byte) error
    23  	// Flush writes the result of inspecting all elements into the output stream.
    24  	Flush() error
    25  }
    26  
    27  // TemplateInspector uses a text template to inspect elements.
    28  type TemplateInspector struct {
    29  	outputStream io.Writer
    30  	buffer       *bytes.Buffer
    31  	tmpl         *template.Template
    32  }
    33  
    34  // NewTemplateInspector creates a new inspector with a template.
    35  func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
    36  	return &TemplateInspector{
    37  		outputStream: outputStream,
    38  		buffer:       new(bytes.Buffer),
    39  		tmpl:         tmpl,
    40  	}
    41  }
    42  
    43  // NewTemplateInspectorFromString creates a new TemplateInspector from a string
    44  // which is compiled into a template.
    45  func NewTemplateInspectorFromString(out io.Writer, tmplStr string) (Inspector, error) {
    46  	if tmplStr == "" {
    47  		return NewIndentedInspector(out), nil
    48  	}
    49  
    50  	if tmplStr == "json" {
    51  		return NewJSONInspector(out), nil
    52  	}
    53  
    54  	tmpl, err := templates.Parse(tmplStr)
    55  	if err != nil {
    56  		return nil, errors.Errorf("template parsing error: %s", err)
    57  	}
    58  	return NewTemplateInspector(out, tmpl), nil
    59  }
    60  
    61  // GetRefFunc is a function which used by Inspect to fetch an object from a
    62  // reference
    63  type GetRefFunc func(ref string) (any, []byte, error)
    64  
    65  // Inspect fetches objects by reference using GetRefFunc and writes the json
    66  // representation to the output writer.
    67  func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFunc) error {
    68  	inspector, err := NewTemplateInspectorFromString(out, tmplStr)
    69  	if err != nil {
    70  		return cli.StatusError{StatusCode: 64, Status: err.Error()}
    71  	}
    72  
    73  	var inspectErrs []string
    74  	for _, ref := range references {
    75  		element, raw, err := getRef(ref)
    76  		if err != nil {
    77  			inspectErrs = append(inspectErrs, err.Error())
    78  			continue
    79  		}
    80  
    81  		if err := inspector.Inspect(element, raw); err != nil {
    82  			inspectErrs = append(inspectErrs, err.Error())
    83  		}
    84  	}
    85  
    86  	if err := inspector.Flush(); err != nil {
    87  		logrus.Errorf("%s\n", err)
    88  	}
    89  
    90  	if len(inspectErrs) != 0 {
    91  		return cli.StatusError{
    92  			StatusCode: 1,
    93  			Status:     strings.Join(inspectErrs, "\n"),
    94  		}
    95  	}
    96  	return nil
    97  }
    98  
    99  // Inspect executes the inspect template.
   100  // It decodes the raw element into a map if the initial execution fails.
   101  // This allows docker cli to parse inspect structs injected with Swarm fields.
   102  func (i *TemplateInspector) Inspect(typedElement any, rawElement []byte) error {
   103  	buffer := new(bytes.Buffer)
   104  	if err := i.tmpl.Execute(buffer, typedElement); err != nil {
   105  		if rawElement == nil {
   106  			return errors.Errorf("template parsing error: %v", err)
   107  		}
   108  		return i.tryRawInspectFallback(rawElement)
   109  	}
   110  	i.buffer.Write(buffer.Bytes())
   111  	i.buffer.WriteByte('\n')
   112  	return nil
   113  }
   114  
   115  // tryRawInspectFallback executes the inspect template with a raw interface.
   116  // This allows docker cli to parse inspect structs injected with Swarm fields.
   117  func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
   118  	var raw any
   119  	buffer := new(bytes.Buffer)
   120  	rdr := bytes.NewReader(rawElement)
   121  	dec := json.NewDecoder(rdr)
   122  	dec.UseNumber()
   123  
   124  	if rawErr := dec.Decode(&raw); rawErr != nil {
   125  		return errors.Errorf("unable to read inspect data: %v", rawErr)
   126  	}
   127  
   128  	tmplMissingKey := i.tmpl.Option("missingkey=error")
   129  	if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
   130  		return errors.Errorf("template parsing error: %v", rawErr)
   131  	}
   132  
   133  	i.buffer.Write(buffer.Bytes())
   134  	i.buffer.WriteByte('\n')
   135  	return nil
   136  }
   137  
   138  // Flush writes the result of inspecting all elements into the output stream.
   139  func (i *TemplateInspector) Flush() error {
   140  	if i.buffer.Len() == 0 {
   141  		_, err := io.WriteString(i.outputStream, "\n")
   142  		return err
   143  	}
   144  	_, err := io.Copy(i.outputStream, i.buffer)
   145  	return err
   146  }
   147  
   148  // NewIndentedInspector generates a new inspector with an indented representation
   149  // of elements.
   150  func NewIndentedInspector(outputStream io.Writer) Inspector {
   151  	return &elementsInspector{
   152  		outputStream: outputStream,
   153  		raw: func(dst *bytes.Buffer, src []byte) error {
   154  			return json.Indent(dst, src, "", "    ")
   155  		},
   156  		el: func(v any) ([]byte, error) {
   157  			return json.MarshalIndent(v, "", "    ")
   158  		},
   159  	}
   160  }
   161  
   162  // NewJSONInspector generates a new inspector with a compact representation
   163  // of elements.
   164  func NewJSONInspector(outputStream io.Writer) Inspector {
   165  	return &elementsInspector{
   166  		outputStream: outputStream,
   167  		raw:          json.Compact,
   168  		el:           json.Marshal,
   169  	}
   170  }
   171  
   172  type elementsInspector struct {
   173  	outputStream io.Writer
   174  	elements     []any
   175  	rawElements  [][]byte
   176  	raw          func(dst *bytes.Buffer, src []byte) error
   177  	el           func(v any) ([]byte, error)
   178  }
   179  
   180  func (e *elementsInspector) Inspect(typedElement any, rawElement []byte) error {
   181  	if rawElement != nil {
   182  		e.rawElements = append(e.rawElements, rawElement)
   183  	} else {
   184  		e.elements = append(e.elements, typedElement)
   185  	}
   186  	return nil
   187  }
   188  
   189  func (e *elementsInspector) Flush() error {
   190  	if len(e.elements) == 0 && len(e.rawElements) == 0 {
   191  		_, err := io.WriteString(e.outputStream, "[]\n")
   192  		return err
   193  	}
   194  
   195  	var buffer io.Reader
   196  	if len(e.rawElements) > 0 {
   197  		bytesBuffer := new(bytes.Buffer)
   198  		bytesBuffer.WriteString("[")
   199  		for idx, r := range e.rawElements {
   200  			bytesBuffer.Write(r)
   201  			if idx < len(e.rawElements)-1 {
   202  				bytesBuffer.WriteString(",")
   203  			}
   204  		}
   205  		bytesBuffer.WriteString("]")
   206  		output := new(bytes.Buffer)
   207  		if err := e.raw(output, bytesBuffer.Bytes()); err != nil {
   208  			return err
   209  		}
   210  		buffer = output
   211  	} else {
   212  		b, err := e.el(e.elements)
   213  		if err != nil {
   214  			return err
   215  		}
   216  		buffer = bytes.NewReader(b)
   217  	}
   218  
   219  	if _, err := io.Copy(e.outputStream, buffer); err != nil {
   220  		return err
   221  	}
   222  	_, err := io.WriteString(e.outputStream, "\n")
   223  	return err
   224  }