github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/command/inspect/inspector.go (about)

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