Merge pull request #1685 from jessvalarezo/ctr-refactor
ctr: move ctr commands
This commit is contained in:
commit
638f3a3380
210
cmd/ctr/commands/containers/containers.go
Normal file
210
cmd/ctr/commands/containers/containers.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command is the cli command for managing containers
|
||||||
|
var Command = cli.Command{
|
||||||
|
Name: "containers",
|
||||||
|
Usage: "manage containers",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
listCommand,
|
||||||
|
deleteCommand,
|
||||||
|
setLabelsCommand,
|
||||||
|
infoCommand,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var listCommand = cli.Command{
|
||||||
|
Name: "list",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Usage: "list containers",
|
||||||
|
ArgsUsage: "[flags] [<filter>, ...]",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "quiet, q",
|
||||||
|
Usage: "print only the container id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(context *cli.Context) error {
|
||||||
|
var (
|
||||||
|
filters = context.Args()
|
||||||
|
quiet = context.Bool("quiet")
|
||||||
|
)
|
||||||
|
client, ctx, cancel, err := commands.NewClient(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
containers, err := client.Containers(ctx, filters...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if quiet {
|
||||||
|
for _, c := range containers {
|
||||||
|
fmt.Printf("%s\n", c.ID())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
|
||||||
|
fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
|
||||||
|
for _, c := range containers {
|
||||||
|
info, err := c.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
imageName := info.Image
|
||||||
|
if imageName == "" {
|
||||||
|
imageName = "-"
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t\n",
|
||||||
|
c.ID(),
|
||||||
|
imageName,
|
||||||
|
info.Runtime.Name,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w.Flush()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteCommand = cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Usage: "delete one or more existing containers",
|
||||||
|
ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]",
|
||||||
|
Aliases: []string{"del", "rm"},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "keep-snapshot",
|
||||||
|
Usage: "do not clean up snapshot with container",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(context *cli.Context) error {
|
||||||
|
var exitErr error
|
||||||
|
client, ctx, cancel, err := commands.NewClient(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
deleteOpts := []containerd.DeleteOpts{}
|
||||||
|
if !context.Bool("keep-snapshot") {
|
||||||
|
deleteOpts = append(deleteOpts, containerd.WithSnapshotCleanup)
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.NArg() == 0 {
|
||||||
|
return errors.New("must specify at least one container to delete")
|
||||||
|
}
|
||||||
|
for _, arg := range context.Args() {
|
||||||
|
if err := deleteContainer(ctx, client, arg, deleteOpts...); err != nil {
|
||||||
|
if exitErr == nil {
|
||||||
|
exitErr = err
|
||||||
|
}
|
||||||
|
log.G(ctx).WithError(err).Errorf("failed to delete container %q", arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitErr
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteContainer(ctx context.Context, client *containerd.Client, id string, opts ...containerd.DeleteOpts) error {
|
||||||
|
container, err := client.LoadContainer(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task, err := container.Task(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return container.Delete(ctx, opts...)
|
||||||
|
}
|
||||||
|
status, err := task.Status(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if status.Status == containerd.Stopped || status.Status == containerd.Created {
|
||||||
|
if _, err := task.Delete(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return container.Delete(ctx, opts...)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot delete a non stopped container: %v", status)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var setLabelsCommand = cli.Command{
|
||||||
|
Name: "label",
|
||||||
|
Usage: "set and clear labels for a container",
|
||||||
|
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
|
||||||
|
Description: "set and clear labels for a container",
|
||||||
|
Flags: []cli.Flag{},
|
||||||
|
Action: func(context *cli.Context) error {
|
||||||
|
containerID, labels := commands.ObjectWithLabelArgs(context)
|
||||||
|
if containerID == "" {
|
||||||
|
return errors.New("please specify a container")
|
||||||
|
}
|
||||||
|
client, ctx, cancel, err := commands.NewClient(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
container, err := client.LoadContainer(ctx, containerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
setlabels, err := container.SetLabels(ctx, labels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelStrings []string
|
||||||
|
for k, v := range setlabels {
|
||||||
|
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strings.Join(labelStrings, ","))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var infoCommand = cli.Command{
|
||||||
|
Name: "info",
|
||||||
|
Usage: "get info about a container",
|
||||||
|
ArgsUsage: "CONTAINER",
|
||||||
|
Action: func(context *cli.Context) error {
|
||||||
|
id := context.Args().First()
|
||||||
|
if id == "" {
|
||||||
|
return errors.New("container id must be provided")
|
||||||
|
}
|
||||||
|
client, ctx, cancel, err := commands.NewClient(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
container, err := client.LoadContainer(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := container.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
commands.PrintAsJSON(info)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package content
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -21,26 +21,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
contentCommand = cli.Command{
|
// Command is the cli command for managing content
|
||||||
|
Command = cli.Command{
|
||||||
Name: "content",
|
Name: "content",
|
||||||
Usage: "manage content",
|
Usage: "manage content",
|
||||||
Subcommands: cli.Commands{
|
Subcommands: cli.Commands{
|
||||||
listContentCommand,
|
listCommand,
|
||||||
ingestContentCommand,
|
ingestCommand,
|
||||||
activeIngestCommand,
|
activeIngestCommand,
|
||||||
getContentCommand,
|
getCommand,
|
||||||
editContentCommand,
|
editCommand,
|
||||||
deleteContentCommand,
|
deleteCommand,
|
||||||
labelContentCommand,
|
setLabelsCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
getContentCommand = cli.Command{
|
getCommand = cli.Command{
|
||||||
Name: "get",
|
Name: "get",
|
||||||
Usage: "get the data for an object",
|
Usage: "get the data for an object",
|
||||||
ArgsUsage: "[flags] [<digest>, ...]",
|
ArgsUsage: "[<digest>, ...]",
|
||||||
Description: "Display the image object.",
|
Description: "display the image object",
|
||||||
Flags: []cli.Flag{},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
dgst, err := digest.Parse(context.Args().First())
|
dgst, err := digest.Parse(context.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,11 +63,11 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ingestContentCommand = cli.Command{
|
ingestCommand = cli.Command{
|
||||||
Name: "ingest",
|
Name: "ingest",
|
||||||
Usage: "accept content into the store",
|
Usage: "accept content into the store",
|
||||||
ArgsUsage: "[flags] <key>",
|
ArgsUsage: "[flags] <key>",
|
||||||
Description: `Ingest objects into the local content store.`,
|
Description: "ingest objects into the local content store",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.Int64Flag{
|
cli.Int64Flag{
|
||||||
Name: "expected-size",
|
Name: "expected-size",
|
||||||
@ -107,9 +107,9 @@ var (
|
|||||||
|
|
||||||
activeIngestCommand = cli.Command{
|
activeIngestCommand = cli.Command{
|
||||||
Name: "active",
|
Name: "active",
|
||||||
Usage: "display active transfers.",
|
Usage: "display active transfers",
|
||||||
ArgsUsage: "[flags] [<regexp>]",
|
ArgsUsage: "[flags] [<regexp>]",
|
||||||
Description: `Display the ongoing transfers.`,
|
Description: "display the ongoing transfers",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.DurationFlag{
|
cli.DurationFlag{
|
||||||
Name: "timeout, t",
|
Name: "timeout, t",
|
||||||
@ -147,12 +147,12 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
listContentCommand = cli.Command{
|
listCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "list all blobs in the store.",
|
Usage: "list all blobs in the store",
|
||||||
ArgsUsage: "[flags] [<filter>, ...]",
|
ArgsUsage: "[flags]",
|
||||||
Description: `List blobs in the content store.`,
|
Description: "list blobs in the content store",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "quiet, q",
|
Name: "quiet, q",
|
||||||
@ -206,12 +206,11 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
labelContentCommand = cli.Command{
|
setLabelsCommand = cli.Command{
|
||||||
Name: "label",
|
Name: "label",
|
||||||
Usage: "add labels to content",
|
Usage: "add labels to content",
|
||||||
ArgsUsage: "[flags] <digest> [<label>=<value> ...]",
|
ArgsUsage: "<digest> [<label>=<value> ...]",
|
||||||
Description: `Labels blobs in the content store`,
|
Description: "labels blobs in the content store",
|
||||||
Flags: []cli.Flag{},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
object, labels := commands.ObjectWithLabelArgs(context)
|
object, labels := commands.ObjectWithLabelArgs(context)
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
client, ctx, cancel, err := commands.NewClient(context)
|
||||||
@ -261,11 +260,11 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
editContentCommand = cli.Command{
|
editCommand = cli.Command{
|
||||||
Name: "edit",
|
Name: "edit",
|
||||||
Usage: "edit a blob and return a new digest.",
|
Usage: "edit a blob and return a new digest",
|
||||||
ArgsUsage: "[flags] <digest>",
|
ArgsUsage: "[flags] <digest>",
|
||||||
Description: `Edit a blob and return a new digest.`,
|
Description: "edit a blob and return a new digest",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "validate",
|
Name: "validate",
|
||||||
@ -325,14 +324,13 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteContentCommand = cli.Command{
|
deleteCommand = cli.Command{
|
||||||
Name: "delete",
|
Name: "delete",
|
||||||
Aliases: []string{"del", "remove", "rm"},
|
Aliases: []string{"del", "remove", "rm"},
|
||||||
Usage: "permanently delete one or more blobs.",
|
Usage: "permanently delete one or more blobs",
|
||||||
ArgsUsage: "[flags] [<digest>, ...]",
|
ArgsUsage: "[<digest>, ...]",
|
||||||
Description: `Delete one or more blobs permanently. Successfully deleted
|
Description: `Delete one or more blobs permanently. Successfully deleted
|
||||||
blobs are printed to stdout.`,
|
blobs are printed to stdout.`,
|
||||||
Flags: []cli.Flag{},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
args = []string(context.Args())
|
args = []string(context.Args())
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package events
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -10,7 +10,8 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var eventsCommand = cli.Command{
|
// Command is the cli command for displaying containerd events
|
||||||
|
var Command = cli.Command{
|
||||||
Name: "events",
|
Name: "events",
|
||||||
Usage: "display containerd events",
|
Usage: "display containerd events",
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@ -12,11 +12,11 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var imagesExportCommand = cli.Command{
|
var exportCommand = cli.Command{
|
||||||
Name: "export",
|
Name: "export",
|
||||||
Usage: "export an image",
|
Usage: "export an image",
|
||||||
ArgsUsage: "[flags] <out> <image>",
|
ArgsUsage: "[flags] <out> <image>",
|
||||||
Description: `Export an image to a tar stream.`,
|
Description: "export an image to a tar stream",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "oci-ref-name",
|
Name: "oci-ref-name",
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -17,25 +17,26 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var imageCommand = cli.Command{
|
// Command is the cli command for managing images
|
||||||
|
var Command = cli.Command{
|
||||||
Name: "images",
|
Name: "images",
|
||||||
Usage: "manage images",
|
Usage: "manage images",
|
||||||
Subcommands: cli.Commands{
|
Subcommands: cli.Commands{
|
||||||
imagesListCommand,
|
listCommand,
|
||||||
imagesCheckCommand,
|
checkCommand,
|
||||||
imageRemoveCommand,
|
removeCommand,
|
||||||
imagesSetLabelsCommand,
|
setLabelsCommand,
|
||||||
imagesImportCommand,
|
importCommand,
|
||||||
imagesExportCommand,
|
exportCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var imagesListCommand = cli.Command{
|
var listCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "list images known to containerd",
|
Usage: "list images known to containerd",
|
||||||
ArgsUsage: "[flags] <ref>",
|
ArgsUsage: "[flags] <ref>",
|
||||||
Description: `List images registered with containerd.`,
|
Description: "list images registered with containerd",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "quiet, q",
|
Name: "quiet, q",
|
||||||
@ -114,11 +115,11 @@ var imagesListCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var imagesSetLabelsCommand = cli.Command{
|
var setLabelsCommand = cli.Command{
|
||||||
Name: "label",
|
Name: "label",
|
||||||
Usage: "set and clear labels for an image.",
|
Usage: "set and clear labels for an image",
|
||||||
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
|
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
|
||||||
Description: "Set and clear labels for an image.",
|
Description: "set and clear labels for an image",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "replace-all, r",
|
Name: "replace-all, r",
|
||||||
@ -173,12 +174,11 @@ var imagesSetLabelsCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var imagesCheckCommand = cli.Command{
|
var checkCommand = cli.Command{
|
||||||
Name: "check",
|
Name: "check",
|
||||||
Usage: "Check that an image has all content available locally.",
|
Usage: "check that an image has all content available locally",
|
||||||
ArgsUsage: "[flags] <ref> [<ref>, ...]",
|
ArgsUsage: "<ref> [<ref>, ...]",
|
||||||
Description: "Check that an image has all content available locally.",
|
Description: "check that an image has all content available locally",
|
||||||
Flags: []cli.Flag{},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
exitErr error
|
exitErr error
|
||||||
@ -255,13 +255,12 @@ var imagesCheckCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageRemoveCommand = cli.Command{
|
var removeCommand = cli.Command{
|
||||||
Name: "remove",
|
Name: "remove",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
Usage: "Remove one or more images by reference.",
|
Usage: "remove one or more images by reference",
|
||||||
ArgsUsage: "[flags] <ref> [<ref>, ...]",
|
ArgsUsage: "<ref> [<ref>, ...]",
|
||||||
Description: `Remove one or more images by reference.`,
|
Description: "remove one or more images by reference",
|
||||||
Flags: []cli.Flag{},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
client, ctx, cancel, err := commands.NewClient(context)
|
||||||
if err != nil {
|
if err != nil {
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -11,11 +11,11 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var imagesImportCommand = cli.Command{
|
var importCommand = cli.Command{
|
||||||
Name: "import",
|
Name: "import",
|
||||||
Usage: "import an image",
|
Usage: "import an image",
|
||||||
ArgsUsage: "[flags] <ref> <in>",
|
ArgsUsage: "[flags] <ref> <in>",
|
||||||
Description: `Import an image from a tar stream.`,
|
Description: "import an image from a tar stream",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "ref-object",
|
Name: "ref-object",
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package namespaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -14,22 +14,23 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var namespacesCommand = cli.Command{
|
// Command is the cli command for managing namespaces
|
||||||
|
var Command = cli.Command{
|
||||||
Name: "namespaces",
|
Name: "namespaces",
|
||||||
Usage: "manage namespaces",
|
Usage: "manage namespaces",
|
||||||
Subcommands: cli.Commands{
|
Subcommands: cli.Commands{
|
||||||
namespacesCreateCommand,
|
createCommand,
|
||||||
namespacesSetLabelsCommand,
|
setLabelsCommand,
|
||||||
namespacesListCommand,
|
listCommand,
|
||||||
namespacesRemoveCommand,
|
removeCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var namespacesCreateCommand = cli.Command{
|
var createCommand = cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
Usage: "create a new namespace.",
|
Usage: "create a new namespace",
|
||||||
ArgsUsage: "[flags] <name> [<key>=<value]",
|
ArgsUsage: "<name> [<key>=<value]",
|
||||||
Description: "Create a new namespace. It must be unique.",
|
Description: "create a new namespace. it must be unique",
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
namespace, labels := commands.ObjectWithLabelArgs(context)
|
namespace, labels := commands.ObjectWithLabelArgs(context)
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
@ -45,12 +46,11 @@ var namespacesCreateCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var namespacesSetLabelsCommand = cli.Command{
|
var setLabelsCommand = cli.Command{
|
||||||
Name: "label",
|
Name: "label",
|
||||||
Usage: "set and clear labels for a namespace.",
|
Usage: "set and clear labels for a namespace",
|
||||||
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
|
ArgsUsage: "<name> [<key>=<value>, ...]",
|
||||||
Description: "Set and clear labels for a namespace.",
|
Description: "set and clear labels for a namespace",
|
||||||
Flags: []cli.Flag{},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
namespace, labels := commands.ObjectWithLabelArgs(context)
|
namespace, labels := commands.ObjectWithLabelArgs(context)
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
@ -71,16 +71,16 @@ var namespacesSetLabelsCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var namespacesListCommand = cli.Command{
|
var listCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "list namespaces.",
|
Usage: "list namespaces",
|
||||||
ArgsUsage: "[flags]",
|
ArgsUsage: "[flags]",
|
||||||
Description: "List namespaces.",
|
Description: "list namespaces",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "quiet, q",
|
Name: "quiet, q",
|
||||||
Usage: "print only the namespace name.",
|
Usage: "print only the namespace name",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
@ -123,12 +123,12 @@ var namespacesListCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var namespacesRemoveCommand = cli.Command{
|
var removeCommand = cli.Command{
|
||||||
Name: "remove",
|
Name: "remove",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
Usage: "remove one or more namespaces",
|
Usage: "remove one or more namespaces",
|
||||||
ArgsUsage: "[flags] <name> [<name>, ...]",
|
ArgsUsage: "<name> [<name>, ...]",
|
||||||
Description: "Remove one or more namespaces. For now, the namespace must be empty.",
|
Description: "remove one or more namespaces. for now, the namespace must be empty",
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
var exitErr error
|
var exitErr error
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
client, ctx, cancel, err := commands.NewClient(context)
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package snapshot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
gocontext "context"
|
gocontext "context"
|
||||||
@ -15,25 +15,26 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var snapshotCommand = cli.Command{
|
// Command is the cli command for managing snapshots
|
||||||
|
var Command = cli.Command{
|
||||||
Name: "snapshot",
|
Name: "snapshot",
|
||||||
Usage: "snapshot management",
|
Usage: "manage snapshots",
|
||||||
Flags: commands.SnapshotterFlags,
|
Flags: commands.SnapshotterFlags,
|
||||||
Subcommands: cli.Commands{
|
Subcommands: cli.Commands{
|
||||||
listSnapshotCommand,
|
listCommand,
|
||||||
usageSnapshotCommand,
|
usageCommand,
|
||||||
removeSnapshotCommand,
|
removeCommand,
|
||||||
prepareSnapshotCommand,
|
prepareCommand,
|
||||||
viewSnapshotCommand,
|
viewCommand,
|
||||||
treeSnapshotCommand,
|
treeCommand,
|
||||||
mountSnapshotCommand,
|
mountCommand,
|
||||||
commitSnapshotCommand,
|
commitCommand,
|
||||||
infoSnapshotCommand,
|
infoCommand,
|
||||||
labelSnapshotCommand,
|
setLabelCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var listSnapshotCommand = cli.Command{
|
var listCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "list snapshots",
|
Usage: "list snapshots",
|
||||||
@ -62,7 +63,7 @@ var listSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var usageSnapshotCommand = cli.Command{
|
var usageCommand = cli.Command{
|
||||||
Name: "usage",
|
Name: "usage",
|
||||||
Usage: "usage snapshots",
|
Usage: "usage snapshots",
|
||||||
ArgsUsage: "[flags] [<key>, ...]",
|
ArgsUsage: "[flags] [<key>, ...]",
|
||||||
@ -118,7 +119,7 @@ var usageSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var removeSnapshotCommand = cli.Command{
|
var removeCommand = cli.Command{
|
||||||
Name: "remove",
|
Name: "remove",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
ArgsUsage: "<key> [<key>, ...]",
|
ArgsUsage: "<key> [<key>, ...]",
|
||||||
@ -141,7 +142,7 @@ var removeSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var prepareSnapshotCommand = cli.Command{
|
var prepareCommand = cli.Command{
|
||||||
Name: "prepare",
|
Name: "prepare",
|
||||||
Usage: "prepare a snapshot from a committed snapshot",
|
Usage: "prepare a snapshot from a committed snapshot",
|
||||||
ArgsUsage: "[flags] <key> [<parent>]",
|
ArgsUsage: "[flags] <key> [<parent>]",
|
||||||
@ -180,7 +181,7 @@ var prepareSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewSnapshotCommand = cli.Command{
|
var viewCommand = cli.Command{
|
||||||
Name: "view",
|
Name: "view",
|
||||||
Usage: "create a read-only snapshot from a committed snapshot",
|
Usage: "create a read-only snapshot from a committed snapshot",
|
||||||
ArgsUsage: "[flags] <key> [<parent>]",
|
ArgsUsage: "[flags] <key> [<parent>]",
|
||||||
@ -219,11 +220,11 @@ var viewSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var mountSnapshotCommand = cli.Command{
|
var mountCommand = cli.Command{
|
||||||
Name: "mounts",
|
Name: "mounts",
|
||||||
Aliases: []string{"m", "mount"},
|
Aliases: []string{"m", "mount"},
|
||||||
Usage: "mount gets mount commands for the snapshots",
|
Usage: "mount gets mount commands for the snapshots",
|
||||||
ArgsUsage: "[flags] <target> <key>",
|
ArgsUsage: "<target> <key>",
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
if context.NArg() != 2 {
|
if context.NArg() != 2 {
|
||||||
return cli.ShowSubcommandHelp(context)
|
return cli.ShowSubcommandHelp(context)
|
||||||
@ -249,10 +250,10 @@ var mountSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var commitSnapshotCommand = cli.Command{
|
var commitCommand = cli.Command{
|
||||||
Name: "commit",
|
Name: "commit",
|
||||||
Usage: "commit an active snapshot into the provided name",
|
Usage: "commit an active snapshot into the provided name",
|
||||||
ArgsUsage: "[flags] <key> <active>",
|
ArgsUsage: "<key> <active>",
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
if context.NArg() != 2 {
|
if context.NArg() != 2 {
|
||||||
return cli.ShowSubcommandHelp(context)
|
return cli.ShowSubcommandHelp(context)
|
||||||
@ -271,7 +272,7 @@ var commitSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var treeSnapshotCommand = cli.Command{
|
var treeCommand = cli.Command{
|
||||||
Name: "tree",
|
Name: "tree",
|
||||||
Usage: "display tree view of snapshot branches",
|
Usage: "display tree view of snapshot branches",
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
@ -305,7 +306,7 @@ var treeSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var infoSnapshotCommand = cli.Command{
|
var infoCommand = cli.Command{
|
||||||
Name: "info",
|
Name: "info",
|
||||||
Usage: "get info about a snapshot",
|
Usage: "get info about a snapshot",
|
||||||
ArgsUsage: "<key>",
|
ArgsUsage: "<key>",
|
||||||
@ -332,12 +333,11 @@ var infoSnapshotCommand = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var labelSnapshotCommand = cli.Command{
|
var setLabelCommand = cli.Command{
|
||||||
Name: "label",
|
Name: "label",
|
||||||
Usage: "add labels to content",
|
Usage: "add labels to content",
|
||||||
ArgsUsage: "[flags] <name> [<label>=<value> ...]",
|
ArgsUsage: "<name> [<label>=<value> ...]",
|
||||||
Description: `Labels snapshots in the snapshotter`,
|
Description: "labels snapshots in the snapshotter",
|
||||||
Flags: []cli.Flag{},
|
|
||||||
Action: func(context *cli.Context) error {
|
Action: func(context *cli.Context) error {
|
||||||
key, labels := commands.ObjectWithLabelArgs(context)
|
key, labels := commands.ObjectWithLabelArgs(context)
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
client, ctx, cancel, err := commands.NewClient(context)
|
@ -1,74 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
|
||||||
"github.com/containerd/containerd/log"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var containersDeleteCommand = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Usage: "delete one or more existing containers",
|
|
||||||
ArgsUsage: "CONTAINER [CONTAINER, ...]",
|
|
||||||
Aliases: []string{"del", "rm"},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "keep-snapshot",
|
|
||||||
Usage: "do not clean up snapshot with container",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(context *cli.Context) error {
|
|
||||||
var exitErr error
|
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cancel()
|
|
||||||
deleteOpts := []containerd.DeleteOpts{}
|
|
||||||
if !context.Bool("keep-snapshot") {
|
|
||||||
deleteOpts = append(deleteOpts, containerd.WithSnapshotCleanup)
|
|
||||||
}
|
|
||||||
|
|
||||||
if context.NArg() == 0 {
|
|
||||||
return errors.New("must specify at least one container to delete")
|
|
||||||
}
|
|
||||||
for _, arg := range context.Args() {
|
|
||||||
if err := deleteContainer(ctx, client, arg, deleteOpts...); err != nil {
|
|
||||||
if exitErr == nil {
|
|
||||||
exitErr = err
|
|
||||||
}
|
|
||||||
log.G(ctx).WithError(err).Errorf("failed to delete container %q", arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return exitErr
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteContainer(ctx context.Context, client *containerd.Client, id string, opts ...containerd.DeleteOpts) error {
|
|
||||||
container, err := client.LoadContainer(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
task, err := container.Task(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return container.Delete(ctx, opts...)
|
|
||||||
}
|
|
||||||
status, err := task.Status(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if status.Status == containerd.Stopped || status.Status == containerd.Created {
|
|
||||||
if _, err := task.Delete(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return container.Delete(ctx, opts...)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("cannot delete a non stopped container: %v", status)
|
|
||||||
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var containersCommand = cli.Command{
|
|
||||||
Name: "containers",
|
|
||||||
Usage: "manage containers (metadata)",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "quiet, q",
|
|
||||||
Usage: "print only the container id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
containersDeleteCommand,
|
|
||||||
containersSetLabelsCommand,
|
|
||||||
containerInfoCommand,
|
|
||||||
},
|
|
||||||
ArgsUsage: "[filter, ...]",
|
|
||||||
Action: func(context *cli.Context) error {
|
|
||||||
var (
|
|
||||||
filters = context.Args()
|
|
||||||
quiet = context.Bool("quiet")
|
|
||||||
)
|
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cancel()
|
|
||||||
containers, err := client.Containers(ctx, filters...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if quiet {
|
|
||||||
for _, c := range containers {
|
|
||||||
fmt.Printf("%s\n", c.ID())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
|
|
||||||
fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
|
|
||||||
for _, c := range containers {
|
|
||||||
info, err := c.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
imageName := info.Image
|
|
||||||
if imageName == "" {
|
|
||||||
imageName = "-"
|
|
||||||
}
|
|
||||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t\n",
|
|
||||||
c.ID(),
|
|
||||||
imageName,
|
|
||||||
info.Runtime.Name,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w.Flush()
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var containerInfoCommand = cli.Command{
|
|
||||||
Name: "info",
|
|
||||||
Usage: "get info about a container",
|
|
||||||
ArgsUsage: "CONTAINER",
|
|
||||||
Action: func(context *cli.Context) error {
|
|
||||||
id := context.Args().First()
|
|
||||||
if id == "" {
|
|
||||||
return errors.New("container id must be provided")
|
|
||||||
}
|
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cancel()
|
|
||||||
container, err := client.LoadContainer(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info, err := container.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
commands.PrintAsJSON(info)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var containersSetLabelsCommand = cli.Command{
|
|
||||||
Name: "label",
|
|
||||||
Usage: "set and clear labels for a container.",
|
|
||||||
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
|
|
||||||
Description: "Set and clear labels for a container.",
|
|
||||||
Flags: []cli.Flag{},
|
|
||||||
Action: func(context *cli.Context) error {
|
|
||||||
containerID, labels := commands.ObjectWithLabelArgs(context)
|
|
||||||
if containerID == "" {
|
|
||||||
return errors.New("please specify a container")
|
|
||||||
}
|
|
||||||
client, ctx, cancel, err := commands.NewClient(context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
container, err := client.LoadContainer(ctx, containerID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
setlabels, err := container.SetLabels(ctx, labels)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var labelStrings []string
|
|
||||||
for k, v := range setlabels {
|
|
||||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(strings.Join(labelStrings, ","))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -6,7 +6,13 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/cmd/ctr/commands/containers"
|
||||||
|
"github.com/containerd/containerd/cmd/ctr/commands/content"
|
||||||
|
"github.com/containerd/containerd/cmd/ctr/commands/events"
|
||||||
|
"github.com/containerd/containerd/cmd/ctr/commands/images"
|
||||||
|
namespacesCmd "github.com/containerd/containerd/cmd/ctr/commands/namespaces"
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands/plugins"
|
"github.com/containerd/containerd/cmd/ctr/commands/plugins"
|
||||||
|
"github.com/containerd/containerd/cmd/ctr/commands/snapshot"
|
||||||
versionCmd "github.com/containerd/containerd/cmd/ctr/commands/version"
|
versionCmd "github.com/containerd/containerd/cmd/ctr/commands/version"
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/server"
|
"github.com/containerd/containerd/server"
|
||||||
@ -69,20 +75,20 @@ containerd CLI
|
|||||||
plugins.Command,
|
plugins.Command,
|
||||||
versionCmd.Command,
|
versionCmd.Command,
|
||||||
applyCommand,
|
applyCommand,
|
||||||
containersCommand,
|
containers.Command,
|
||||||
contentCommand,
|
content.Command,
|
||||||
eventsCommand,
|
events.Command,
|
||||||
fetchCommand,
|
fetchCommand,
|
||||||
fetchObjectCommand,
|
fetchObjectCommand,
|
||||||
imageCommand,
|
images.Command,
|
||||||
namespacesCommand,
|
namespacesCmd.Command,
|
||||||
pprofCommand,
|
pprofCommand,
|
||||||
pullCommand,
|
pullCommand,
|
||||||
pushCommand,
|
pushCommand,
|
||||||
pushObjectCommand,
|
pushObjectCommand,
|
||||||
rootfsCommand,
|
rootfsCommand,
|
||||||
runCommand,
|
runCommand,
|
||||||
snapshotCommand,
|
snapshot.Command,
|
||||||
tasksCommand,
|
tasksCommand,
|
||||||
}, extraCmds...)
|
}, extraCmds...)
|
||||||
app.Before = func(context *cli.Context) error {
|
app.Before = func(context *cli.Context) error {
|
||||||
|
Loading…
Reference in New Issue
Block a user