Skip to content

Commit a0b0190

Browse files
committed
cmd: Add optional json format to inspect command output
Signed-off-by: Andre Detsch <andre.detsch@foundries.io>
1 parent 4576fc6 commit a0b0190

1 file changed

Lines changed: 152 additions & 7 deletions

File tree

cmd/composectl/cmd/inspect.go

Lines changed: 152 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,176 @@
11
package composectl
22

33
import (
4+
"encoding/json"
45
"fmt"
6+
"os"
7+
58
"github.com/containerd/containerd/platforms"
69
"github.com/foundriesio/composeapp/pkg/compose"
7-
"github.com/foundriesio/composeapp/pkg/compose/v1"
10+
v1 "github.com/foundriesio/composeapp/pkg/compose/v1"
811
"github.com/spf13/cobra"
912
)
1013

14+
type inspectOptions struct {
15+
Format string
16+
}
17+
1118
func init() {
1219
inspectCmd := &cobra.Command{
1320
Use: "inspect <app ref>",
1421
Short: "inspect <ref>",
1522
Long: ``,
1623
Args: cobra.ExactArgs(1),
17-
Run: inspectApp,
24+
}
25+
opts := inspectOptions{}
26+
inspectCmd.Flags().StringVar(&opts.Format, "format", "plain",
27+
"Output format; supported: plain, json")
28+
inspectCmd.Run = func(cmd *cobra.Command, args []string) {
29+
if opts.Format != "plain" && opts.Format != "json" {
30+
DieNotNil(cmd.Usage())
31+
fmt.Fprintf(os.Stderr, "unsupported `--format` value: %s\n", opts.Format)
32+
os.Exit(1)
33+
}
34+
inspectApp(cmd, args, &opts)
1835
}
1936
rootCmd.AddCommand(inspectCmd)
2037
}
2138

