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 }