1 package inspect
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "strings"
9 "text/tabwriter"
10 "time"
11
12 v1 "github.com/google/go-containerregistry/pkg/v1"
13
14 "edge-infra.dev/pkg/f8n/warehouse"
15 wh "edge-infra.dev/pkg/f8n/warehouse"
16 "edge-infra.dev/pkg/f8n/warehouse/lift"
17 "edge-infra.dev/pkg/f8n/warehouse/lift/cmd/internal"
18 "edge-infra.dev/pkg/f8n/warehouse/oci"
19 "edge-infra.dev/pkg/f8n/warehouse/oci/layout"
20 "edge-infra.dev/pkg/f8n/warehouse/oci/name"
21 "edge-infra.dev/pkg/f8n/warehouse/pallet"
22 "edge-infra.dev/pkg/f8n/warehouse/pallet/resolve"
23 "edge-infra.dev/pkg/lib/cli/rags"
24 "edge-infra.dev/pkg/lib/cli/sink"
25 )
26
27 func New(cfg lift.Config) *sink.Command {
28 var (
29 packer = internal.NewPacker(cfg)
30 manifest bool
31 parameters bool
32 )
33
34 cmd := &sink.Command{
35 Use: "inspect [flags] [package]",
36 Short: "introspect Warehouse packages",
37 Extensions: []sink.Extension{packer},
38 Flags: []*rags.Rag{
39 {
40 Name: "manifest",
41 Short: "m",
42 Usage: "print JSON manifest for package. mutually exclusive from --parameters",
43 Value: &rags.Bool{Var: &manifest},
44 },
45 {
46 Name: "parameters",
47 Short: "p",
48 Usage: "print required rendering parameters for package. mutually exclusive from --manifest",
49 Value: &rags.Bool{Var: ¶meters},
50 },
51 },
52 Exec: func(_ context.Context, r sink.Run) error {
53 l, err := layout.New(cfg.Cache)
54 if err != nil {
55 return err
56 }
57 idx, err := l.ImageIndex()
58 if err != nil {
59 return err
60 }
61
62 if len(r.Args()) == 0 {
63 if manifest {
64 return fmt.Errorf("--manifest is invalid when listing all cached packages")
65 }
66 if parameters {
67 return fmt.Errorf("--parameters is invalid when listing all cached packages")
68 }
69
70 return listAll(tabwriter.NewWriter(r.Out(), 5, 2, 2, ' ', 0), idx)
71 }
72
73 artifact, err := internal.ResolveArtifact(packer, r.Args()[0])
74 if err != nil {
75 return err
76 }
77
78 if manifest {
79 out, err := printRaw(artifact, r.Args()[0])
80 if err != nil {
81 return err
82 }
83 fmt.Println(out)
84 return nil
85 }
86 for _, p := range packer.Artifacts() {
87 for _, t := range packer.Tags {
88 ref, err := name.Tag(fmt.Sprintf("%s:%s", p.Name(), t))
89 if err != nil {
90 return fmt.Errorf("failed to parse tag for %s: %w", p.Name(), err)
91 }
92 if err := l.Append(ref, p); err != nil {
93 return fmt.Errorf("failed to write pallet to cache: %v", err)
94 }
95 }
96 }
97
98 p, err := pallet.New(artifact)
99 if err != nil {
100 return fmt.Errorf("failed to create pallet from artifact: %w", err)
101 }
102 if parameters {
103 out := outputParams(p)
104 fmt.Fprintln(r.Out(), out)
105 return nil
106 }
107
108
109 w := tabwriter.NewWriter(r.Err(), 5, 2, 2, ' ', 0)
110 return writePlainOutput(w, p)
111 },
112 }
113 return cmd
114 }
115
116 func printRaw(artifact oci.Artifact, ref string) (string, error) {
117 raw, err := artifact.RawManifest()
118 if err != nil {
119 return "", fmt.Errorf("failed to read manifest for %s: %w", ref, err)
120 }
121 out := &bytes.Buffer{}
122 if err := json.Indent(out, raw, "", " "); err != nil {
123 return "", err
124 }
125 return out.String(), nil
126 }
127
128 func parseTime(created string) (time.Time, error) {
129 createdTime, err := time.Parse(time.RFC3339, created)
130 if err != nil {
131 return time.Time{}, fmt.Errorf("failed to parse time: %w", err)
132 }
133 return createdTime, nil
134 }
135
136 func listAll(w *tabwriter.Writer, idx v1.ImageIndex) error {
137 manifest, err := idx.IndexManifest()
138 if err != nil {
139 return fmt.Errorf("failed to read warehouse index: %w", err)
140 }
141
142 fmt.Fprintln(w, "TAG\tDIGEST\tTEAM\tREVISION\tCREATED\t")
143 for _, m := range manifest.Manifests {
144
145 a, err := oci.ArtifactFromIdx(idx, m)
146 if err != nil {
147
148 return fmt.Errorf("failed to load artifact: %w", err)
149 }
150 p, err := pallet.New(a)
151 if err != nil {
152 return fmt.Errorf("failed to create pallet from artifact: %w", err)
153 }
154
155
156 createdTime, err := parseTime(p.Metadata().Created)
157 if err != nil {
158 return err
159 }
160 creationRaw := time.Since(createdTime)
161
162 var creation string
163 switch {
164 case creationRaw.Seconds() < 60:
165 creation = fmt.Sprint(int(creationRaw.Seconds())) + " seconds ago"
166 case creationRaw.Minutes() < 60:
167 creation = fmt.Sprint(int(creationRaw.Minutes())) + " minutes ago"
168 case creationRaw.Hours() > 1 && creationRaw.Hours() <= 24:
169 creation = fmt.Sprint(int(creationRaw.Hours())) + " hours ago"
170 case creationRaw.Hours() > 24 && creationRaw.Hours() <= 168:
171 creation = fmt.Sprint(int(creationRaw.Hours()/24)) + " days ago"
172 case creationRaw.Hours() > 168 && creationRaw.Hours() <= 672:
173 creation = fmt.Sprint(int(creationRaw.Hours()/168)) + " weeks ago"
174 case creationRaw.Hours() > 672 && creationRaw.Hours() < 8064:
175 creation = fmt.Sprint(int(creationRaw.Hours()/672)) + " months ago"
176 default:
177 creation = (createdTime.Format(time.RFC822Z))[0:9]
178 }
179
180 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t\n",
181 m.Annotations[wh.AnnotationRefName],
182 m.Digest.Hex[0:7],
183 p.Metadata().Team,
184 p.Metadata().Revision[0:7],
185 creation,
186 )
187 }
188 if err := w.Flush(); err != nil {
189 return fmt.Errorf("failed to render output: %w", err)
190 }
191
192 return nil
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214 func writePlainOutput(w *tabwriter.Writer, p pallet.Pallet) error {
215 digest, err := p.Digest()
216 if err != nil {
217 return fmt.Errorf("failed to fetch digest of %s: %w", p, err)
218 }
219 createdTime, err := parseTime(p.Metadata().Created)
220 if err != nil {
221 return err
222 }
223 created := (createdTime.Format(time.RFC1123))[5:17]
224 out := outputParams(p)
225
226 fmt.Printf("NAME:\t\t%s\n", p.Name())
227 fmt.Printf("PARAMETERS:\t%s\n", out)
228 fmt.Printf("DIGEST:\t\t%s\n", digest.Hex)
229 fmt.Printf("TEAM:\t\t%s\n", p.Metadata().Team)
230 fmt.Printf("REVISION:\t%s\n", p.Metadata().Revision)
231 fmt.Printf("CREATED:\t%s\n", created)
232
233 pType, err := p.MediaType()
234 if err != nil {
235 return fmt.Errorf("failed to fetch MediaType of %s: %w", p.Name(), err)
236 }
237
238
239 isComposite, err := oci.IsComposite(p)
240 if err != nil {
241 return err
242 }
243
244 switch {
245 case isComposite:
246
247 fmt.Printf("PALLETS:\n")
248 cp, err := p.RawManifest()
249 if err != nil {
250 return err
251 }
252 manifest := &v1.IndexManifest{}
253 err = json.Unmarshal(cp, manifest)
254 if err != nil {
255 return err
256 }
257 for _, manifest := range manifest.Manifests {
258 fmt.Printf("- %s@%v\n", manifest.Annotations[wh.AnnotationRefName], manifest.Digest)
259 }
260
261 case pType.IsImage():
262 providers, err := json.Marshal(p.Providers()[3:])
263 if err != nil {
264 return fmt.Errorf("failed to marshal providers: %w", err)
265 }
266 fmt.Printf("PROVIDERS:\t%s\n", strings.Trim(strings.Replace(string(providers), "\"", "", -1), "[]"))
267
268 case pType.IsIndex():
269 fmt.Printf("PROVIDERS:\n")
270
271 cp, err := p.RawManifest()
272 if err != nil {
273 return err
274 }
275 manifest := &v1.IndexManifest{}
276 err = json.Unmarshal(cp, manifest)
277 if err != nil {
278 return err
279 }
280 for _, manifest := range manifest.Manifests {
281 if manifest.Annotations[warehouse.AnnotationRefName] != p.Name() {
282 continue
283 }
284 providers := manifest.Annotations[wh.AnnotationClusterProviders]
285 variantDigest := manifest.Digest
286 fmt.Fprintf(w, "- %s@%v\n", providers, variantDigest)
287 }
288
289
290 pDeps, err := resolve.Resolve(p)
291 if err != nil {
292 return err
293 }
294 fmt.Printf("DEPENDENCIES:\n")
295 for depName, depDigest := range pDeps {
296 if depName == p.Name() {
297 continue
298 }
299 fmt.Fprintf(w, "- %s@%v\n", depName, depDigest)
300 }
301 }
302
303 if err := w.Flush(); err != nil {
304 return fmt.Errorf("failed to render output: %w", err)
305 }
306
307 return nil
308 }
309
310 func outputParams(p pallet.Pallet) string {
311 var out string
312 params := p.Parameters()
313 switch {
314 case params != nil:
315 out = fmt.Sprint(params)
316 default:
317 out = "No required rendering parameters"
318 }
319 return out
320 }
321
View as plain text