2016-09-08 13:11:39 -04:00
|
|
|
package inspect
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
2017-02-01 15:03:58 -05:00
|
|
|
"strings"
|
2016-09-08 13:11:39 -04:00
|
|
|
"text/template"
|
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
2017-08-08 11:26:24 -04:00
|
|
|
"github.com/docker/cli/templates"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2017-08-07 05:52:40 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// Inspector defines an interface to implement to process elements
|
|
|
|
type Inspector interface {
|
2022-03-08 08:54:21 -05:00
|
|
|
// Inspect writes the raw element in JSON format.
|
2016-09-08 13:11:39 -04:00
|
|
|
Inspect(typedElement interface{}, rawElement []byte) error
|
2022-03-08 08:54:21 -05:00
|
|
|
// Flush writes the result of inspecting all elements into the output stream.
|
2016-09-08 13:11:39 -04:00
|
|
|
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) {
|
2022-03-08 08:54:21 -05:00
|
|
|
if tmplStr == "" {
|
2016-09-08 13:11:39 -04:00
|
|
|
return NewIndentedInspector(out), nil
|
|
|
|
}
|
|
|
|
|
2022-03-08 08:54:21 -05:00
|
|
|
if tmplStr == "json" {
|
|
|
|
return NewJSONInspector(out), nil
|
|
|
|
}
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
tmpl, err := templates.Parse(tmplStr)
|
|
|
|
if err != nil {
|
linting: fix incorrectly formatted errors (revive)
cli/compose/interpolation/interpolation.go:102:4: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
"invalid interpolation format for %s: %#v. You may need to escape any $ with another $.",
^
cli/command/stack/loader/loader.go:30:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return nil, errors.Errorf("Compose file contains unsupported options:\n\n%s\n",
^
cli/command/formatter/formatter.go:76:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return tmpl, errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/formatter/formatter.go:97:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/image/build.go:257:25: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("error checking context: '%s'.", err)
^
cli/command/volume/create.go:35:27: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
^
cli/command/container/create.go:160:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-27 15:13:03 -04:00
|
|
|
return nil, errors.Errorf("template parsing error: %s", err)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
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) (interface{}, []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()}
|
|
|
|
}
|
|
|
|
|
2017-02-01 15:03:58 -05:00
|
|
|
var inspectErrs []string
|
2016-09-08 13:11:39 -04:00
|
|
|
for _, ref := range references {
|
|
|
|
element, raw, err := getRef(ref)
|
|
|
|
if err != nil {
|
2017-02-01 15:03:58 -05:00
|
|
|
inspectErrs = append(inspectErrs, err.Error())
|
|
|
|
continue
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := inspector.Inspect(element, raw); err != nil {
|
2017-02-01 15:03:58 -05:00
|
|
|
inspectErrs = append(inspectErrs, err.Error())
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := inspector.Flush(); err != nil {
|
|
|
|
logrus.Errorf("%s\n", err)
|
|
|
|
}
|
|
|
|
|
2017-02-01 15:03:58 -05:00
|
|
|
if len(inspectErrs) != 0 {
|
|
|
|
return cli.StatusError{
|
|
|
|
StatusCode: 1,
|
|
|
|
Status: strings.Join(inspectErrs, "\n"),
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
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 interface{}, rawElement []byte) error {
|
|
|
|
buffer := new(bytes.Buffer)
|
|
|
|
if err := i.tmpl.Execute(buffer, typedElement); err != nil {
|
|
|
|
if rawElement == nil {
|
linting: fix incorrectly formatted errors (revive)
cli/compose/interpolation/interpolation.go:102:4: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
"invalid interpolation format for %s: %#v. You may need to escape any $ with another $.",
^
cli/command/stack/loader/loader.go:30:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return nil, errors.Errorf("Compose file contains unsupported options:\n\n%s\n",
^
cli/command/formatter/formatter.go:76:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return tmpl, errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/formatter/formatter.go:97:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/image/build.go:257:25: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("error checking context: '%s'.", err)
^
cli/command/volume/create.go:35:27: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
^
cli/command/container/create.go:160:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-27 15:13:03 -04:00
|
|
|
return errors.Errorf("template parsing error: %v", err)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
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 interface{}
|
|
|
|
buffer := new(bytes.Buffer)
|
|
|
|
rdr := bytes.NewReader(rawElement)
|
|
|
|
dec := json.NewDecoder(rdr)
|
2017-06-18 11:48:10 -04:00
|
|
|
dec.UseNumber()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
if rawErr := dec.Decode(&raw); rawErr != nil {
|
2017-03-09 13:23:45 -05:00
|
|
|
return errors.Errorf("unable to read inspect data: %v", rawErr)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
tmplMissingKey := i.tmpl.Option("missingkey=error")
|
|
|
|
if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
|
linting: fix incorrectly formatted errors (revive)
cli/compose/interpolation/interpolation.go:102:4: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
"invalid interpolation format for %s: %#v. You may need to escape any $ with another $.",
^
cli/command/stack/loader/loader.go:30:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return nil, errors.Errorf("Compose file contains unsupported options:\n\n%s\n",
^
cli/command/formatter/formatter.go:76:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return tmpl, errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/formatter/formatter.go:97:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/image/build.go:257:25: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("error checking context: '%s'.", err)
^
cli/command/volume/create.go:35:27: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
^
cli/command/container/create.go:160:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-27 15:13:03 -04:00
|
|
|
return errors.Errorf("template parsing error: %v", rawErr)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
i.buffer.Write(buffer.Bytes())
|
|
|
|
i.buffer.WriteByte('\n')
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-25 04:47:45 -04:00
|
|
|
// Flush writes the result of inspecting all elements into the output stream.
|
2016-09-08 13:11:39 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-03-08 08:54:21 -05:00
|
|
|
// 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 interface{}) ([]byte, error) {
|
|
|
|
return json.MarshalIndent(v, "", " ")
|
|
|
|
},
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2022-03-08 08:54:21 -05:00
|
|
|
// NewJSONInspector generates a new inspector with a compact representation
|
|
|
|
// of elements.
|
|
|
|
func NewJSONInspector(outputStream io.Writer) Inspector {
|
|
|
|
return &elementsInspector{
|
2016-09-08 13:11:39 -04:00
|
|
|
outputStream: outputStream,
|
2022-03-08 08:54:21 -05:00
|
|
|
raw: json.Compact,
|
|
|
|
el: json.Marshal,
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-08 08:54:21 -05:00
|
|
|
type elementsInspector struct {
|
|
|
|
outputStream io.Writer
|
|
|
|
elements []interface{}
|
|
|
|
rawElements [][]byte
|
|
|
|
raw func(dst *bytes.Buffer, src []byte) error
|
|
|
|
el func(v interface{}) ([]byte, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *elementsInspector) Inspect(typedElement interface{}, rawElement []byte) error {
|
2016-09-08 13:11:39 -04:00
|
|
|
if rawElement != nil {
|
2022-03-08 08:54:21 -05:00
|
|
|
e.rawElements = append(e.rawElements, rawElement)
|
2016-09-08 13:11:39 -04:00
|
|
|
} else {
|
2022-03-08 08:54:21 -05:00
|
|
|
e.elements = append(e.elements, typedElement)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-08 08:54:21 -05:00
|
|
|
func (e *elementsInspector) Flush() error {
|
|
|
|
if len(e.elements) == 0 && len(e.rawElements) == 0 {
|
|
|
|
_, err := io.WriteString(e.outputStream, "[]\n")
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var buffer io.Reader
|
2022-03-08 08:54:21 -05:00
|
|
|
if len(e.rawElements) > 0 {
|
2016-09-08 13:11:39 -04:00
|
|
|
bytesBuffer := new(bytes.Buffer)
|
|
|
|
bytesBuffer.WriteString("[")
|
2022-03-08 08:54:21 -05:00
|
|
|
for idx, r := range e.rawElements {
|
2016-09-08 13:11:39 -04:00
|
|
|
bytesBuffer.Write(r)
|
2022-03-08 08:54:21 -05:00
|
|
|
if idx < len(e.rawElements)-1 {
|
2016-09-08 13:11:39 -04:00
|
|
|
bytesBuffer.WriteString(",")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bytesBuffer.WriteString("]")
|
2022-03-08 08:54:21 -05:00
|
|
|
output := new(bytes.Buffer)
|
|
|
|
if err := e.raw(output, bytesBuffer.Bytes()); err != nil {
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
2022-03-08 08:54:21 -05:00
|
|
|
buffer = output
|
2016-09-08 13:11:39 -04:00
|
|
|
} else {
|
2022-03-08 08:54:21 -05:00
|
|
|
b, err := e.el(e.elements)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
buffer = bytes.NewReader(b)
|
|
|
|
}
|
|
|
|
|
2022-03-08 08:54:21 -05:00
|
|
|
if _, err := io.Copy(e.outputStream, buffer); err != nil {
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
2022-03-08 08:54:21 -05:00
|
|
|
_, err := io.WriteString(e.outputStream, "\n")
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|