Skip to content

Commit 81f1bb3

Browse files
committed
add manifest command
Enable inspection (aka "shallow pull") of images' manifest info, and also the creation of manifest lists (aka "fat manifests"). The workflow for creating a manifest list will be: `docker manifest create new-list-ref-name image-ref [image-ref...]` `docker manifest annotate new-list-ref-name image-ref --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 <[email protected]>
1 parent eb7452c commit 81f1bb3

File tree

141 files changed

+19055
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+19055
-3
lines changed

cli/command/commands/commands.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/docker/cli/cli/command/config"
99
"github.com/docker/cli/cli/command/container"
1010
"github.com/docker/cli/cli/command/image"
11+
"github.com/docker/cli/cli/command/manifest"
1112
"github.com/docker/cli/cli/command/network"
1213
"github.com/docker/cli/cli/command/node"
1314
"github.com/docker/cli/cli/command/plugin"
@@ -38,12 +39,15 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
3839
image.NewImageCommand(dockerCli),
3940
image.NewBuildCommand(dockerCli),
4041

41-
// node
42-
node.NewNodeCommand(dockerCli),
42+
// manfiest
43+
manifest.NewManifestCommand(dockerCli),
4344

4445
// network
4546
network.NewNetworkCommand(dockerCli),
4647

48+
// node
49+
node.NewNodeCommand(dockerCli),
50+
4751
// plugin
4852
plugin.NewPluginCommand(dockerCli),
4953

cli/command/manifest/annotate.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package manifest
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/docker/cli/cli"
7+
"github.com/docker/cli/cli/command"
8+
"github.com/docker/distribution/reference"
9+
10+
"github.com/Sirupsen/logrus"
11+
"github.com/pkg/errors"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
type annotateOptions struct {
16+
target string // the target manifest list name (also transaction ID)
17+
image string // the manifest to annotate within the list
18+
variant string // an architecture variant
19+
os string
20+
arch string
21+
osFeatures []string
22+
}
23+
24+
// NewAnnotateCommand creates a new `docker manifest annotate` command
25+
func newAnnotateCommand(dockerCli *command.DockerCli) *cobra.Command {
26+
var opts annotateOptions
27+
28+
cmd := &cobra.Command{
29+
Use: "annotate NAME[:TAG] [OPTIONS]",
30+
Short: "Add additional information to an image's manifest.",
31+
Args: cli.ExactArgs(2),
32+
RunE: func(cmd *cobra.Command, args []string) error {
33+
opts.target = args[0]
34+
opts.image = args[1]
35+
return runManifestAnnotate(dockerCli, opts)
36+
},
37+
}
38+
39+
flags := cmd.Flags()
40+
41+
flags.StringVar(&opts.os, "os", "", "Add ios info to a manifest before pushing it.")
42+
flags.StringVar(&opts.arch, "arch", "", "Add arch info to a manifest before pushing it.")
43+
flags.StringSliceVar(&opts.osFeatures, "os-features", []string{}, "Add feature info to a manifest before pushing it.")
44+
flags.StringVar(&opts.variant, "variant", "", "Add arch variant to a manifest before pushing it.")
45+
46+
return cmd
47+
}
48+
49+
func runManifestAnnotate(dockerCli *command.DockerCli, opts annotateOptions) error {
50+
51+
// Make sure the manifests are pulled, find the file you need, unmarshal the json, edit the file, and done.
52+
targetRef, err := reference.ParseNormalizedNamed(opts.target)
53+
if err != nil {
54+
return errors.Wrapf(err, "annotate: Error parsing name for manifest list (%s): %s", opts.target)
55+
}
56+
imgRef, err := reference.ParseNormalizedNamed(opts.image)
57+
if err != nil {
58+
return errors.Wrapf(err, "annotate: Error prasing name for manifest (%s): %s:", opts.image)
59+
}
60+
61+
// Make sure we've got tags or digests:
62+
if _, isDigested := targetRef.(reference.Canonical); !isDigested {
63+
targetRef = reference.TagNameOnly(targetRef)
64+
}
65+
if _, isDigested := imgRef.(reference.Canonical); !isDigested {
66+
imgRef = reference.TagNameOnly(imgRef)
67+
}
68+
transactionID := makeFilesafeName(targetRef.String())
69+
imgID := makeFilesafeName(imgRef.String())
70+
logrus.Debugf("beginning annotate for %s/%s", transactionID, imgID)
71+
72+
imgInspect, _, err := getImageData(dockerCli, imgRef.String(), targetRef.String(), false)
73+
if err != nil {
74+
return err
75+
}
76+
77+
if len(imgInspect) > 1 {
78+
return fmt.Errorf("cannot annotate manifest list. Please pass an image (not list) name")
79+
}
80+
81+
mf := imgInspect[0]
82+
83+
newMf, err := unmarshalIntoManifestInspect(imgID, transactionID)
84+
if err != nil {
85+
return err
86+
}
87+
88+
// Update the mf
89+
if opts.os != "" {
90+
newMf.OS = opts.os
91+
}
92+
if opts.arch != "" {
93+
newMf.Architecture = opts.arch
94+
}
95+
for _, osFeature := range opts.osFeatures {
96+
newMf.OSFeatures = appendIfUnique(mf.OSFeatures, osFeature)
97+
}
98+
if opts.variant != "" {
99+
newMf.Variant = opts.variant
100+
}
101+
102+
// validate os/arch input
103+
if !isValidOSArch(newMf.OS, newMf.Architecture) {
104+
return fmt.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
105+
}
106+
107+
if err := updateMfFile(newMf, imgID, transactionID); err != nil {
108+
return err
109+
}
110+
111+
logrus.Debugf("annotated %s with options %v", mf.RefName, opts)
112+
return nil
113+
}
114+
func appendIfUnique(list []string, str string) []string {
115+
for _, s := range list {
116+
if s == str {
117+
return list
118+
}
119+
}
120+
return append(list, str)
121+
}

