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{
|
app.Commands = []cli.Command{
|
||||||
contentCommand,
|
|
||||||
pullCommand,
|
pullCommand,
|
||||||
fetchCommand,
|
fetchCommand,
|
||||||
fetchObjectCommand,
|
fetchObjectCommand,
|
||||||
@ -78,17 +77,3 @@ distribution tool
|
|||||||
os.Exit(1)
|
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