github.com/xeptore/docker-cli@v20.10.14+incompatible/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 }