
With this change, we integrate all the plugin changes into the introspection service. All plugins can be listed with the following command: ```console $ ctr plugins TYPE ID PLATFORM STATUS io.containerd.content.v1 content - ok io.containerd.metadata.v1 bolt - ok io.containerd.differ.v1 walking linux/amd64 ok io.containerd.grpc.v1 containers - ok io.containerd.grpc.v1 content - ok io.containerd.grpc.v1 diff - ok io.containerd.grpc.v1 events - ok io.containerd.grpc.v1 healthcheck - ok io.containerd.grpc.v1 images - ok io.containerd.grpc.v1 namespaces - ok io.containerd.snapshotter.v1 btrfs linux/amd64 error io.containerd.snapshotter.v1 overlayfs linux/amd64 ok io.containerd.grpc.v1 snapshots - ok io.containerd.monitor.v1 cgroups linux/amd64 ok io.containerd.runtime.v1 linux linux/amd64 ok io.containerd.grpc.v1 tasks - ok io.containerd.grpc.v1 version - ok ``` There are few things to note about this output. The first is that it is printed in the order in which plugins are initialized. This useful for debugging plugin initialization problems. Also note that even though the introspection GPRC api is a itself a plugin, it is not listed. This is because the plugin takes a snapshot of the initialization state at the end of the plugin init process. This allows us to see errors from each plugin, as they happen. If it is required to introspect the existence of the introspection service, we can make modifications to include it in the future. The last thing to note is that the btrfs plugin is in an error state. This is a common state for containerd because even though we load the plugin, most installations aren't on top of btrfs and the plugin cannot be used. We can actually view this error using the detailed view with a filter: ```console $ ctr plugins --detailed id==btrfs Type: io.containerd.snapshotter.v1 ID: btrfs Platforms: linux/amd64 Exports: root /var/lib/containerd/io.containerd.snapshotter.v1.btrfs Error: Code: Unknown Message: path /var/lib/containerd/io.containerd.snapshotter.v1.btrfs must be a btrfs filesystem to be used with the btrfs snapshotter ``` Along with several other values, this is a valuable tool for evaluating the state of components in containerd. Signed-off-by: Stephen J Day <stephen.day@docker.com>
140 lines
3.1 KiB
Go
140 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
introspection "github.com/containerd/containerd/api/services/introspection/v1"
|
|
"github.com/containerd/containerd/api/types"
|
|
"github.com/containerd/containerd/platforms"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/urfave/cli"
|
|
"google.golang.org/grpc/codes"
|
|
)
|
|
|
|
var pluginsCommand = cli.Command{
|
|
Name: "plugins",
|
|
Usage: "Provides information about containerd plugins",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "quiet, q",
|
|
Usage: "print only the plugin ids",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "detailed, d",
|
|
Usage: "print detailed information about each plugin",
|
|
},
|
|
},
|
|
Action: func(context *cli.Context) error {
|
|
var (
|
|
quiet = context.Bool("quiet")
|
|
detailed = context.Bool("detailed")
|
|
ctx, cancel = appContext(context)
|
|
)
|
|
defer cancel()
|
|
|
|
client, err := newClient(context)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ps := client.IntrospectionService()
|
|
response, err := ps.Plugins(ctx, &introspection.PluginsRequest{
|
|
Filters: context.Args(),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if quiet {
|
|
for _, plugin := range response.Plugins {
|
|
fmt.Println(plugin.ID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
|
|
if detailed {
|
|
first := true
|
|
for _, plugin := range response.Plugins {
|
|
if !first {
|
|
fmt.Fprintln(w, "\t\t\t")
|
|
}
|
|
first = false
|
|
fmt.Fprintln(w, "Type:\t", plugin.Type)
|
|
fmt.Fprintln(w, "ID:\t", plugin.ID)
|
|
if len(plugin.Requires) > 0 {
|
|
fmt.Fprintln(w, "Requires:\t")
|
|
for _, r := range plugin.Requires {
|
|
fmt.Fprintln(w, "\t", r)
|
|
}
|
|
}
|
|
if len(plugin.Platforms) > 0 {
|
|
fmt.Fprintln(w, "Platforms:\t", prettyPlatforms(plugin.Platforms))
|
|
}
|
|
|
|
if len(plugin.Exports) > 0 {
|
|
fmt.Fprintln(w, "Exports:\t")
|
|
for k, v := range plugin.Exports {
|
|
fmt.Fprintln(w, "\t", k, "\t", v)
|
|
}
|
|
}
|
|
|
|
if len(plugin.Capabilities) > 0 {
|
|
fmt.Fprintln(w, "Capabilities:\t", strings.Join(plugin.Capabilities, ","))
|
|
}
|
|
|
|
if plugin.InitErr != nil {
|
|
fmt.Fprintln(w, "Error:\t")
|
|
fmt.Fprintln(w, "\t Code:\t", codes.Code(plugin.InitErr.Code))
|
|
fmt.Fprintln(w, "\t Message:\t", plugin.InitErr.Message)
|
|
}
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
fmt.Fprintln(w, "TYPE\tID\tPLATFORM\tSTATUS\t")
|
|
for _, plugin := range response.Plugins {
|
|
status := "ok"
|
|
|
|
if plugin.InitErr != nil {
|
|
status = "error"
|
|
}
|
|
|
|
var platformColumn = "-"
|
|
if len(plugin.Platforms) > 0 {
|
|
platformColumn = prettyPlatforms(plugin.Platforms)
|
|
}
|
|
|
|
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n",
|
|
plugin.Type,
|
|
plugin.ID,
|
|
platformColumn,
|
|
status,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return w.Flush()
|
|
},
|
|
}
|
|
|
|
func prettyPlatforms(pspb []types.Platform) string {
|
|
psm := map[string]struct{}{}
|
|
for _, p := range pspb {
|
|
psm[platforms.Format(ocispec.Platform{
|
|
OS: p.OS,
|
|
Architecture: p.Architecture,
|
|
Variant: p.Variant,
|
|
})] = struct{}{}
|
|
}
|
|
var ps []string
|
|
for p := range psm {
|
|
ps = append(ps, p)
|
|
}
|
|
sort.Stable(sort.StringSlice(ps))
|
|
|
|
return strings.Join(ps, ",")
|
|
}
|