cli/command/manifest/cmd.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package manifest
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/docker/cli/cli"
7+
"github.com/docker/cli/cli/command"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
// NewManifestCommand returns a cobra command for `manifest` subcommands
13+
func NewManifestCommand(dockerCli *command.DockerCli) *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "manifest COMMAND",
16+
Short: "Manage Docker image manifests and lists",
17+
Long: manifestDescription,
18+
Args: cli.NoArgs,
19+
Run: func(cmd *cobra.Command, args []string) {
20+
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
21+
},
22+
}
23+
cmd.AddCommand(
24+
//newListFetchCommand(dockerCli),
25+
newCreateListCommand(dockerCli),
26+
newInspectCommand(dockerCli),
27+
newAnnotateCommand(dockerCli),
28+
newPushListCommand(dockerCli),
29+
)
30+
return cmd
31+
}
32+
33+
var manifestDescription = `
34+
The **docker manifest** command has subcommands for managing image manifests and
35+
manifest lists. A manifest list allows you to use one name to refer to the same image
36+
built for multiple architectures.
37+
38+
To see help for a subcommand, use:
39+
40+
docker manifest CMD help
41+
42+
For full details on using docker manifest lists view the registry v2 specification.
43+
44+
`
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package manifest
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Sirupsen/logrus"
7+
"github.com/pkg/errors"
8+
"github.com/spf13/cobra"
9+
10+
"github.com/docker/cli/cli"
11+
"github.com/docker/cli/cli/command"
12+
"github.com/docker/distribution/reference"
13+
"github.com/docker/docker/registry"
14+
)
15+
16+
type annotateOpts struct {
17+
amend bool
18+
}
19+
20+
func newCreateListCommand(dockerCli *command.DockerCli) *cobra.Command {
21+
22+
opts := annotateOpts{}
23+
24+
cmd := &cobra.Command{
25+
Use: "create newRef manifest [manifest...]",
26+
Short: "Create a local manifest list for annotating and pushing to a registry",
27+
Args: cli.RequiresMinArgs(2),
28+
RunE: func(cmd *cobra.Command, args []string) error {
29+
return createManifestList(dockerCli, args, opts)
30+
},
31+
}
32+
33+
flags := cmd.Flags()
34+
flags.BoolVarP(&opts.amend, "amend", "a", false, "Amend an existing manifest list transaction")
35+
return cmd
36+
}
37+
38+
func createManifestList(dockerCli *command.DockerCli, args []string, opts annotateOpts) error {
39+
40+
// Just do some basic verification here, and leave the rest for when the user pushes the list
41+
newRef := args[0]
42+
targetRef, err := reference.ParseNormalizedNamed(newRef)
43+
if err != nil {
44+
return errors.Wrapf(err, "error parsing name for manifest list (%s): %v", newRef)
45+
}
46+
_, err = registry.ParseRepositoryInfo(targetRef)
47+
if err != nil {
48+
return errors.Wrapf(err, "error parsing repository name for manifest list (%s): %v", newRef)
49+
}
50+
51+
// Check locally for this list transaction before proceeding
52+
if _, isDigested := targetRef.(reference.Canonical); !isDigested {
53+
targetRef = reference.TagNameOnly(targetRef)
54+
}
55+
manifestFiles, err := getListFilenames(makeFilesafeName(targetRef.String()))
56+
if err != nil {
57+
return err
58+
}
59+
if len(manifestFiles) > 0 && !opts.amend {
60+
return fmt.Errorf("refusing to continue over an existing manifest list transaction with no --amend flag")
61+
}
62+
63+
// Now create the local manifest list transaction by looking up the manifest schemas
64+
// for the constituent images:
65+
manifests := args[1:]
66+
logrus.Info("retrieving digests of images...")
67+
for _, manifestRef := range manifests {
68+
69+
mfstData, _, err := getImageData(dockerCli, manifestRef, targetRef.String(), false)
70+
if err != nil {
71+
return err
72+
}
73+
74+
if len(mfstData) > 1 {
75+
// too many responses--can only happen if a manifest list was returned for the name lookup
76+
return fmt.Errorf("manifest lists cannot embed another manifest list")
77+
}
78+
79+
}
80+
return nil
81+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// +build linux
2+
3+
package manifest
4+
5+
import (
6+
"os"
7+
8+
"github.com/Sirupsen/logrus"
9+
"github.com/docker/docker/dockerversion"
10+
"github.com/docker/docker/pkg/homedir"
11+
)
12+
13+
// ensureHomeIfIAmStatic ensure $HOME to be set if dockerversion.IAmStatic is "true".
14+
// In a static binary, os/user.Current() leads to segfault due to a glibc issue that won't be fixed
15+
// in the foreseeable future. (golang/go#13470, https://sourceware.org/bugzilla/show_bug.cgi?id=19341)
16+
// So we forcibly set HOME so as to avoid call to os/user/Current()
17+
func ensureHomeIfIAmStatic() error {
18+
// Note: dockerversion.IAmStatic and homedir.GetStatic() is only available for linux.
19+
if dockerversion.IAmStatic == "true" && os.Getenv("HOME") == "" {
20+
home, err := homedir.GetStatic()
21+
if err != nil {
22+
return err
23+
}
24+
logrus.Warnf("docker manifest requires HOME to be set for static client binary. Forcibly setting HOME to %s.", home)
25+
os.Setenv("HOME", home)
26+
}
27+
return nil
28+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// +build !linux
2+
3+
package manifest
4+
5+
func ensureHomeIfIAmStatic() error {
6+
return nil
7+
}

0 commit comments

Comments
 (0)