mirror of https://github.com/docker/cli.git
feature: subcommand `docker manifest ls`
Signed-off-by: Wayne Cheng <zhengwei@tiduyun.com>
This commit is contained in:
parent
4b06a93c5e
commit
6bff05f02f
|
@ -28,6 +28,7 @@ func NewManifestCommand(dockerCli command.Cli) *cobra.Command {
|
|||
newAnnotateCommand(dockerCli),
|
||||
newPushListCommand(dockerCli),
|
||||
newRmManifestListCommand(dockerCli),
|
||||
newListCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package manifest
|
||||
|
||||
import (
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultManifestListQuietFormat = "{{.Name}}"
|
||||
defaultManifestListTableFormat = "table {{.Repository}}\t{{.Tag}}"
|
||||
|
||||
repositoryHeader = "REPOSITORY"
|
||||
tagHeader = "TAG"
|
||||
)
|
||||
|
||||
// NewFormat returns a Format for rendering using a manifest list Context
|
||||
func NewFormat(source string, quiet bool) formatter.Format {
|
||||
switch source {
|
||||
case formatter.TableFormatKey:
|
||||
if quiet {
|
||||
return defaultManifestListQuietFormat
|
||||
}
|
||||
return defaultManifestListTableFormat
|
||||
case formatter.RawFormatKey:
|
||||
if quiet {
|
||||
return `name: {{.Name}}`
|
||||
}
|
||||
return `repo: {{.Repository}}\ntag: {{.Tag}}\n`
|
||||
}
|
||||
return formatter.Format(source)
|
||||
}
|
||||
|
||||
// FormatWrite writes formatted manifestLists using the Context
|
||||
func FormatWrite(ctx formatter.Context, manifestLists []reference.Reference) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, manifestList := range manifestLists {
|
||||
if n, ok := manifestList.(reference.Named); ok {
|
||||
if nt, ok := n.(reference.NamedTagged); ok {
|
||||
if err := format(&manifestListContext{
|
||||
name: reference.FamiliarString(manifestList),
|
||||
repo: reference.FamiliarName(nt),
|
||||
tag: nt.Tag(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(newManifestListContext(), render)
|
||||
}
|
||||
|
||||
type manifestListContext struct {
|
||||
formatter.HeaderContext
|
||||
name string
|
||||
repo string
|
||||
tag string
|
||||
}
|
||||
|
||||
func newManifestListContext() *manifestListContext {
|
||||
manifestListCtx := manifestListContext{}
|
||||
manifestListCtx.Header = formatter.SubHeaderContext{
|
||||
"Name": formatter.NameHeader,
|
||||
"Repository": repositoryHeader,
|
||||
"Tag": tagHeader,
|
||||
}
|
||||
return &manifestListCtx
|
||||
}
|
||||
|
||||
func (c *manifestListContext) MarshalJSON() ([]byte, error) {
|
||||
return formatter.MarshalJSON(c)
|
||||
}
|
||||
|
||||
func (c *manifestListContext) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *manifestListContext) Repository() string {
|
||||
return c.repo
|
||||
}
|
||||
|
||||
func (c *manifestListContext) Tag() string {
|
||||
return c.tag
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package manifest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type listOptions struct {
|
||||
quiet bool
|
||||
format string
|
||||
}
|
||||
|
||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var options listOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ls [OPTIONS]",
|
||||
Aliases: []string{"list"},
|
||||
Short: "List local manifest lists",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show manifest list NAMEs")
|
||||
flags.StringVar(&options.format, "format", "", flagsHelper.FormatHelp)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runList(ctx context.Context, dockerCli command.Cli, options listOptions) error {
|
||||
|
||||
manifestStore := dockerCli.ManifestStore()
|
||||
|
||||
var manifestLists []reference.Reference
|
||||
|
||||
manifestLists, searchErr := manifestStore.List()
|
||||
if searchErr != nil {
|
||||
return errors.New(searchErr.Error())
|
||||
}
|
||||
|
||||
format := options.format
|
||||
if len(format) == 0 {
|
||||
if len(dockerCli.ConfigFile().ManifestListsFormat) > 0 && !options.quiet {
|
||||
format = dockerCli.ConfigFile().ManifestListsFormat
|
||||
} else {
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
}
|
||||
|
||||
manifestListsCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: NewFormat(format, options.quiet),
|
||||
}
|
||||
sort.Slice(manifestLists, func(i, j int) bool {
|
||||
return sortorder.NaturalLess(manifestLists[i].String(), manifestLists[j].String())
|
||||
})
|
||||
return FormatWrite(manifestListsCtx, manifestLists)
|
||||
}
|
|
@ -24,6 +24,7 @@ type ConfigFile struct {
|
|||
PluginsFormat string `json:"pluginsFormat,omitempty"`
|
||||
VolumesFormat string `json:"volumesFormat,omitempty"`
|
||||
StatsFormat string `json:"statsFormat,omitempty"`
|
||||
ManifestListsFormat string `json:"manifestListsFormat,omitempty"`
|
||||
DetachKeys string `json:"detachKeys,omitempty"`
|
||||
CredentialsStore string `json:"credsStore,omitempty"`
|
||||
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||
|
|
|
@ -21,6 +21,7 @@ type Store interface {
|
|||
Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error)
|
||||
GetList(listRef reference.Reference) ([]types.ImageManifest, error)
|
||||
Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error
|
||||
List() ([]reference.Reference, error)
|
||||
}
|
||||
|
||||
// fsStore manages manifest files stored on the local filesystem
|
||||
|
@ -86,6 +87,26 @@ func (s *fsStore) getFromFilename(ref reference.Reference, filename string) (typ
|
|||
return manifestInfo.ImageManifest, nil
|
||||
}
|
||||
|
||||
// List returns the local manifest lists for a transaction
|
||||
func (s *fsStore) List() ([]reference.Reference, error) {
|
||||
fileInfos, err := os.ReadDir(s.root)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return nil, nil
|
||||
case err != nil:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listRefs := make([]reference.Reference, 0, len(fileInfos))
|
||||
for _, info := range fileInfos {
|
||||
refString := filenameToRefString(info.Name())
|
||||
if listRef, err := reference.Parse(refString); err == nil {
|
||||
listRefs = append(listRefs, listRef)
|
||||
}
|
||||
}
|
||||
return listRefs, nil
|
||||
}
|
||||
|
||||
// GetList returns all the local manifests for a transaction
|
||||
func (s *fsStore) GetList(listRef reference.Reference) ([]types.ImageManifest, error) {
|
||||
filenames, err := s.listManifests(listRef.String())
|
||||
|
@ -149,8 +170,13 @@ func manifestToFilename(root, manifestList, manifest string) string {
|
|||
}
|
||||
|
||||
func makeFilesafeName(ref string) string {
|
||||
fileName := strings.ReplaceAll(ref, ":", "-")
|
||||
return strings.ReplaceAll(fileName, "/", "_")
|
||||
fileName := strings.ReplaceAll(ref, ":", "--")
|
||||
return strings.ReplaceAll(fileName, "/", "__")
|
||||
}
|
||||
|
||||
func filenameToRefString(filename string) string {
|
||||
refString := strings.ReplaceAll(filename, "--", ":")
|
||||
return strings.ReplaceAll(refString, "__", "/")
|
||||
}
|
||||
|
||||
type notFoundError struct {
|
||||
|
|
Loading…
Reference in New Issue