package content import ( "fmt" "io" "io/ioutil" "os" "os/exec" "strings" "text/tabwriter" "time" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" units "github.com/docker/go-units" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/urfave/cli" ) var ( // Command is the cli command for managing content Command = cli.Command{ Name: "content", Usage: "manage content", Subcommands: cli.Commands{ listCommand, ingestCommand, activeIngestCommand, getCommand, editCommand, deleteCommand, setLabelsCommand, fetchCommand, fetchObjectCommand, pushObjectCommand, }, } getCommand = cli.Command{ Name: "get", Usage: "get the data for an object", ArgsUsage: "[, ...]", Description: "display the image object", Action: func(context *cli.Context) error { dgst, err := digest.Parse(context.Args().First()) if err != nil { return err } client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() cs := client.ContentStore() ra, err := cs.ReaderAt(ctx, dgst) if err != nil { return err } defer ra.Close() _, err = io.Copy(os.Stdout, content.NewReader(ra)) return err }, } ingestCommand = cli.Command{ Name: "ingest", Usage: "accept content into the store", ArgsUsage: "[flags] ", Description: "ingest objects into the local content store", Flags: []cli.Flag{ cli.Int64Flag{ Name: "expected-size", Usage: "validate against provided size", }, cli.StringFlag{ Name: "expected-digest", Usage: "verify content against expected digest", }, }, Action: func(context *cli.Context) error { var ( ref = context.Args().First() expectedSize = context.Int64("expected-size") expectedDigest = digest.Digest(context.String("expected-digest")) ) if err := expectedDigest.Validate(); expectedDigest != "" && err != nil { return err } if ref == "" { return errors.New("must specify a transaction reference") } client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() cs := client.ContentStore() // TODO(stevvooe): Allow ingest to be reentrant. Currently, we expect // all data to be written in a single invocation. Allow multiple writes // to the same transaction key followed by a commit. return content.WriteBlob(ctx, cs, ref, os.Stdin, expectedSize, expectedDigest) }, } activeIngestCommand = cli.Command{ Name: "active", Usage: "display active transfers", ArgsUsage: "[flags] []", Description: "display the ongoing transfers", Flags: []cli.Flag{ cli.DurationFlag{ Name: "timeout, t", Usage: "total timeout for fetch", EnvVar: "CONTAINERD_FETCH_TIMEOUT", }, cli.StringFlag{ Name: "root", Usage: "path to content store root", Value: "/tmp/content", // TODO(stevvooe): for now, just use the PWD/.content }, }, Action: func(context *cli.Context) error { match := context.Args().First() client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() cs := client.ContentStore() active, err := cs.ListStatuses(ctx, match) if err != nil { return err } tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0) fmt.Fprintln(tw, "REF\tSIZE\tAGE\t") for _, active := range active { fmt.Fprintf(tw, "%s\t%s\t%s\t\n", active.Ref, units.HumanSize(float64(active.Offset)), units.HumanDuration(time.Since(active.StartedAt))) } return tw.Flush() }, } listCommand = cli.Command{ Name: "list", Aliases: []string{"ls"}, Usage: "list all blobs in the store", ArgsUsage: "[flags]", Description: "list blobs in the content store", Flags: []cli.Flag{ cli.BoolFlag{ Name: "quiet, q", Usage: "print only the blob digest", }, }, Action: func(context *cli.Context) error { var ( quiet = context.Bool("quiet") args = []string(context.Args()) ) client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() cs := client.ContentStore() var walkFn content.WalkFunc if quiet { walkFn = func(info content.Info) error { fmt.Println(info.Digest) return nil } } else { tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0) defer tw.Flush() fmt.Fprintln(tw, "DIGEST\tSIZE\tAGE\tLABELS") walkFn = func(info content.Info) error { var labelStrings []string for k, v := range info.Labels { labelStrings = append(labelStrings, strings.Join([]string{k, v}, "=")) } labels := strings.Join(labelStrings, ",") if labels == "" { labels = "-" } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", info.Digest, units.HumanSize(float64(info.Size)), units.HumanDuration(time.Since(info.CreatedAt)), labels) return nil } } return cs.Walk(ctx, walkFn, args...) }, } setLabelsCommand = cli.Command{ Name: "label", Usage: "add labels to content", ArgsUsage: " [