cmd/{ctr, dist}: move content command to ctr
Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
parent
695351f38e
commit
1db80ed966
445
cmd/ctr/content.go
Normal file
445
cmd/ctr/content.go
Normal file
@ -0,0 +1,445 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
contentCommand = cli.Command{
|
||||
Name: "content",
|
||||
Usage: "content management",
|
||||
Subcommands: cli.Commands{
|
||||
listContentCommand,
|
||||
ingestContentCommand,
|
||||
activeIngestCommand,
|
||||
getContentCommand,
|
||||
editContentCommand,
|
||||
deleteContentCommand,
|
||||
labelContentCommand,
|
||||
},
|
||||
}
|
||||
|
||||
getContentCommand = cli.Command{
|
||||
Name: "get",
|
||||
Usage: "get the data for an object",
|
||||
ArgsUsage: "[flags] [<digest>, ...]",
|
||||
Description: "Display the image object.",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(context *cli.Context) error {
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
dgst, err := digest.Parse(context.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs, err := getContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := cs.Reader(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
_, err = io.Copy(os.Stdout, rc)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
ingestContentCommand = cli.Command{
|
||||
Name: "ingest",
|
||||
Usage: "accept content into the store",
|
||||
ArgsUsage: "[flags] <key>",
|
||||
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"))
|
||||
)
|
||||
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
if err := expectedDigest.Validate(); expectedDigest != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ref == "" {
|
||||
return errors.New("must specify a transaction reference")
|
||||
}
|
||||
|
||||
cs, err := getContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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] [<regexp>]",
|
||||
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 {
|
||||
var (
|
||||
match = context.Args().First()
|
||||
)
|
||||
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
cs, err := getContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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()
|
||||
},
|
||||
}
|
||||
|
||||
listContentCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list all blobs in the store.",
|
||||
ArgsUsage: "[flags] [<filter>, ...]",
|
||||
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())
|
||||
)
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
cs, err := getContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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...)
|
||||
},
|
||||
}
|
||||
|
||||
labelContentCommand = cli.Command{
|
||||
Name: "label",
|
||||
Usage: "adds labels to content",
|
||||
ArgsUsage: "[flags] <digest> [<label>=<value> ...]",
|
||||
Description: `Labels blobs in the content store`,
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
object, labels = objectWithLabelArgs(context)
|
||||
)
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
cs, err := getContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dgst, err := digest.Parse(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info := content.Info{
|
||||
Digest: dgst,
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
|
||||
var paths []string
|
||||
for k, v := range labels {
|
||||
paths = append(paths, fmt.Sprintf("labels.%s", k))
|
||||
if v != "" {
|
||||
info.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing updated, do no clear
|
||||
if len(paths) == 0 {
|
||||
info, err = cs.Info(ctx, info.Digest)
|
||||
} else {
|
||||
info, err = cs.Update(ctx, info, paths...)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var labelStrings []string
|
||||
for k, v := range info.Labels {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(labelStrings, ","))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
editContentCommand = cli.Command{
|
||||
Name: "edit",
|
||||
Usage: "edit a blob and return a new digest.",
|
||||
ArgsUsage: "[flags] <digest>",
|
||||
Description: `Edit a blob and return a new digest.`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "validate",
|
||||
Usage: "validate the result against a format (json, mediatype, etc.)",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
validate = context.String("validate")
|
||||
object = context.Args().First()
|
||||
)
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
if validate != "" {
|
||||
return errors.New("validating the edit result not supported")
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Support looking up objects by a reference through
|
||||
// the image metadata storage.
|
||||
|
||||
dgst, err := digest.Parse(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := getContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := content.Reader(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
nrc, err := edit(rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nrc.Close()
|
||||
|
||||
wr, err := content.Writer(ctx, "edit-"+object, 0, "") // TODO(stevvooe): Choose a better key?
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(wr, nrc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := wr.Commit(0, wr.Digest()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(wr.Digest())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
deleteContentCommand = cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"del", "remove", "rm"},
|
||||
Usage: "permanently delete one or more blobs.",
|
||||
ArgsUsage: "[flags] [<digest>, ...]",
|
||||
Description: `Delete one or more blobs permanently. Successfully deleted
|
||||
blobs are printed to stdout.`,
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
args = []string(context.Args())
|
||||
exitError error
|
||||
)
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
cs, err := getContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
dgst, err := digest.Parse(arg)
|
||||
if err != nil {
|
||||
if exitError == nil {
|
||||
exitError = err
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("could not delete %v", dgst)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cs.Delete(ctx, dgst); err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
if exitError == nil {
|
||||
exitError = err
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("could not delete %v", dgst)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println(dgst)
|
||||
}
|
||||
|
||||
return exitError
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func edit(rd io.Reader) (io.ReadCloser, error) {
|
||||
tmp, err := ioutil.TempFile("", "edit-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(tmp, rd); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command("sh", "-c", "$EDITOR "+tmp.Name())
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := tmp.Seek(0, io.SeekStart); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return onCloser{ReadCloser: tmp, onClose: func() error {
|
||||
return os.RemoveAll(tmp.Name())
|
||||
}}, nil
|
||||
}
|
||||
|
||||
type onCloser struct {
|
||||
io.ReadCloser
|
||||
onClose func() error
|
||||
}
|
||||
|
||||
func (oc onCloser) Close() error {
|
||||
var err error
|
||||
if err1 := oc.ReadCloser.Close(); err1 != nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
if oc.onClose != nil {
|
||||
err1 := oc.onClose()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
59
cmd/dist/active.go
vendored
59
cmd/dist/active.go
vendored
@ -1,59 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var activeCommand = cli.Command{
|
||||
Name: "active",
|
||||
Usage: "display active transfers.",
|
||||
ArgsUsage: "[flags] [<regexp>]",
|
||||
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 {
|
||||
var (
|
||||
match = context.Args().First()
|
||||
)
|
||||
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
cs, err := resolveContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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()
|
||||
},
|
||||
}
|
60
cmd/dist/delete.go
vendored
60
cmd/dist/delete.go
vendored
@ -1,60 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/log"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var deleteCommand = cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"del", "remove", "rm"},
|
||||
Usage: "permanently delete one or more blobs.",
|
||||
ArgsUsage: "[flags] [<digest>, ...]",
|
||||
Description: `Delete one or more blobs permanently. Successfully deleted
|
||||
blobs are printed to stdout.`,
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
args = []string(context.Args())
|
||||
exitError error
|
||||
)
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
client, err := getClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := client.ContentStore()
|
||||
|
||||
for _, arg := range args {
|
||||
dgst, err := digest.Parse(arg)
|
||||
if err != nil {
|
||||
if exitError == nil {
|
||||
exitError = err
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("could not delete %v", dgst)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cs.Delete(ctx, dgst); err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
if exitError == nil {
|
||||
exitError = err
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("could not delete %v", dgst)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println(dgst)
|
||||
}
|
||||
|
||||
return exitError
|
||||
},
|
||||
}
|
135
cmd/dist/edit.go
vendored
135
cmd/dist/edit.go
vendored
@ -1,135 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var editCommand = cli.Command{
|
||||
Name: "edit",
|
||||
Usage: "edit a blob and return a new digest.",
|
||||
ArgsUsage: "[flags] <digest>",
|
||||
Description: `Edit a blob and return a new digest.`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "validate",
|
||||
Usage: "validate the result against a format (json, mediatype, etc.)",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
validate = context.String("validate")
|
||||
object = context.Args().First()
|
||||
)
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
if validate != "" {
|
||||
return errors.New("validating the edit result not supported")
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Support looking up objects by a reference through
|
||||
// the image metadata storage.
|
||||
|
||||
dgst, err := digest.Parse(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := getClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := client.ContentStore()
|
||||
|
||||
rc, err := content.Reader(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
nrc, err := edit(rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nrc.Close()
|
||||
|
||||
wr, err := content.Writer(ctx, "edit-"+object, 0, "") // TODO(stevvooe): Choose a better key?
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(wr, nrc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := wr.Commit(0, wr.Digest()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(wr.Digest())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func edit(rd io.Reader) (io.ReadCloser, error) {
|
||||
tmp, err := ioutil.TempFile("", "edit-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(tmp, rd); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command("sh", "-c", "$EDITOR "+tmp.Name())
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := tmp.Seek(0, io.SeekStart); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return onCloser{ReadCloser: tmp, onClose: func() error {
|
||||
return os.RemoveAll(tmp.Name())
|
||||
}}, nil
|
||||
}
|
||||
|
||||
type onCloser struct {
|
||||
io.ReadCloser
|
||||
onClose func() error
|
||||
}
|
||||
|
||||
func (oc onCloser) Close() error {
|
||||
var err error
|
||||
if err1 := oc.ReadCloser.Close(); err1 != nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
if oc.onClose != nil {
|
||||
err1 := oc.onClose()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
40
cmd/dist/get.go
vendored
40
cmd/dist/get.go
vendored
@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var getCommand = cli.Command{
|
||||
Name: "get",
|
||||
Usage: "get the data for an object",
|
||||
ArgsUsage: "[flags] [<digest>, ...]",
|
||||
Description: "Display the image object.",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(context *cli.Context) error {
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
dgst, err := digest.Parse(context.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs, err := resolveContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := cs.Reader(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
_, err = io.Copy(os.Stdout, rc)
|
||||
return err
|
||||
},
|
||||
}
|
55
cmd/dist/ingest.go
vendored
55
cmd/dist/ingest.go
vendored
@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var ingestCommand = cli.Command{
|
||||
Name: "ingest",
|
||||
Usage: "accept content into the store",
|
||||
ArgsUsage: "[flags] <key>",
|
||||
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"))
|
||||
)
|
||||
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
if err := expectedDigest.Validate(); expectedDigest != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ref == "" {
|
||||
return errors.New("must specify a transaction reference")
|
||||
}
|
||||
|
||||
cs, err := resolveContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
},
|
||||
}
|
92
cmd/dist/labels.go
vendored
92
cmd/dist/labels.go
vendored
@ -1,92 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var labelContentCommand = cli.Command{
|
||||
Name: "label",
|
||||
Usage: "adds labels to content",
|
||||
ArgsUsage: "[flags] <digest> [<label>=<value> ...]",
|
||||
Description: `Labels blobs in the content store`,
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
object, labels = objectWithLabelArgs(context)
|
||||
)
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
cs, err := resolveContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dgst, err := digest.Parse(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info := content.Info{
|
||||
Digest: dgst,
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
|
||||
var paths []string
|
||||
for k, v := range labels {
|
||||
paths = append(paths, fmt.Sprintf("labels.%s", k))
|
||||
if v != "" {
|
||||
info.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing updated, do no clear
|
||||
if len(paths) == 0 {
|
||||
info, err = cs.Info(ctx, info.Digest)
|
||||
} else {
|
||||
info, err = cs.Update(ctx, info, paths...)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var labelStrings []string
|
||||
for k, v := range info.Labels {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(labelStrings, ","))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func objectWithLabelArgs(clicontext *cli.Context) (string, map[string]string) {
|
||||
var (
|
||||
namespace = clicontext.Args().First()
|
||||
labelStrings = clicontext.Args().Tail()
|
||||
)
|
||||
|
||||
return namespace, labelArgs(labelStrings)
|
||||
}
|
||||
|
||||
func labelArgs(labelStrings []string) map[string]string {
|
||||
labels := make(map[string]string, len(labelStrings))
|
||||
for _, label := range labelStrings {
|
||||
parts := strings.SplitN(label, "=", 2)
|
||||
key := parts[0]
|
||||
value := "true"
|
||||
if len(parts) > 1 {
|
||||
value = parts[1]
|
||||
}
|
||||
|
||||
labels[key] = value
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
73
cmd/dist/list.go
vendored
73
cmd/dist/list.go
vendored
@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list all blobs in the store.",
|
||||
ArgsUsage: "[flags] [<filter>, ...]",
|
||||
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())
|
||||
)
|
||||
ctx, cancel := appContext(context)
|
||||
defer cancel()
|
||||
|
||||
cs, err := resolveContentStore(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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...)
|
||||
},
|
||||
}
|
15
cmd/dist/main.go
vendored
15
cmd/dist/main.go
vendored
@ -58,7 +58,6 @@ distribution tool
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
contentCommand,
|
||||
pullCommand,
|
||||
fetchCommand,
|
||||
fetchObjectCommand,
|
||||
@ -78,17 +77,3 @@ distribution tool
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var contentCommand = cli.Command{
|
||||
Name: "content",
|
||||
Usage: "content management",
|
||||
Subcommands: cli.Commands{
|
||||
listCommand,
|
||||
ingestCommand,
|
||||
activeCommand,
|
||||
getCommand,
|
||||
editCommand,
|
||||
deleteCommand,
|
||||
labelContentCommand,
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user