Skip to content

Commit 42b0542

Browse files
committed
Add manifest command
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 <[email protected]>
1 parent f3cb13c commit 42b0542

File tree

141 files changed

+19148
-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

+19148
-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: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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/spf13/cobra"
12+
)
13+
14+
type annotateOptions struct {
15+
target string // the target manifest list name (also transaction ID)
16+
image string // the manifest to annotate within the list
17+
variant string // an architecture variant
18+
os string
19+
arch string
20+
cpuFeatures []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.cpuFeatures, "cpuFeatures", []string{}, "Add feature info to a manifest before pushing it.")
44+
flags.StringSliceVar(&opts.osFeatures, "osFeatures", []string{}, "Add feature info to a manifest before pushing it.")
45+
flags.StringVar(&opts.variant, "variant", "", "Add arch variant to a manifest before pushing it.")
46+
47+
return cmd
48+
}
49+
50+
func runManifestAnnotate(dockerCli *command.DockerCli, opts annotateOptions) error {
51+
52+
// Make sure the manifests are pulled, find the file you need, unmarshal the json, edit the file, and done.
53+
targetRef, err := reference.ParseNormalizedNamed(opts.target)
54+
if err != nil {
55+
return fmt.Errorf("Annotate: Error parsing name for manifest list (%s): %s", opts.target, err)
56+
}
57+
imgRef, err := reference.ParseNormalizedNamed(opts.image)
58+
if err != nil {
59+
return fmt.Errorf("Annotate: Error prasing name for manifest (%s): %s:", opts.image, err)
60+
}
61+
62+
// Make sure we've got tags or digests:
63+
if _, isDigested := targetRef.(reference.Canonical); !isDigested {
64+
targetRef = reference.TagNameOnly(targetRef)
65+
}
66+
if _, isDigested := imgRef.(reference.Canonical); !isDigested {
67+
imgRef = reference.TagNameOnly(imgRef)
68+
}
69+
transactionID := makeFilesafeName(targetRef.String())
70+
imgID := makeFilesafeName(imgRef.String())
71+
logrus.Debugf("Beginning annotate for %s/%s", transactionID, imgID)
72+
73+
imgInspect, _, err := getImageData(dockerCli, imgRef.String(), targetRef.String(), false)
74+
if err != nil {
75+
return err
76+
}
77+
78+
if len(imgInspect) > 1 {
79+
return fmt.Errorf("Cannot annotate manifest list. Please pass an image (not list) name")
80+
}
81+
82+
mf := imgInspect[0]
83+
84+
newMf, err := unmarshalIntoManifestInspect(imgID, transactionID)
85+
if err != nil {
86+
return err
87+
}
88+
89+
// Update the mf
90+
if opts.os != "" {
91+
newMf.OS = opts.os
92+
}
93+
if opts.arch != "" {
94+
newMf.Architecture = opts.arch
95+
}
96+
for _, cpuFeature := range opts.cpuFeatures {
97+
newMf.Features = appendIfUnique(mf.Features, cpuFeature)
98+
}
99+
for _, osFeature := range opts.osFeatures {
100+
newMf.OSFeatures = appendIfUnique(mf.OSFeatures, osFeature)
101+
}
102+
if opts.variant != "" {
103+
newMf.Variant = opts.variant
104+
}
105+
106+
// validate os/arch input
107+
if !isValidOSArch(newMf.OS, newMf.Architecture) {
108+
return fmt.Errorf("Manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
109+
}
110+
// @TODO
111+
// dgst := digest.FromBytes(b) can't use b/c not of the json.
112+
113+
if err := updateMfFile(newMf, imgID, transactionID); err != nil {
114+
return err
115+
}
116+
117+
logrus.Debugf("Annotated %s with options %v", mf.RefName, opts)
118+
return nil
119+
}
120+
func appendIfUnique(list []string, str string) []string {
121+
for _, s := range list {
122+
if s == str {
123+
return list
124+
}
125+
}
126+
return append(list, str)
127+
}

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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package manifest
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Sirupsen/logrus"
7+
"github.com/spf13/cobra"
8+
9+
"github.com/docker/cli/cli"
10+
"github.com/docker/cli/cli/command"
11+
"github.com/docker/distribution/reference"
12+
"github.com/docker/docker/registry"
13+
)
14+
15+
type annotateOpts struct {
16+
amend bool
17+
}
18+
19+
func newCreateListCommand(dockerCli *command.DockerCli) *cobra.Command {
20+
21+
opts := annotateOpts{}
22+
23+
cmd := &cobra.Command{
24+
Use: "create newRef manifest [manifest...]",
25+
Short: "Create a local manifest list for annotating and pushing to a registry",
26+
Args: cli.RequiresMinArgs(2),
27+
RunE: func(cmd *cobra.Command, args []string) error {
28+
return createManifestList(dockerCli, args, opts)
29+
},
30+
}
31+
32+
flags := cmd.Flags()
33+
flags.BoolVarP(&opts.amend, "amend", "a", false, "Amend an existing manifest list transaction")
34+
return cmd
35+
}
36+
37+
func createManifestList(dockerCli *command.DockerCli, args []string, opts annotateOpts) error {
38+
39+
// Just do some basic verification here, and leave the rest for when the user pushes the list
40+
newRef := args[0]
41+
targetRef, err := reference.ParseNormalizedNamed(newRef)
42+
if err != nil {
43+
return fmt.Errorf("Error parsing name for manifest list (%s): %v", newRef, err)
44+
}
45+
_, err = registry.ParseRepositoryInfo(targetRef)
46+
if err != nil {
47+
return fmt.Errorf("Error parsing repository name for manifest list (%s): %v", newRef, err)
48+
}
49+
50+
// Check locally for this list transaction before proceeding
51+
if _, isDigested := targetRef.(reference.Canonical); !isDigested {
52+
targetRef = reference.TagNameOnly(targetRef)
53+
}
54+
manifestFiles, err := getListFilenames(makeFilesafeName(targetRef.String()))
55+
if err != nil {
56+
return err
57+
}
58+
if len(manifestFiles) > 0 && !opts.amend {
59+
return fmt.Errorf("Refusing to continue over an existing manifest list transaction with no --amend flag")
60+
}
61+
62+
// Now create the local manifest list transaction by looking up the manifest schemas
63+
// for the constituent images:
64+
manifests := args[1:]
65+
logrus.Info("Retrieving digests of images...")
66+
for _, manifestRef := range manifests {
67+
68+
mfstData, _, err := getImageData(dockerCli, manifestRef, targetRef.String(), false)
69+
if err != nil {
70+
return err
71+
}
72+
73+
if len(mfstData) > 1 {
74+
// too many responses--can only happen if a manifest list was returned for the name lookup
75+
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.")
76+
}
77+
78+
}
79+
return nil
80+
}
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)