22-
func inspectApp(cmd *cobra.Command, args []string) {
39+
func inspectApp(cmd *cobra.Command, args []string, opts *inspectOptions) {
2340
appRef := args[0]
2441

25-
fmt.Printf("Inspecting App %s...", appRef)
42+
if opts.Format == "plain" {
43+
fmt.Printf("Inspecting App %s...", appRef)
44+
}
2645
app, err := v1.NewAppLoader().LoadAppTree(cmd.Context(), compose.NewRemoteBlobProviderFromConfig(config), platforms.All, appRef)
2746
DieNotNil(err)
28-
fmt.Println("ok")
29-
app.Tree().Print()
30-
fmt.Printf("App tree node count: %d\n", app.NodeCount())
47+
if opts.Format == "plain" {
48+
fmt.Println("ok")
49+
app.Tree().Print()
50+
} else {
51+
// json
52+
type BlobInfo struct {
53+
Digest string `json:"digest"`
54+
Size int64 `json:"size"`
55+
}
56+
57+
type ManifestInfo struct {
58+
BlobInfo
59+
Architecture string `json:"architecture,omitempty"`
60+
Config BlobInfo `json:"config"`
61+
Layers []BlobInfo `json:"layers"`
62+
}
63+
64+
type ImageContentInfo struct {
65+
Ref string `json:"ref"`
66+
Manifests []ManifestInfo `json:"manifests"`
67+
}
68+
69+
type ServiceContentInfo struct {
70+
Name string `json:"name"`
71+
Image ImageContentInfo `json:"image"`
72+
}
73+
74+
type BundleInfo struct {
75+
BlobInfo
76+
Services []ServiceContentInfo `json:"services,omitempty"`
77+
}
78+
79+
type AppInfo struct {
80+
Name string `json:"name"`
81+
Ref string `json:"ref"`
82+
Meta BlobInfo `json:"meta"`
83+
Index BlobInfo `json:"index"`
84+
Bundle BundleInfo `json:"bundle"`
85+
}
86+
87+
currApp := AppInfo{
88+
Name: app.Name(),
89+
Ref: app.Ref().Spec.String(),
90+
}
91+
currApp.Bundle = BundleInfo{
92+
Services: []ServiceContentInfo{},
93+
}
94+
95+
err = app.Tree().Walk(func(node *compose.TreeNode, depth int) error {
96+
if depth == 1 {
97+
switch node.Type {
98+
case compose.BlobTypeAppLayersMeta:
99+
currApp.Meta = BlobInfo{
100+
Digest: node.Descriptor.Digest.String(),
101+
Size: node.Descriptor.Size,
102+
}
103+
case compose.BlobTypeAppIndex:
104+
currApp.Index = BlobInfo{
105+
Digest: node.Descriptor.Digest.String(),
106+
Size: node.Descriptor.Size,
107+
}
108+
case compose.BlobTypeAppBundle:
109+
currApp.Bundle = BundleInfo{
110+
BlobInfo: BlobInfo{
111+
Digest: node.Descriptor.Digest.String(),
112+
Size: node.Descriptor.Size,
113+
},
114+
Services: []ServiceContentInfo{},
115+
}
116+
}
117+
} else if depth == 2 {
118+
var manifests []ManifestInfo
119+
if node.Type == compose.BlobTypeImageManifest {
120+
manifestInfo := ManifestInfo{
121+
BlobInfo: BlobInfo{
122+
Digest: node.Descriptor.Digest.String(),
123+
Size: node.Descriptor.Size,
124+
},
125+
// If an image manifest is found at depth 2,
126+
// then there is no platform information available, as the manifest is not part of an image index.
127+
Architecture: "unknown",
128+
}
129+
manifests = append(manifests, manifestInfo)
130+
}
131+
serviceInfo := ServiceContentInfo{
132+
Name: node.Descriptor.Annotations[v1.AnnotationKeyAppServiceName],
133+
Image: ImageContentInfo{
134+
Ref: node.Descriptor.URLs[0],
135+
Manifests: manifests,
136+
},
137+
}
138+
currApp.Bundle.Services = append(currApp.Bundle.Services, serviceInfo)
139+
} else if depth == 3 && node.Type == compose.BlobTypeImageManifest {
140+
manifestInfo := ManifestInfo{
141+
BlobInfo: BlobInfo{
142+
Digest: node.Descriptor.Digest.String(),
143+
Size: node.Descriptor.Size,
144+
},
145+
Architecture: node.Descriptor.Platform.Architecture,
146+
}
147+
lastIndex := &currApp.Bundle.Services[len(currApp.Bundle.Services)-1]
148+
lastIndex.Image.Manifests = append(lastIndex.Image.Manifests, manifestInfo)
149+
} else if depth == 4 || depth == 3 {
150+
switch node.Type {
151+
case compose.BlobTypeImageConfig:
152+
lastIndex := &currApp.Bundle.Services[len(currApp.Bundle.Services)-1]
153+
lastIndex.Image.Manifests[len(lastIndex.Image.Manifests)-1].Config = BlobInfo{
154+
Digest: node.Descriptor.Digest.String(),
155+
Size: node.Descriptor.Size,
156+
}
157+
case compose.BlobTypeImageLayer:
158+
lastIndex := &currApp.Bundle.Services[len(currApp.Bundle.Services)-1]
159+
lastIndex.Image.Manifests[len(lastIndex.Image.Manifests)-1].Layers = append(lastIndex.Image.Manifests[len(lastIndex.Image.Manifests)-1].Layers, BlobInfo{
160+
Digest: node.Descriptor.Digest.String(),
161+
Size: node.Descriptor.Size,
162+
})
163+
}
164+
}
165+
return nil
166+
})
167+
DieNotNil(err)
168+
169+
b, err := json.Marshal(currApp)
170+
DieNotNil(err)
171+
fmt.Println(string(b))
172+
}
173+
if opts.Format == "plain" {
174+
fmt.Printf("App tree node count: %d\n", app.NodeCount())
175+
}
31176
}

0 commit comments

Comments
 (0)