images, containers: converge metadata API conventions

The primary feature we get with this PR is support for filters and
labels on the image metadata store. In the process of doing this, the
conventions for the API have been converged between containers and
images, providing a model for other services.

With images, `Put` (renamed to `Update` briefly) has been split into a
`Create` and `Update`, allowing one to control the behavior around these
operations. `Update` now includes support for masking fields at the
datastore-level across both the containers and image service. Filters
are now just string values to interpreted directly within the data
store. This should allow for some interesting future use cases in which
the datastore might use the syntax for more efficient query paths.

The containers service has been updated to follow these conventions as
closely as possible.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day
2017-07-10 16:19:44 -07:00
parent 34f7b29120
commit 7f4c4aecf7
19 changed files with 1581 additions and 318 deletions

109
cmd/dist/images.go vendored
View File

@@ -3,9 +3,12 @@ package main
import (
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/progress"
"github.com/pkg/errors"
@@ -18,6 +21,7 @@ var imageCommand = cli.Command{
Subcommands: cli.Commands{
imagesListCommand,
imageRemoveCommand,
imagesSetLabelsCommand,
},
}
@@ -29,7 +33,10 @@ var imagesListCommand = cli.Command{
Description: `List images registered with containerd.`,
Flags: []cli.Flag{},
Action: func(clicontext *cli.Context) error {
ctx, cancel := appContext(clicontext)
var (
filters = clicontext.Args()
ctx, cancel = appContext(clicontext)
)
defer cancel()
client, err := getClient(clicontext)
@@ -40,26 +47,93 @@ var imagesListCommand = cli.Command{
imageStore := client.ImageService()
cs := client.ContentStore()
images, err := imageStore.List(ctx)
images, err := imageStore.List(ctx, filters...)
if err != nil {
return errors.Wrap(err, "failed to list images")
}
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSIZE\t")
fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSIZE\tLABELS\t")
for _, image := range images {
size, err := image.Size(ctx, cs)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed calculating size for image %s", image.Name)
}
fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t\n", image.Name, image.Target.MediaType, image.Target.Digest, progress.Bytes(size))
labels := "-"
if len(image.Labels) > 0 {
var pairs []string
for k, v := range image.Labels {
pairs = append(pairs, fmt.Sprintf("%v=%v", k, v))
}
sort.Strings(pairs)
labels = strings.Join(pairs, ",")
}
fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%s\t\n",
image.Name,
image.Target.MediaType,
image.Target.Digest,
progress.Bytes(size),
labels)
}
return tw.Flush()
},
}
var imagesSetLabelsCommand = cli.Command{
Name: "set-labels",
Usage: "Set and clear labels for an image.",
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
Description: "Set and clear labels for an image.",
Flags: []cli.Flag{},
Action: func(clicontext *cli.Context) error {
var (
ctx, cancel = appContext(clicontext)
name, labels = objectWithLabelArgs(clicontext)
)
defer cancel()
client, err := getClient(clicontext)
if err != nil {
return err
}
if name == "" {
return errors.New("please specify an image")
}
var (
is = client.ImageService()
fieldpaths []string
)
for k := range labels {
fieldpaths = append(fieldpaths, strings.Join([]string{"labels", k}, "."))
}
image := images.Image{
Name: name,
Labels: labels,
}
updated, err := is.Update(ctx, image, fieldpaths...)
if err != nil {
return err
}
var labelStrings []string
for k, v := range updated.Labels {
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
}
fmt.Println(strings.Join(labelStrings, ","))
return nil
},
}
var imageRemoveCommand = cli.Command{
Name: "remove",
Aliases: []string{"rm"},
@@ -98,3 +172,30 @@ var imageRemoveCommand = cli.Command{
return exitErr
},
}
// TODO(stevvooe): These helpers should go away when we merge dist and ctr.
func objectWithLabelArgs(clicontext *cli.Context) (string, map[string]string) {
var (
name = clicontext.Args().First()
labelStrings = clicontext.Args().Tail()
)
return name, 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
}