Skip to content

Commit

Permalink
Add manifest command
Browse files Browse the repository at this point in the history
The workflow will be:

`docker manifest create new-list-ref-name manifest [manifests...]`
`docker manifest annotate new-list-ref-name manifest --os linux --arch arm`
`docker manifest push new-list-ref-name`

- or -

`docker manifest push -f annotated-manifests.yaml`

There is also a `manifest inspect` command to allow for a *shallow pull*
of an image's manifest: `docker manifest inspect
manifest-or-manifest_list`.
These by default show a ManifestDescriptor in the case of a single
manifest, or a DeserialedManifestList.

To be more in line with the existing external manifest tool, there is
also a `-v` option for inspect that will show information depending on
what the reference maps to (list or single manifest).

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com>
  • Loading branch information
clnperez committed May 30, 2017
1 parent f3cb13c commit 42b0542
Show file tree
Hide file tree
Showing 141 changed files with 19,148 additions and 3 deletions.
8 changes: 6 additions & 2 deletions cli/command/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/docker/cli/cli/command/config"
"github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/manifest"
"github.com/docker/cli/cli/command/network"
"github.com/docker/cli/cli/command/node"
"github.com/docker/cli/cli/command/plugin"
Expand Down Expand Up @@ -38,12 +39,15 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
image.NewImageCommand(dockerCli),
image.NewBuildCommand(dockerCli),

// node
node.NewNodeCommand(dockerCli),
// manfiest
manifest.NewManifestCommand(dockerCli),

// network
network.NewNetworkCommand(dockerCli),

// node
node.NewNodeCommand(dockerCli),

// plugin
plugin.NewPluginCommand(dockerCli),

Expand Down
127 changes: 127 additions & 0 deletions cli/command/manifest/annotate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package manifest

import (
"fmt"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/distribution/reference"

"github.com/Sirupsen/logrus"
"github.com/spf13/cobra"
)

type annotateOptions struct {
target string // the target manifest list name (also transaction ID)
image string // the manifest to annotate within the list
variant string // an architecture variant
os string
arch string
cpuFeatures []string
osFeatures []string
}

// NewAnnotateCommand creates a new `docker manifest annotate` command
func newAnnotateCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts annotateOptions

cmd := &cobra.Command{
Use: "annotate NAME[:TAG] [OPTIONS]",
Short: "Add additional information to an image's manifest.",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.target = args[0]
opts.image = args[1]
return runManifestAnnotate(dockerCli, opts)
},
}

flags := cmd.Flags()

flags.StringVar(&opts.os, "os", "", "Add ios info to a manifest before pushing it.")
flags.StringVar(&opts.arch, "arch", "", "Add arch info to a manifest before pushing it.")
flags.StringSliceVar(&opts.cpuFeatures, "cpuFeatures", []string{}, "Add feature info to a manifest before pushing it.")
flags.StringSliceVar(&opts.osFeatures, "osFeatures", []string{}, "Add feature info to a manifest before pushing it.")
flags.StringVar(&opts.variant, "variant", "", "Add arch variant to a manifest before pushing it.")

return cmd
}

func runManifestAnnotate(dockerCli *command.DockerCli, opts annotateOptions) error {

// Make sure the manifests are pulled, find the file you need, unmarshal the json, edit the file, and done.
targetRef, err := reference.ParseNormalizedNamed(opts.target)
if err != nil {
return fmt.Errorf("Annotate: Error parsing name for manifest list (%s): %s", opts.target, err)
}
imgRef, err := reference.ParseNormalizedNamed(opts.image)
if err != nil {
return fmt.Errorf("Annotate: Error prasing name for manifest (%s): %s:", opts.image, err)
}

// Make sure we've got tags or digests:
if _, isDigested := targetRef.(reference.Canonical); !isDigested {
targetRef = reference.TagNameOnly(targetRef)
}
if _, isDigested := imgRef.(reference.Canonical); !isDigested {
imgRef = reference.TagNameOnly(imgRef)
}
transactionID := makeFilesafeName(targetRef.String())
imgID := makeFilesafeName(imgRef.String())
logrus.Debugf("Beginning annotate for %s/%s", transactionID, imgID)

imgInspect, _, err := getImageData(dockerCli, imgRef.String(), targetRef.String(), false)
if err != nil {
return err
}

if len(imgInspect) > 1 {
return fmt.Errorf("Cannot annotate manifest list. Please pass an image (not list) name")
}

mf := imgInspect[0]

newMf, err := unmarshalIntoManifestInspect(imgID, transactionID)
if err != nil {
return err
}

// Update the mf
if opts.os != "" {
newMf.OS = opts.os
}
if opts.arch != "" {
newMf.Architecture = opts.arch
}
for _, cpuFeature := range opts.cpuFeatures {
newMf.Features = appendIfUnique(mf.Features, cpuFeature)
}
for _, osFeature := range opts.osFeatures {
newMf.OSFeatures = appendIfUnique(mf.OSFeatures, osFeature)
}
if opts.variant != "" {
newMf.Variant = opts.variant
}

// validate os/arch input
if !isValidOSArch(newMf.OS, newMf.Architecture) {
return fmt.Errorf("Manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
}
// @TODO
// dgst := digest.FromBytes(b) can't use b/c not of the json.

if err := updateMfFile(newMf, imgID, transactionID); err != nil {
return err
}

logrus.Debugf("Annotated %s with options %v", mf.RefName, opts)
return nil
}
func appendIfUnique(list []string, str string) []string {
for _, s := range list {
if s == str {
return list
}
}
return append(list, str)
}
44 changes: 44 additions & 0 deletions cli/command/manifest/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package manifest

import (
"fmt"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"

"github.com/spf13/cobra"
)

// NewManifestCommand returns a cobra command for `manifest` subcommands
func NewManifestCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd := &cobra.Command{
Use: "manifest COMMAND",
Short: "Manage Docker image manifests and lists",
Long: manifestDescription,
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
},
}
cmd.AddCommand(
//newListFetchCommand(dockerCli),
newCreateListCommand(dockerCli),
newInspectCommand(dockerCli),
newAnnotateCommand(dockerCli),
newPushListCommand(dockerCli),
)
return cmd
}

var manifestDescription = `
The **docker manifest** command has subcommands for managing image manifests and
manifest lists. A manifest list allows you to use one name to refer to the same image
built for multiple architectures.
To see help for a subcommand, use:
docker manifest CMD help
For full details on using docker manifest lists view the registry v2 specification.
`
80 changes: 80 additions & 0 deletions cli/command/manifest/create_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package manifest

import (
"fmt"

"github.com/Sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/distribution/reference"
"github.com/docker/docker/registry"
)

type annotateOpts struct {
amend bool
}

func newCreateListCommand(dockerCli *command.DockerCli) *cobra.Command {

opts := annotateOpts{}

cmd := &cobra.Command{
Use: "create newRef manifest [manifest...]",
Short: "Create a local manifest list for annotating and pushing to a registry",
Args: cli.RequiresMinArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return createManifestList(dockerCli, args, opts)
},
}

flags := cmd.Flags()
flags.BoolVarP(&opts.amend, "amend", "a", false, "Amend an existing manifest list transaction")
return cmd
}

func createManifestList(dockerCli *command.DockerCli, args []string, opts annotateOpts) error {

// Just do some basic verification here, and leave the rest for when the user pushes the list
newRef := args[0]
targetRef, err := reference.ParseNormalizedNamed(newRef)
if err != nil {
return fmt.Errorf("Error parsing name for manifest list (%s): %v", newRef, err)
}
_, err = registry.ParseRepositoryInfo(targetRef)
if err != nil {
return fmt.Errorf("Error parsing repository name for manifest list (%s): %v", newRef, err)
}

// Check locally for this list transaction before proceeding
if _, isDigested := targetRef.(reference.Canonical); !isDigested {
targetRef = reference.TagNameOnly(targetRef)
}
manifestFiles, err := getListFilenames(makeFilesafeName(targetRef.String()))
if err != nil {
return err
}
if len(manifestFiles) > 0 && !opts.amend {
return fmt.Errorf("Refusing to continue over an existing manifest list transaction with no --amend flag")
}

// Now create the local manifest list transaction by looking up the manifest schemas
// for the constituent images:
manifests := args[1:]
logrus.Info("Retrieving digests of images...")
for _, manifestRef := range manifests {

mfstData, _, err := getImageData(dockerCli, manifestRef, targetRef.String(), false)
if err != nil {
return err
}

if len(mfstData) > 1 {
// too many responses--can only happen if a manifest list was returned for the name lookup
return fmt.Errorf("You specified a manifest list entry from a digest that points to a current manifest list. Manifest lists do not allow recursion.")
}

}
return nil
}
28 changes: 28 additions & 0 deletions cli/command/manifest/ensurehome_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// +build linux

package manifest

import (
"os"

"github.com/Sirupsen/logrus"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/pkg/homedir"
)

// ensureHomeIfIAmStatic ensure $HOME to be set if dockerversion.IAmStatic is "true".
// In a static binary, os/user.Current() leads to segfault due to a glibc issue that won't be fixed
// in the foreseeable future. (golang/go#13470, https://sourceware.org/bugzilla/show_bug.cgi?id=19341)
// So we forcibly set HOME so as to avoid call to os/user/Current()
func ensureHomeIfIAmStatic() error {
// Note: dockerversion.IAmStatic and homedir.GetStatic() is only available for linux.
if dockerversion.IAmStatic == "true" && os.Getenv("HOME") == "" {
home, err := homedir.GetStatic()
if err != nil {
return err
}
logrus.Warnf("docker manifest requires HOME to be set for static client binary. Forcibly setting HOME to %s.", home)
os.Setenv("HOME", home)
}
return nil
}
7 changes: 7 additions & 0 deletions cli/command/manifest/ensurehome_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build !linux

package manifest

func ensureHomeIfIAmStatic() error {
return nil
}
Loading

0 comments on commit 42b0542

Please sign in to comment.