mirror of
https://github.com/containers/podman.git
synced 2025-09-24 23:35:27 +08:00
image lookup: do not match *any* tags
For reasons buried in the history of Podman, looking up an untagged image would match any tag of matching image. For instance, looking up centos would match a local image centos:foobar. Change that behavior to only match the latest tag. Fix: #11964 Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
7
vendor/github.com/containers/common/libimage/image.go
generated
vendored
7
vendor/github.com/containers/common/libimage/image.go
generated
vendored
@ -624,8 +624,7 @@ func (i *Image) NamedRepoTags() ([]reference.Named, error) {
|
||||
}
|
||||
|
||||
// inRepoTags looks for the specified name/tag pair in the image's repo tags.
|
||||
// Note that tag may be empty.
|
||||
func (i *Image) inRepoTags(name, tag string) (reference.Named, error) {
|
||||
func (i *Image) inRepoTags(namedTagged reference.NamedTagged) (reference.Named, error) {
|
||||
repoTags, err := i.NamedRepoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -636,8 +635,10 @@ func (i *Image) inRepoTags(name, tag string) (reference.Named, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := namedTagged.Name()
|
||||
tag := namedTagged.Tag()
|
||||
for _, pair := range pairs {
|
||||
if tag != "" && tag != pair.Tag {
|
||||
if tag != pair.Tag {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(pair.Name, name) {
|
||||
|
42
vendor/github.com/containers/common/libimage/runtime.go
generated
vendored
42
vendor/github.com/containers/common/libimage/runtime.go
generated
vendored
@ -389,16 +389,17 @@ func (r *Runtime) lookupImageInDigestsAndRepoTags(name string, options *LookupIm
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if !shortnames.IsShortName(name) {
|
||||
named, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
digested, hasDigest := named.(reference.Digested)
|
||||
if !hasDigest {
|
||||
return nil, "", errors.Wrap(storage.ErrImageUnknown, name)
|
||||
}
|
||||
ref, err := reference.Parse(name) // Warning! This is not ParseNormalizedNamed
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
named, isNamed := ref.(reference.Named)
|
||||
if !isNamed {
|
||||
return nil, "", errors.Wrap(storage.ErrImageUnknown, name)
|
||||
}
|
||||
|
||||
digested, isDigested := named.(reference.Digested)
|
||||
if isDigested {
|
||||
logrus.Debug("Looking for image with matching recorded digests")
|
||||
digest := digested.Digest()
|
||||
for _, image := range allImages {
|
||||
@ -408,22 +409,23 @@ func (r *Runtime) lookupImageInDigestsAndRepoTags(name string, options *LookupIm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, "", errors.Wrap(storage.ErrImageUnknown, name)
|
||||
}
|
||||
|
||||
// Podman compat: if we're looking for a short name but couldn't
|
||||
// resolve it via the registries.conf dance, we need to look at *all*
|
||||
// images and check if the name we're looking for matches a repo tag.
|
||||
// Split the name into a repo/tag pair
|
||||
split := strings.SplitN(name, ":", 2)
|
||||
repo := split[0]
|
||||
tag := ""
|
||||
if len(split) == 2 {
|
||||
tag = split[1]
|
||||
if !shortnames.IsShortName(name) {
|
||||
return nil, "", errors.Wrap(storage.ErrImageUnknown, name)
|
||||
}
|
||||
|
||||
named = reference.TagNameOnly(named) // Make sure to add ":latest" if needed
|
||||
namedTagged, isNammedTagged := named.(reference.NamedTagged)
|
||||
if !isNammedTagged {
|
||||
// NOTE: this should never happen since we already know it's
|
||||
// not a digested reference.
|
||||
return nil, "", fmt.Errorf("%s: %w (could not cast to tagged)", name, storage.ErrImageUnknown)
|
||||
}
|
||||
|
||||
for _, image := range allImages {
|
||||
named, err := image.inRepoTags(repo, tag)
|
||||
named, err := image.inRepoTags(namedTagged)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
6
vendor/github.com/containers/common/pkg/config/config.go
generated
vendored
6
vendor/github.com/containers/common/pkg/config/config.go
generated
vendored
@ -215,6 +215,12 @@ type EngineConfig struct {
|
||||
// The first path pointing to a valid file will be used.
|
||||
ConmonPath []string `toml:"conmon_path,omitempty"`
|
||||
|
||||
// CompatAPIEnforceDockerHub enforces using docker.io for completing
|
||||
// short names in Podman's compatibility REST API. Note that this will
|
||||
// ignore unqualified-search-registries and short-name aliases defined
|
||||
// in containers-registries.conf(5).
|
||||
CompatAPIEnforceDockerHub bool `toml:"compat_api_enforce_docker_hub,omitempty"`
|
||||
|
||||
// DetachKeys is the sequence of keys used to detach a container.
|
||||
DetachKeys string `toml:"detach_keys,omitempty"`
|
||||
|
||||
|
5
vendor/github.com/containers/common/pkg/config/containers.conf
generated
vendored
5
vendor/github.com/containers/common/pkg/config/containers.conf
generated
vendored
@ -317,6 +317,11 @@ default_sysctls = [
|
||||
# "/usr/local/sbin/conmon"
|
||||
#]
|
||||
|
||||
# Enforces using docker.io for completing short names in Podman's compatibility
|
||||
# REST API. Note that this will ignore unqualified-search-registries and
|
||||
# short-name aliases defined in containers-registries.conf(5).
|
||||
#compat_api_enforce_docker_hub = true
|
||||
|
||||
# Specify the keys sequence used to detach a container.
|
||||
# Format is a single character [a-Z] or a comma separated sequence of
|
||||
# `ctrl-<value>`, where `<value>` is one of:
|
||||
|
5
vendor/github.com/containers/common/pkg/config/default.go
generated
vendored
5
vendor/github.com/containers/common/pkg/config/default.go
generated
vendored
@ -190,6 +190,7 @@ func DefaultConfig() (*Config, error) {
|
||||
IPCNS: "private",
|
||||
LogDriver: defaultLogDriver(),
|
||||
LogSizeMax: DefaultLogSizeMax,
|
||||
NetNS: "private",
|
||||
NoHosts: false,
|
||||
PidsLimit: DefaultPidsLimit,
|
||||
PidNS: "private",
|
||||
@ -225,7 +226,7 @@ func defaultSecretConfig() SecretConfig {
|
||||
func defaultMachineConfig() MachineConfig {
|
||||
return MachineConfig{
|
||||
CPUs: 1,
|
||||
DiskSize: 10,
|
||||
DiskSize: 100,
|
||||
Image: "testing",
|
||||
Memory: 2048,
|
||||
}
|
||||
@ -243,6 +244,8 @@ func defaultConfigFromMemory() (*EngineConfig, error) {
|
||||
|
||||
c.EventsLogFilePath = filepath.Join(c.TmpDir, "events", "events.log")
|
||||
|
||||
c.CompatAPIEnforceDockerHub = true
|
||||
|
||||
if path, ok := os.LookupEnv("CONTAINERS_STORAGE_CONF"); ok {
|
||||
types.SetDefaultConfigFilePath(path)
|
||||
}
|
||||
|
49
vendor/github.com/containers/common/pkg/report/doc.go
generated
vendored
49
vendor/github.com/containers/common/pkg/report/doc.go
generated
vendored
@ -3,34 +3,44 @@ Package report provides helper structs/methods/funcs for formatting output
|
||||
|
||||
To format output for an array of structs:
|
||||
|
||||
w := report.NewWriterDefault(os.Stdout)
|
||||
defer w.Flush()
|
||||
|
||||
ExamplePodman:
|
||||
headers := report.Headers(struct {
|
||||
ID string
|
||||
}{}, nil)
|
||||
t, _ := report.NewTemplate("command name").Parse("{{range .}}{{.ID}}{{end}}")
|
||||
t.Execute(t, headers)
|
||||
t.Execute(t, map[string]string{
|
||||
|
||||
f := report.New(os.Stdout, "Command Name")
|
||||
f, _ := f.Parse(report.OriginPodman, "{{range .}}{{.ID}}{{end}}")
|
||||
defer f.Flush()
|
||||
|
||||
if f.RenderHeaders {
|
||||
f.Execute(headers)
|
||||
}
|
||||
f.Execute( map[string]string{
|
||||
"ID":"fa85da03b40141899f3af3de6d27852b",
|
||||
})
|
||||
// t.IsTable() == false
|
||||
|
||||
or
|
||||
|
||||
w := report.NewWriterDefault(os.Stdout)
|
||||
defer w.Flush()
|
||||
// Output:
|
||||
// ID
|
||||
// fa85da03b40141899f3af3de6d27852b
|
||||
|
||||
ExampleUser:
|
||||
headers := report.Headers(struct {
|
||||
CID string
|
||||
}{}, map[string]string{
|
||||
"CID":"ID"})
|
||||
t, _ := report.NewTemplate("command name").Parse("table {{.CID}}")
|
||||
t.Execute(t, headers)
|
||||
}{}, map[string]string{"CID":"ID"})
|
||||
|
||||
f, _ := report.New(os.Stdout, "Command Name").Parse(report.OriginUser, "table {{.CID}}")
|
||||
defer f.Flush()
|
||||
|
||||
if f.RenderHeaders {
|
||||
t.Execute(t, headers)
|
||||
}
|
||||
t.Execute(t,map[string]string{
|
||||
"CID":"fa85da03b40141899f3af3de6d27852b",
|
||||
})
|
||||
// t.IsTable() == true
|
||||
|
||||
// Output:
|
||||
// ID
|
||||
// fa85da03b40141899f3af3de6d27852b
|
||||
|
||||
Helpers:
|
||||
|
||||
@ -38,13 +48,20 @@ Helpers:
|
||||
... process JSON and output
|
||||
}
|
||||
|
||||
if report.HasTable(cmd.Flag("format").Value.String()) {
|
||||
... "table" keyword prefix in format text
|
||||
}
|
||||
|
||||
Template Functions:
|
||||
|
||||
The following template functions are added to the template when parsed:
|
||||
- join strings.Join, {{join .Field separator}}
|
||||
- json encode field as JSON {{ json .Field }}
|
||||
- lower strings.ToLower {{ .Field | lower }}
|
||||
- pad add spaces as prefix and suffix {{ pad . 2 2 }}
|
||||
- split strings.Split {{ .Field | split }}
|
||||
- title strings.Title {{ .Field | title }}
|
||||
- truncate limit field length {{ truncate . 10 }}
|
||||
- upper strings.ToUpper {{ .Field | upper }}
|
||||
|
||||
report.Funcs() may be used to add additional template functions.
|
||||
|
151
vendor/github.com/containers/common/pkg/report/formatter.go
generated
vendored
Normal file
151
vendor/github.com/containers/common/pkg/report/formatter.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Flusher is the interface that wraps the Flush method.
|
||||
type Flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
// NopFlusher represents a type which flush operation is nop.
|
||||
type NopFlusher struct{}
|
||||
|
||||
// Flush is a nop operation.
|
||||
func (f *NopFlusher) Flush() (err error) { return }
|
||||
|
||||
type Origin int
|
||||
|
||||
const (
|
||||
OriginUnknown Origin = iota
|
||||
OriginPodman
|
||||
OriginUser
|
||||
)
|
||||
|
||||
func (o Origin) String() string {
|
||||
switch o {
|
||||
case OriginPodman:
|
||||
return "OriginPodman"
|
||||
case OriginUser:
|
||||
return "OriginUser"
|
||||
default:
|
||||
return "OriginUnknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Formatter holds the configured Writer and parsed Template, additional state fields are
|
||||
// maintained to assist in the podman command report writing.
|
||||
type Formatter struct {
|
||||
Origin Origin // Source of go template. OriginUser or OriginPodman
|
||||
RenderHeaders bool // Hint, default behavior for given template is to include headers
|
||||
RenderTable bool // Does template have "table" keyword
|
||||
flusher Flusher // Flush any buffered formatted output
|
||||
template *template.Template // Go text/template for formatting output
|
||||
text string // value of canonical template after processing
|
||||
writer io.Writer // Destination for formatted output
|
||||
}
|
||||
|
||||
// Parse parses golang template returning a formatter
|
||||
//
|
||||
// - OriginPodman implies text is a template from podman code. Output will
|
||||
// be filtered through a tabwriter.
|
||||
//
|
||||
// - OriginUser implies text is a template from a user. If template includes
|
||||
// keyword "table" output will be filtered through a tabwriter.
|
||||
func (f *Formatter) Parse(origin Origin, text string) (*Formatter, error) {
|
||||
f.Origin = origin
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(text, "table "):
|
||||
f.RenderTable = true
|
||||
text = "{{range .}}" + NormalizeFormat(text) + "{{end -}}"
|
||||
case OriginUser == origin:
|
||||
text = EnforceRange(NormalizeFormat(text))
|
||||
default:
|
||||
text = NormalizeFormat(text)
|
||||
}
|
||||
f.text = text
|
||||
|
||||
if f.RenderTable || origin == OriginPodman {
|
||||
tw := tabwriter.NewWriter(f.writer, 12, 2, 2, ' ', tabwriter.StripEscape)
|
||||
f.writer = tw
|
||||
f.flusher = tw
|
||||
f.RenderHeaders = true
|
||||
}
|
||||
|
||||
tmpl, err := f.template.Funcs(template.FuncMap(DefaultFuncs)).Parse(text)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
f.template = tmpl
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Funcs adds the elements of the argument map to the template's function map.
|
||||
// A default template function will be replaced if there is a key collision.
|
||||
func (f *Formatter) Funcs(funcMap template.FuncMap) *Formatter {
|
||||
m := make(template.FuncMap, len(DefaultFuncs)+len(funcMap))
|
||||
for k, v := range DefaultFuncs {
|
||||
m[k] = v
|
||||
}
|
||||
for k, v := range funcMap {
|
||||
m[k] = v
|
||||
}
|
||||
f.template = f.template.Funcs(funcMap)
|
||||
return f
|
||||
}
|
||||
|
||||
// Init either resets the given tabwriter with new values or wraps w in tabwriter with given values
|
||||
func (f *Formatter) Init(w io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Formatter {
|
||||
flags |= tabwriter.StripEscape
|
||||
|
||||
if tw, ok := f.writer.(*tabwriter.Writer); ok {
|
||||
tw = tw.Init(w, minwidth, tabwidth, padding, padchar, flags)
|
||||
f.writer = tw
|
||||
f.flusher = tw
|
||||
} else {
|
||||
tw = tabwriter.NewWriter(w, minwidth, tabwidth, padding, padchar, flags)
|
||||
f.writer = tw
|
||||
f.flusher = tw
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Execute applies a parsed template to the specified data object,
|
||||
// and writes the output to Formatter.Writer.
|
||||
func (f *Formatter) Execute(data interface{}) error {
|
||||
return f.template.Execute(f.writer, data)
|
||||
}
|
||||
|
||||
// Flush should be called after the last call to Write to ensure
|
||||
// that any data buffered in the Formatter is written to output. Any
|
||||
// incomplete escape sequence at the end is considered
|
||||
// complete for formatting purposes.
|
||||
func (f *Formatter) Flush() error {
|
||||
// Indirection is required here to prevent caller from having to know when
|
||||
// value of Flusher may be changed.
|
||||
return f.flusher.Flush()
|
||||
}
|
||||
|
||||
// Writer returns the embedded io.Writer from Formatter
|
||||
func (f *Formatter) Writer() io.Writer {
|
||||
return f.writer
|
||||
}
|
||||
|
||||
// New allocates a new, undefined Formatter with the given name and Writer
|
||||
func New(output io.Writer, name string) *Formatter {
|
||||
f := new(Formatter)
|
||||
|
||||
f.flusher = new(NopFlusher)
|
||||
if flusher, ok := output.(Flusher); ok {
|
||||
f.flusher = flusher
|
||||
}
|
||||
|
||||
f.template = template.New(name)
|
||||
f.writer = output
|
||||
return f
|
||||
}
|
6
vendor/github.com/containers/common/pkg/report/template.go
generated
vendored
6
vendor/github.com/containers/common/pkg/report/template.go
generated
vendored
@ -25,17 +25,19 @@ var tableReplacer = strings.NewReplacer(
|
||||
"table ", "",
|
||||
`\t`, "\t",
|
||||
" ", "\t",
|
||||
`\n`, "\n",
|
||||
)
|
||||
|
||||
// escapedReplacer will clean up escaped characters from CLI
|
||||
var escapedReplacer = strings.NewReplacer(
|
||||
`\t`, "\t",
|
||||
`\n`, "\n",
|
||||
)
|
||||
|
||||
var DefaultFuncs = FuncMap{
|
||||
"join": strings.Join,
|
||||
"json": func(v interface{}) string {
|
||||
buf := &bytes.Buffer{}
|
||||
buf := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.Encode(v)
|
||||
@ -157,7 +159,7 @@ func (t *Template) IsTable() bool {
|
||||
return t.isTable
|
||||
}
|
||||
|
||||
var rangeRegex = regexp.MustCompile(`{{\s*range\s*\.\s*}}.*{{\s*end\s*-?\s*}}`)
|
||||
var rangeRegex = regexp.MustCompile(`(?s){{\s*range\s*\.\s*}}.*{{\s*end\s*-?\s*}}`)
|
||||
|
||||
// EnforceRange ensures that the format string contains a range
|
||||
func EnforceRange(format string) string {
|
||||
|
Reference in New Issue
Block a user