package plugins 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/cmd/ctr/commands" "github.com/containerd/containerd/platforms" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/urfave/cli" "google.golang.org/grpc/codes" ) // Command is a cli command that outputs plugin information var Command = cli.Command{ Name: "plugins", Aliases: []string{"plugin"}, 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") ) client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() 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\tPLATFORMS\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(v1.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, ",") }