| package inspect |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io" |
| "text/template" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/docker/docker/cli" |
| "github.com/docker/docker/utils/templates" |
| ) |
| |
| // Inspector defines an interface to implement to process elements |
| type Inspector interface { |
| Inspect(typedElement interface{}, rawElement []byte) error |
| 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 |
| } |
| |
| tmpl, err := templates.Parse(tmplStr) |
| if err != nil { |
| return nil, fmt.Errorf("Template parsing error: %s", 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) (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()} |
| } |
| |
| var inspectErr error |
| for _, ref := range references { |
| element, raw, err := getRef(ref) |
| if err != nil { |
| inspectErr = err |
| break |
| } |
| |
| if err := inspector.Inspect(element, raw); err != nil { |
| inspectErr = err |
| break |
| } |
| } |
| |
| if err := inspector.Flush(); err != nil { |
| logrus.Errorf("%s\n", err) |
| } |
| |
| if inspectErr != nil { |
| return cli.StatusError{StatusCode: 1, Status: inspectErr.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 interface{}, 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: %v", 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 interface{} |
| buffer := new(bytes.Buffer) |
| rdr := bytes.NewReader(rawElement) |
| dec := json.NewDecoder(rdr) |
| |
| if rawErr := dec.Decode(&raw); rawErr != nil { |
| return fmt.Errorf("unable to read inspect data: %v", rawErr) |
| } |
| |
| tmplMissingKey := i.tmpl.Option("missingkey=error") |
| if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil { |
| return fmt.Errorf("Template parsing error: %v", rawErr) |
| } |
| |
| 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 |
| } |
| |
| // IndentedInspector uses a buffer to stop the indented representation of an element. |
| type IndentedInspector struct { |
| outputStream io.Writer |
| elements []interface{} |
| rawElements [][]byte |
| } |
| |
| // NewIndentedInspector generates a new IndentedInspector. |
| func NewIndentedInspector(outputStream io.Writer) Inspector { |
| return &IndentedInspector{ |
| outputStream: outputStream, |
| } |
| } |
| |
| // Inspect writes the raw element with an indented json format. |
| func (i *IndentedInspector) Inspect(typedElement interface{}, rawElement []byte) error { |
| if rawElement != nil { |
| i.rawElements = append(i.rawElements, rawElement) |
| } else { |
| i.elements = append(i.elements, typedElement) |
| } |
| return nil |
| } |
| |
| // Flush writes the result of inspecting all elements into the output stream. |
| func (i *IndentedInspector) Flush() error { |
| if len(i.elements) == 0 && len(i.rawElements) == 0 { |
| _, err := io.WriteString(i.outputStream, "[]\n") |
| return err |
| } |
| |
| var buffer io.Reader |
| if len(i.rawElements) > 0 { |
| bytesBuffer := new(bytes.Buffer) |
| bytesBuffer.WriteString("[") |
| for idx, r := range i.rawElements { |
| bytesBuffer.Write(r) |
| if idx < len(i.rawElements)-1 { |
| bytesBuffer.WriteString(",") |
| } |
| } |
| bytesBuffer.WriteString("]") |
| indented := new(bytes.Buffer) |
| if err := json.Indent(indented, bytesBuffer.Bytes(), "", " "); err != nil { |
| return err |
| } |
| buffer = indented |
| } else { |
| b, err := json.MarshalIndent(i.elements, "", " ") |
| if err != nil { |
| return err |
| } |
| buffer = bytes.NewReader(b) |
| } |
| |
| if _, err := io.Copy(i.outputStream, buffer); err != nil { |
| return err |
| } |
| _, err := io.WriteString(i.outputStream, "\n") |
| return err |
| } |