-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable inspection (aka "shallow pull") of an image's manifest info, and also the creation of manifest lists (aka "fat manifests" or "multi-arch images"). The workflow for creating a manifest list 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
Showing
141 changed files
with
19,091 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
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/pkg/errors" | ||
"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, "cpu-features", []string{}, "Add feature info to a manifest before pushing it.") | ||
flags.StringSliceVar(&opts.osFeatures, "os-features", []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 errors.Wrapf(err, "annotate: Error parsing name for manifest list (%s): %s", opts.target) | ||
} | ||
imgRef, err := reference.ParseNormalizedNamed(opts.image) | ||
if err != nil { | ||
return errors.Wrapf(err, "annotate: Error prasing name for manifest (%s): %s:", opts.image) | ||
} | ||
|
||
// 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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package manifest | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/Sirupsen/logrus" | ||
"github.com/pkg/errors" | ||
"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 errors.Wrapf(err, "error parsing name for manifest list (%s): %v", newRef) | ||
} | ||
_, err = registry.ParseRepositoryInfo(targetRef) | ||
if err != nil { | ||
return errors.Wrapf(err, "error parsing repository name for manifest list (%s): %v", newRef) | ||
} | ||
|
||
// 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("manifest lists cannot embed another manifest list") | ||
} | ||
|
||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// +build !linux | ||
|
||
package manifest | ||
|
||
func ensureHomeIfIAmStatic() error { | ||
return nil | ||
} |
Oops, something went wrong.