-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathinspector.go
More file actions
239 lines (211 loc) · 5.95 KB
/
inspector.go
File metadata and controls
239 lines (211 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package inspect
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"text/template"
"github.com/docker/cli/cli"
"github.com/docker/cli/templates"
"github.com/sirupsen/logrus"
)
// Inspector defines an interface to implement to process elements
type Inspector interface {
// Inspect writes the raw element in JSON format.
Inspect(typedElement any, rawElement []byte) error
// Flush writes the result of inspecting all elements into the output stream.
Flush() error
}
// TemplateInspector uses a text template to inspect elements.
type TemplateInspector struct {
out io.Writer
buffer *bytes.Buffer
tmpl *template.Template
}
// NewTemplateInspector creates a new inspector with a template.
func NewTemplateInspector(out io.Writer, tmpl *template.Template) *TemplateInspector {
if out == nil {
out = io.Discard
}
return &TemplateInspector{
out: out,
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 out == nil {
return nil, errors.New("no output stream")
}
if tmplStr == "" {
return NewIndentedInspector(out), nil
}
if tmplStr == "json" {
return NewJSONInspector(out), nil
}
tmpl, err := templates.Parse(tmplStr)
if err != nil {
return nil, fmt.Errorf("template parsing error: %w", 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) (any, []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 {
if out == nil {
return errors.New("no output stream")
}
inspector, err := NewTemplateInspectorFromString(out, tmplStr)
if err != nil {
return cli.StatusError{StatusCode: 64, Status: err.Error()}
}
var errs []error
for _, ref := range references {
element, raw, err := getRef(ref)
if err != nil {
errs = append(errs, err)
continue
}
if err := inspector.Inspect(element, raw); err != nil {
errs = append(errs, err)
}
}
if err := inspector.Flush(); err != nil {
logrus.Error(err)
}
if err := errors.Join(errs...); err != nil {
return cli.StatusError{
StatusCode: 1,
Status: err.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 any, 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: %w", 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 any
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
dec.UseNumber()
if err := dec.Decode(&raw); err != nil {
return fmt.Errorf("unable to read inspect data: %w", err)
}
tmplMissingKey := i.tmpl.Option("missingkey=error")
if err := tmplMissingKey.Execute(buffer, raw); err != nil {
return fmt.Errorf("template parsing error: %w", err)
}
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.out, "\n")
return err
}
_, err := io.Copy(i.out, i.buffer)
return err
}
// NewIndentedInspector generates a new inspector with an indented representation
// of elements.
func NewIndentedInspector(out io.Writer) Inspector {
if out == nil {
out = io.Discard
}
return &jsonInspector{
out: out,
raw: func(dst *bytes.Buffer, src []byte) error {
return json.Indent(dst, src, "", " ")
},
el: func(v any) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
},
}
}
// NewJSONInspector generates a new inspector with a compact representation
// of elements.
func NewJSONInspector(out io.Writer) Inspector {
if out == nil {
out = io.Discard
}
return &jsonInspector{
out: out,
raw: json.Compact,
el: json.Marshal,
}
}
type jsonInspector struct {
out io.Writer
elements []any
rawElements [][]byte
raw func(dst *bytes.Buffer, src []byte) error
el func(v any) ([]byte, error)
}
func (e *jsonInspector) Inspect(typedElement any, rawElement []byte) error {
if rawElement != nil {
e.rawElements = append(e.rawElements, rawElement)
} else {
e.elements = append(e.elements, typedElement)
}
return nil
}
func (e *jsonInspector) Flush() error {
if len(e.elements) == 0 && len(e.rawElements) == 0 {
_, err := io.WriteString(e.out, "[]\n")
return err
}
var buffer io.Reader
if len(e.rawElements) > 0 {
bytesBuffer := new(bytes.Buffer)
bytesBuffer.WriteString("[")
for idx, r := range e.rawElements {
bytesBuffer.Write(r)
if idx < len(e.rawElements)-1 {
bytesBuffer.WriteString(",")
}
}
bytesBuffer.WriteString("]")
output := new(bytes.Buffer)
if err := e.raw(output, bytesBuffer.Bytes()); err != nil {
return err
}
buffer = output
} else {
b, err := e.el(e.elements)
if err != nil {
return err
}
buffer = bytes.NewReader(b)
}
if _, err := io.Copy(e.out, buffer); err != nil {
return err
}
_, err := io.WriteString(e.out, "\n")
return err
}