mirror of
https://github.com/containers/podman.git
synced 2025-12-08 14:48:48 +08:00
short-name aliasing
Add support for short-name aliasing. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
17
vendor/github.com/containers/image/v5/copy/copy.go
generated
vendored
17
vendor/github.com/containers/image/v5/copy/copy.go
generated
vendored
@@ -1166,6 +1166,21 @@ func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc)
|
||||
return digest.Canonical.FromReader(stream)
|
||||
}
|
||||
|
||||
// errorAnnotationReader wraps the io.Reader passed to PutBlob for annotating the error happened during read.
|
||||
// These errors are reported as PutBlob errors, so we would otherwise misleadingly attribute them to the copy destination.
|
||||
type errorAnnotationReader struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// Read annotates the error happened during read
|
||||
func (r errorAnnotationReader) Read(b []byte) (n int, err error) {
|
||||
n, err = r.reader.Read(b)
|
||||
if err != io.EOF {
|
||||
return n, errors.Wrapf(err, "error happened during read")
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// copyBlobFromStream copies a blob with srcInfo (with known Digest and Annotations and possibly known Size) from srcStream to dest,
|
||||
// perhaps sending a copy to an io.Writer if getOriginalLayerCopyWriter != nil,
|
||||
// perhaps compressing it if canCompress,
|
||||
@@ -1335,7 +1350,7 @@ func (c *copier) copyBlobFromStream(ctx context.Context, srcStream io.Reader, sr
|
||||
}
|
||||
|
||||
// === Finally, send the layer stream to dest.
|
||||
uploadedInfo, err := c.dest.PutBlob(ctx, destStream, inputInfo, c.blobInfoCache, isConfig)
|
||||
uploadedInfo, err := c.dest.PutBlob(ctx, &errorAnnotationReader{destStream}, inputInfo, c.blobInfoCache, isConfig)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, errors.Wrap(err, "Error writing blob")
|
||||
}
|
||||
|
||||
47
vendor/github.com/containers/image/v5/docker/docker_image.go
generated
vendored
47
vendor/github.com/containers/image/v5/docker/docker_image.go
generated
vendored
@@ -4,12 +4,15 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -103,3 +106,47 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types.
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// GetDigest returns the image's digest
|
||||
// Use this to optimize and avoid use of an ImageSource based on the returned digest;
|
||||
// if you are going to use an ImageSource anyway, it’s more efficient to create it first
|
||||
// and compute the digest from the value returned by GetManifest.
|
||||
// NOTE: Implemented to avoid Docker Hub API limits, and mirror configuration may be
|
||||
// ignored (but may be implemented in the future)
|
||||
func GetDigest(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) (digest.Digest, error) {
|
||||
dr, ok := ref.(dockerReference)
|
||||
if !ok {
|
||||
return "", errors.Errorf("ref must be a dockerReference")
|
||||
}
|
||||
|
||||
tagOrDigest, err := dr.tagOrDigest()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
client, err := newDockerClientFromRef(sys, dr, false, "pull")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create client")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(dr.ref), tagOrDigest)
|
||||
headers := map[string][]string{
|
||||
"Accept": manifest.DefaultRequestedManifestMIMETypes,
|
||||
}
|
||||
|
||||
res, err := client.makeRequest(ctx, "HEAD", path, headers, nil, v2Auth, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return "", errors.Wrapf(registryHTTPResponseToError(res), "Error reading digest %s in %s", tagOrDigest, dr.ref.Name())
|
||||
}
|
||||
|
||||
dig, err := digest.Parse(res.Header.Get("Docker-Content-Digest"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dig, nil
|
||||
}
|
||||
|
||||
458
vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go
generated
vendored
Normal file
458
vendor/github.com/containers/image/v5/pkg/shortnames/shortnames.go
generated
vendored
Normal file
@@ -0,0 +1,458 @@
|
||||
package shortnames
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/pkg/sysregistriesv2"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// IsShortName returns true if the specified input is a "short name". A "short
|
||||
// name" refers to a container image without a fully-qualified reference, and
|
||||
// is hence missing a registry (or domain). Names including a digest are not
|
||||
// short names.
|
||||
//
|
||||
// Examples:
|
||||
// * short names: "image:tag", "library/fedora"
|
||||
// * not short names: "quay.io/image", "localhost/image:tag",
|
||||
// "server.org:5000/lib/image", "image@sha256:..."
|
||||
func IsShortName(input string) bool {
|
||||
isShort, _, _ := parseUnnormalizedShortName(input)
|
||||
return isShort
|
||||
}
|
||||
|
||||
// parseUnnormalizedShortName parses the input and returns if it's short name,
|
||||
// the unnormalized reference.Named, and a parsing error.
|
||||
func parseUnnormalizedShortName(input string) (bool, reference.Named, error) {
|
||||
ref, err := reference.Parse(input)
|
||||
if err != nil {
|
||||
return false, nil, errors.Wrapf(err, "cannot parse input: %q", input)
|
||||
}
|
||||
|
||||
named, ok := ref.(reference.Named)
|
||||
if !ok {
|
||||
return true, nil, errors.Errorf("%q is not a named reference", input)
|
||||
}
|
||||
|
||||
registry := reference.Domain(named)
|
||||
if strings.ContainsAny(registry, ".:") || registry == "localhost" {
|
||||
// A final parse to make sure that docker.io references are correctly
|
||||
// normalized (e.g., docker.io/alpine to docker.io/library/alpine.
|
||||
named, err = reference.ParseNormalizedNamed(input)
|
||||
if err != nil {
|
||||
return false, nil, errors.Wrapf(err, "cannot normalize input: %q", input)
|
||||
}
|
||||
return false, named, nil
|
||||
}
|
||||
|
||||
return true, named, nil
|
||||
}
|
||||
|
||||
// splitUserInput parses the user-specified reference. Namely, it strips off
|
||||
// the tag or digest and stores it in the return values so that both can be
|
||||
// re-added to a possible resolved alias' or USRs at a later point.
|
||||
func splitUserInput(named reference.Named) (isTagged bool, isDigested bool, normalized reference.Named, tag string, digest digest.Digest) {
|
||||
normalized = named
|
||||
|
||||
tagged, isT := named.(reference.NamedTagged)
|
||||
if isT {
|
||||
isTagged = true
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
|
||||
digested, isD := named.(reference.Digested)
|
||||
if isD {
|
||||
isDigested = true
|
||||
digest = digested.Digest()
|
||||
}
|
||||
|
||||
// Strip off tag/digest if present.
|
||||
normalized = reference.TrimNamed(named)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Add records the specified name-value pair as a new short-name alias to the
|
||||
// user-specific aliases.conf. It may override an existing alias for `name`.
|
||||
func Add(ctx *types.SystemContext, name string, value reference.Named) error {
|
||||
isShort, _, err := parseUnnormalizedShortName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isShort {
|
||||
return errors.Errorf("%q is not a short name", name)
|
||||
}
|
||||
return sysregistriesv2.AddShortNameAlias(ctx, name, value.String())
|
||||
}
|
||||
|
||||
// Remove clears the short-name alias for the specified name. It throws an
|
||||
// error in case name does not exist in the machine-generated
|
||||
// short-name-alias.conf. In such case, the alias must be specified in one of
|
||||
// the registries.conf files, which is the users' responsibility.
|
||||
func Remove(ctx *types.SystemContext, name string) error {
|
||||
isShort, _, err := parseUnnormalizedShortName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isShort {
|
||||
return errors.Errorf("%q is not a short name", name)
|
||||
}
|
||||
return sysregistriesv2.RemoveShortNameAlias(ctx, name)
|
||||
}
|
||||
|
||||
// Resolved encapsulates all data for a resolved image name.
|
||||
type Resolved struct {
|
||||
PullCandidates []PullCandidate
|
||||
|
||||
userInput reference.Named
|
||||
systemContext *types.SystemContext
|
||||
rationale rationale
|
||||
originDescription string
|
||||
}
|
||||
|
||||
func (r *Resolved) addCandidate(named reference.Named) {
|
||||
r.PullCandidates = append(r.PullCandidates, PullCandidate{named, false, r})
|
||||
}
|
||||
|
||||
func (r *Resolved) addCandidateToRecord(named reference.Named) {
|
||||
r.PullCandidates = append(r.PullCandidates, PullCandidate{named, true, r})
|
||||
}
|
||||
|
||||
// Allows to reason over pull errors and add some context information.
|
||||
// Used in (*Resolved).WrapPullError.
|
||||
type rationale int
|
||||
|
||||
const (
|
||||
// No additional context.
|
||||
rationaleNone rationale = iota
|
||||
// Resolved value is a short-name alias.
|
||||
rationaleAlias
|
||||
// Resolved value has been completed with an Unqualified Search Registry.
|
||||
rationaleUSR
|
||||
// Resolved value has been selected by the user (via the prompt).
|
||||
rationaleUserSelection
|
||||
)
|
||||
|
||||
// Description returns a human-readable description about the resolution
|
||||
// process (e.g., short-name alias, unqualified-search registries, etc.).
|
||||
// It is meant to be printed before attempting to pull the pull candidates
|
||||
// to make the short-name resolution more transparent to user.
|
||||
//
|
||||
// If the returned string is empty, it is not meant to be printed.
|
||||
func (r *Resolved) Description() string {
|
||||
switch r.rationale {
|
||||
case rationaleAlias:
|
||||
return fmt.Sprintf("Resolved short name %q to a recorded short-name alias (origin: %s)", r.userInput, r.originDescription)
|
||||
case rationaleUSR:
|
||||
return fmt.Sprintf("Completed short name %q with unqualified-search registries (origin: %s)", r.userInput, r.originDescription)
|
||||
case rationaleUserSelection, rationaleNone:
|
||||
fallthrough
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// FormatPullErrors is a convenience function to format errors that occurred
|
||||
// while trying to pull all of the resolved pull candidates.
|
||||
//
|
||||
// Note that nil is returned if len(pullErrors) == 0. Otherwise, the amount of
|
||||
// pull errors must equal the amount of pull candidates.
|
||||
func (r *Resolved) FormatPullErrors(pullErrors []error) error {
|
||||
if len(pullErrors) >= 0 && len(pullErrors) != len(r.PullCandidates) {
|
||||
pullErrors = append(pullErrors,
|
||||
errors.Errorf("internal error: expected %d instead of %d errors for %d pull candidates",
|
||||
len(r.PullCandidates), len(pullErrors), len(r.PullCandidates)))
|
||||
}
|
||||
|
||||
switch len(pullErrors) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return pullErrors[0]
|
||||
default:
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("%d errors occurred while pulling:", len(pullErrors)))
|
||||
for _, e := range pullErrors {
|
||||
sb.WriteString("\n * ")
|
||||
sb.WriteString(e.Error())
|
||||
}
|
||||
return errors.New(sb.String())
|
||||
}
|
||||
}
|
||||
|
||||
// PullCandidate is a resolved name. Once the Value has been used
|
||||
// successfully, users MUST call `(*PullCandidate).Record(..)` to possibly
|
||||
// record it as a new short-name alias.
|
||||
type PullCandidate struct {
|
||||
// Fully-qualified reference with tag or digest.
|
||||
Value reference.Named
|
||||
// Control whether to record it permanently as an alias.
|
||||
record bool
|
||||
|
||||
// Backwards pointer to the Resolved "parent".
|
||||
resolved *Resolved
|
||||
}
|
||||
|
||||
// Record may store a short-name alias for the PullCandidate.
|
||||
func (c *PullCandidate) Record() error {
|
||||
if !c.record {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Strip off tags/digests from name/value.
|
||||
name := reference.TrimNamed(c.resolved.userInput)
|
||||
value := reference.TrimNamed(c.Value)
|
||||
|
||||
if err := Add(c.resolved.systemContext, name.String(), value); err != nil {
|
||||
return errors.Wrapf(err, "error recording short-name alias (%q=%q)", c.resolved.userInput, c.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve resolves the specified name to either one or more fully-qualified
|
||||
// image references that the short name may be *pulled* from. If the specified
|
||||
// name is already a fully-qualified reference (i.e., not a short name), it is
|
||||
// returned as is. In case, it's a short name, it's resolved according to the
|
||||
// ShortNameMode in the SystemContext (if specified) or in the registries.conf.
|
||||
//
|
||||
// Note that tags and digests are stripped from the specified name before
|
||||
// looking up an alias. Stripped off tags and digests are later on appended to
|
||||
// all candidates. If neither tag nor digest is specified, candidates are
|
||||
// normalized with the "latest" tag. PullCandidates in the returned value may
|
||||
// be empty if there is no matching alias and no unqualified-search registries
|
||||
// are configured.
|
||||
//
|
||||
// Note that callers *must* call `(PullCandidate).Record` after a returned
|
||||
// item has been pulled successfully; this callback will record a new
|
||||
// short-name alias (depending on the specified short-name mode).
|
||||
//
|
||||
// Furthermore, before attempting to pull callers *should* call
|
||||
// `(Resolved).Description` and afterwards use
|
||||
// `(Resolved).FormatPullErrors` in case of pull errors.
|
||||
func Resolve(ctx *types.SystemContext, name string) (*Resolved, error) {
|
||||
resolved := &Resolved{}
|
||||
|
||||
// Create a copy of the system context to make it usable beyond this
|
||||
// function call.
|
||||
var sys *types.SystemContext
|
||||
if ctx != nil {
|
||||
sys = &(*ctx)
|
||||
}
|
||||
resolved.systemContext = ctx
|
||||
|
||||
// Detect which mode we're running in.
|
||||
mode, err := sysregistriesv2.GetShortNameMode(sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sanity check the short-name mode.
|
||||
switch mode {
|
||||
case types.ShortNameModeDisabled, types.ShortNameModePermissive, types.ShortNameModeEnforcing:
|
||||
// We're good.
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported short-name mode (%v)", mode)
|
||||
}
|
||||
|
||||
isShort, shortRef, err := parseUnnormalizedShortName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isShort { // no short name
|
||||
named := reference.TagNameOnly(shortRef) // Make sure to add ":latest" if needed
|
||||
resolved.addCandidate(named)
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// Strip off the tag to normalize the short name for looking it up in
|
||||
// the config files.
|
||||
isTagged, isDigested, shortNameRepo, tag, digest := splitUserInput(shortRef)
|
||||
resolved.userInput = shortNameRepo
|
||||
|
||||
// If there's already an alias, use it.
|
||||
namedAlias, aliasOriginDescription, err := sysregistriesv2.ResolveShortNameAlias(sys, shortNameRepo.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Always use an alias if present.
|
||||
if namedAlias != nil {
|
||||
if isTagged {
|
||||
namedAlias, err = reference.WithTag(namedAlias, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if isDigested {
|
||||
namedAlias, err = reference.WithDigest(namedAlias, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Make sure to add ":latest" if needed
|
||||
namedAlias = reference.TagNameOnly(namedAlias)
|
||||
|
||||
resolved.addCandidate(namedAlias)
|
||||
resolved.rationale = rationaleAlias
|
||||
resolved.originDescription = aliasOriginDescription
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
resolved.rationale = rationaleUSR
|
||||
|
||||
// Query the registry for unqualified-search registries.
|
||||
unqualifiedSearchRegistries, usrConfig, err := sysregistriesv2.UnqualifiedSearchRegistriesWithOrigin(sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolved.originDescription = usrConfig
|
||||
|
||||
for _, reg := range unqualifiedSearchRegistries {
|
||||
named, err := reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", reg, name))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating reference with unqualified-search registry %q", reg)
|
||||
}
|
||||
// Make sure to add ":latest" if needed
|
||||
named = reference.TagNameOnly(named)
|
||||
|
||||
resolved.addCandidate(named)
|
||||
}
|
||||
|
||||
// If we're running in disabled, return the candidates without
|
||||
// prompting (and without recording).
|
||||
if mode == types.ShortNameModeDisabled {
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// If we have only one candidate, there's no ambiguity. In case of an
|
||||
// empty candidate slices, callers can implement custom logic or raise
|
||||
// an error.
|
||||
if len(resolved.PullCandidates) <= 1 {
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// If we don't have a TTY, act according to the mode.
|
||||
if !terminal.IsTerminal(int(os.Stdout.Fd())) || !terminal.IsTerminal(int(os.Stdin.Fd())) {
|
||||
switch mode {
|
||||
case types.ShortNameModePermissive:
|
||||
// Permissive falls back to using all candidates.
|
||||
return resolved, nil
|
||||
case types.ShortNameModeEnforcing:
|
||||
// Enforcing errors out without a prompt.
|
||||
return nil, errors.New("short-name resolution enforced but cannot prompt without a TTY")
|
||||
default:
|
||||
// We should not end up here.
|
||||
return nil, errors.Errorf("unexpected short-name mode (%v) during resolution", mode)
|
||||
}
|
||||
}
|
||||
|
||||
// We have a TTY, and can prompt the user with a selection of all
|
||||
// possible candidates.
|
||||
strCandidates := []string{}
|
||||
for _, candidate := range resolved.PullCandidates {
|
||||
strCandidates = append(strCandidates, candidate.Value.String())
|
||||
}
|
||||
prompt := promptui.Select{
|
||||
Label: "Please select an image",
|
||||
Items: strCandidates,
|
||||
HideHelp: true, // do not show navigation help
|
||||
}
|
||||
|
||||
_, selection, err := prompt.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
named, err := reference.ParseNormalizedNamed(selection)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "selection %q is not a valid reference", selection)
|
||||
}
|
||||
|
||||
resolved.PullCandidates = nil
|
||||
resolved.addCandidateToRecord(named)
|
||||
resolved.rationale = rationaleUserSelection
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// ResolveLocally resolves the specified name to either one or more local
|
||||
// images. If the specified name is already a fully-qualified reference (i.e.,
|
||||
// not a short name), it is returned as is. In case, it's a short name, the
|
||||
// returned slice of named references looks as follows:
|
||||
//
|
||||
// 1) If present, the short-name alias
|
||||
// 2) "localhost/" as used by many container engines such as Podman and Buildah
|
||||
// 3) Unqualified-search registries from the registries.conf files
|
||||
//
|
||||
// Note that tags and digests are stripped from the specified name before
|
||||
// looking up an alias. Stripped off tags and digests are later on appended to
|
||||
// all candidates. If neither tag nor digest is specified, candidates are
|
||||
// normalized with the "latest" tag. The returned slice contains at least one
|
||||
// item.
|
||||
func ResolveLocally(ctx *types.SystemContext, name string) ([]reference.Named, error) {
|
||||
isShort, shortRef, err := parseUnnormalizedShortName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isShort { // no short name
|
||||
named := reference.TagNameOnly(shortRef) // Make sure to add ":latest" if needed
|
||||
return []reference.Named{named}, nil
|
||||
}
|
||||
|
||||
var candidates []reference.Named
|
||||
|
||||
// Strip off the tag to normalize the short name for looking it up in
|
||||
// the config files.
|
||||
isTagged, isDigested, shortNameRepo, tag, digest := splitUserInput(shortRef)
|
||||
|
||||
// If there's already an alias, use it.
|
||||
namedAlias, _, err := sysregistriesv2.ResolveShortNameAlias(ctx, shortNameRepo.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if namedAlias != nil {
|
||||
if isTagged {
|
||||
namedAlias, err = reference.WithTag(namedAlias, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if isDigested {
|
||||
namedAlias, err = reference.WithDigest(namedAlias, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Make sure to add ":latest" if needed
|
||||
namedAlias = reference.TagNameOnly(namedAlias)
|
||||
|
||||
candidates = append(candidates, namedAlias)
|
||||
}
|
||||
|
||||
// Query the registry for unqualified-search registries.
|
||||
unqualifiedSearchRegistries, err := sysregistriesv2.UnqualifiedSearchRegistries(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Note that "localhost" has precedence over the unqualified-search registries.
|
||||
for _, reg := range append([]string{"localhost"}, unqualifiedSearchRegistries...) {
|
||||
named, err := reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", reg, name))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating reference with unqualified-search registry %q", reg)
|
||||
}
|
||||
// Make sure to add ":latest" if needed
|
||||
named = reference.TagNameOnly(named)
|
||||
|
||||
candidates = append(candidates, named)
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
328
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go
generated
vendored
Normal file
328
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/shortnames.go
generated
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
package sysregistriesv2
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage/pkg/lockfile"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// defaultShortNameMode is the default mode of registries.conf files if the
|
||||
// corresponding field is left empty.
|
||||
const defaultShortNameMode = types.ShortNameModePermissive
|
||||
|
||||
// userShortNamesFile is the user-specific config file to store aliases.
|
||||
var userShortNamesFile = filepath.FromSlash("containers/short-name-aliases.conf")
|
||||
|
||||
// shortNameAliasesConfPath returns the path to the machine-generated
|
||||
// short-name-aliases.conf file.
|
||||
func shortNameAliasesConfPath(ctx *types.SystemContext) (string, error) {
|
||||
if ctx != nil && len(ctx.UserShortNameAliasConfPath) > 0 {
|
||||
return ctx.UserShortNameAliasConfPath, nil
|
||||
}
|
||||
|
||||
configHome, err := homedir.GetConfigHome()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(configHome, userShortNamesFile), nil
|
||||
}
|
||||
|
||||
// shortNameAliasConf is a subset of the `V2RegistriesConf` format. It's used in the
|
||||
// software-maintained `userShortNamesFile`.
|
||||
type shortNameAliasConf struct {
|
||||
// A map for aliasing short names to their fully-qualified image
|
||||
// reference counter parts.
|
||||
// Note that Aliases is niled after being loaded from a file.
|
||||
Aliases map[string]string `toml:"aliases"`
|
||||
}
|
||||
|
||||
// alias combines the parsed value of an alias with the config file it has been
|
||||
// specified in. The config file is crucial for an improved user experience
|
||||
// such that users are able to resolve potential pull errors.
|
||||
type alias struct {
|
||||
// The parsed value of an alias. May be nil if set to "" in a config.
|
||||
value reference.Named
|
||||
// The config file the alias originates from.
|
||||
configOrigin string
|
||||
}
|
||||
|
||||
// shortNameAliasCache is the result of parsing shortNameAliasConf,
|
||||
// pre-processed for faster usage.
|
||||
type shortNameAliasCache struct {
|
||||
// Note that an alias value may be nil iff it's set as an empty string
|
||||
// in the config.
|
||||
namedAliases map[string]alias
|
||||
}
|
||||
|
||||
// ResolveShortNameAlias performs an alias resolution of the specified name.
|
||||
// The user-specific short-name-aliases.conf has precedence over aliases in the
|
||||
// assembled registries.conf. It returns the possibly resolved alias or nil, a
|
||||
// human-readable description of the config where the alias is specified, and
|
||||
// an error. The origin of the config file is crucial for an improved user
|
||||
// experience such that users are able to resolve potential pull errors.
|
||||
// Almost all callers should use pkg/shortnames instead.
|
||||
//
|
||||
// Note that it’s the caller’s responsibility to pass only a repository
|
||||
// (reference.IsNameOnly) as the short name.
|
||||
func ResolveShortNameAlias(ctx *types.SystemContext, name string) (reference.Named, string, error) {
|
||||
if err := validateShortName(name); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
confPath, lock, err := shortNameAliasesConfPathAndLock(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Acquire the lock as a reader to allow for multiple routines in the
|
||||
// same process space to read simultaneously.
|
||||
lock.RLock()
|
||||
defer lock.Unlock()
|
||||
|
||||
_, aliasCache, err := loadShortNameAliasConf(confPath)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// First look up the short-name-aliases.conf. Note that a value may be
|
||||
// nil iff it's set as an empty string in the config.
|
||||
alias, resolved := aliasCache.namedAliases[name]
|
||||
if resolved {
|
||||
return alias.value, alias.configOrigin, nil
|
||||
}
|
||||
|
||||
config, err := getConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
alias, resolved = config.aliasCache.namedAliases[name]
|
||||
if resolved {
|
||||
return alias.value, alias.configOrigin, nil
|
||||
}
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
// editShortNameAlias loads the aliases.conf file and changes it. If value is
|
||||
// set, it adds the name-value pair as a new alias. Otherwise, it will remove
|
||||
// name from the config.
|
||||
func editShortNameAlias(ctx *types.SystemContext, name string, value *string) error {
|
||||
if err := validateShortName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
if _, err := parseShortNameValue(*value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
confPath, lock, err := shortNameAliasesConfPathAndLock(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Acquire the lock as a writer to prevent data corruption.
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
// Load the short-name-alias.conf, add the specified name-value pair,
|
||||
// and write it back to the file.
|
||||
conf, _, err := loadShortNameAliasConf(confPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if conf.Aliases == nil { // Ensure we have a map to update.
|
||||
conf.Aliases = make(map[string]string)
|
||||
}
|
||||
if value != nil {
|
||||
conf.Aliases[name] = *value
|
||||
} else {
|
||||
// If the name does not exist, throw an error.
|
||||
if _, exists := conf.Aliases[name]; !exists {
|
||||
return errors.Errorf("short-name alias %q not found in %q: please check registries.conf files", name, confPath)
|
||||
}
|
||||
|
||||
delete(conf.Aliases, name)
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(confPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
encoder := toml.NewEncoder(f)
|
||||
return encoder.Encode(conf)
|
||||
}
|
||||
|
||||
// AddShortNameAlias adds the specified name-value pair as a new alias to the
|
||||
// user-specific aliases.conf. It may override an existing alias for `name`.
|
||||
//
|
||||
// Note that it’s the caller’s responsibility to pass only a repository
|
||||
// (reference.IsNameOnly) as the short name.
|
||||
func AddShortNameAlias(ctx *types.SystemContext, name string, value string) error {
|
||||
return editShortNameAlias(ctx, name, &value)
|
||||
}
|
||||
|
||||
// RemoveShortNameAlias clears the alias for the specified name. It throws an
|
||||
// error in case name does not exist in the machine-generated
|
||||
// short-name-alias.conf. In such case, the alias must be specified in one of
|
||||
// the registries.conf files, which is the users' responsibility.
|
||||
//
|
||||
// Note that it’s the caller’s responsibility to pass only a repository
|
||||
// (reference.IsNameOnly) as the short name.
|
||||
func RemoveShortNameAlias(ctx *types.SystemContext, name string) error {
|
||||
return editShortNameAlias(ctx, name, nil)
|
||||
}
|
||||
|
||||
// parseShortNameValue parses the specified alias into a reference.Named. The alias is
|
||||
// expected to not be tagged or carry a digest and *must* include a
|
||||
// domain/registry.
|
||||
//
|
||||
// Note that the returned reference is always normalized.
|
||||
func parseShortNameValue(alias string) (reference.Named, error) {
|
||||
ref, err := reference.Parse(alias)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing alias %q", alias)
|
||||
}
|
||||
|
||||
if _, ok := ref.(reference.Digested); ok {
|
||||
return nil, errors.Errorf("invalid alias %q: must not contain digest", alias)
|
||||
}
|
||||
|
||||
if _, ok := ref.(reference.Tagged); ok {
|
||||
return nil, errors.Errorf("invalid alias %q: must not contain tag", alias)
|
||||
}
|
||||
|
||||
named, ok := ref.(reference.Named)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid alias %q: must contain registry and repository", alias)
|
||||
}
|
||||
|
||||
registry := reference.Domain(named)
|
||||
if !(strings.ContainsAny(registry, ".:") || registry == "localhost") {
|
||||
return nil, errors.Errorf("invalid alias %q: must contain registry and repository", alias)
|
||||
}
|
||||
|
||||
// A final parse to make sure that docker.io references are correctly
|
||||
// normalized (e.g., docker.io/alpine to docker.io/library/alpine.
|
||||
named, err = reference.ParseNormalizedNamed(alias)
|
||||
return named, err
|
||||
}
|
||||
|
||||
// validateShortName parses the specified `name` of an alias (i.e., the left-hand
|
||||
// side) and checks if it's a short name and does not include a tag or digest.
|
||||
func validateShortName(name string) error {
|
||||
repo, err := reference.Parse(name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot parse short name: %q", name)
|
||||
}
|
||||
|
||||
if _, ok := repo.(reference.Digested); ok {
|
||||
return errors.Errorf("invalid short name %q: must not contain digest", name)
|
||||
}
|
||||
|
||||
if _, ok := repo.(reference.Tagged); ok {
|
||||
return errors.Errorf("invalid short name %q: must not contain tag", name)
|
||||
}
|
||||
|
||||
named, ok := repo.(reference.Named)
|
||||
if !ok {
|
||||
return errors.Errorf("invalid short name %q: no name", name)
|
||||
}
|
||||
|
||||
registry := reference.Domain(named)
|
||||
if strings.ContainsAny(registry, ".:") || registry == "localhost" {
|
||||
return errors.Errorf("invalid short name %q: must not contain registry", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newShortNameAliasCache parses shortNameAliasConf and returns the corresponding internal
|
||||
// representation.
|
||||
func newShortNameAliasCache(path string, conf *shortNameAliasConf) (*shortNameAliasCache, error) {
|
||||
res := shortNameAliasCache{
|
||||
namedAliases: make(map[string]alias),
|
||||
}
|
||||
errs := []error{}
|
||||
for name, value := range conf.Aliases {
|
||||
if err := validateShortName(name); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Empty right-hand side values in config files allow to reset
|
||||
// an alias in a previously loaded config. This way, drop-in
|
||||
// config files from registries.conf.d can reset potentially
|
||||
// malconfigured aliases.
|
||||
if value == "" {
|
||||
res.namedAliases[name] = alias{nil, path}
|
||||
continue
|
||||
}
|
||||
|
||||
named, err := parseShortNameValue(value)
|
||||
if err != nil {
|
||||
// We want to report *all* malformed entries to avoid a
|
||||
// whack-a-mole for the user.
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
res.namedAliases[name] = alias{named, path}
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
err := errs[0]
|
||||
for i := 1; i < len(errs); i++ {
|
||||
err = errors.Wrapf(err, "%v\n", errs[i])
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// updateWithConfigurationFrom updates c with configuration from updates.
|
||||
// In case of conflict, updates is preferred.
|
||||
func (c *shortNameAliasCache) updateWithConfigurationFrom(updates *shortNameAliasCache) {
|
||||
for name, value := range updates.namedAliases {
|
||||
c.namedAliases[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
func loadShortNameAliasConf(confPath string) (*shortNameAliasConf, *shortNameAliasCache, error) {
|
||||
conf := shortNameAliasConf{}
|
||||
|
||||
_, err := toml.DecodeFile(confPath, &conf)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
// It's okay if the config doesn't exist. Other errors are not.
|
||||
return nil, nil, errors.Wrapf(err, "error loading short-name aliases config file %q", confPath)
|
||||
}
|
||||
|
||||
// Even if we don’t always need the cache, doing so validates the machine-generated config. The
|
||||
// file could still be corrupted by another process or user.
|
||||
cache, err := newShortNameAliasCache(confPath, &conf)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "error loading short-name aliases config file %q", confPath)
|
||||
}
|
||||
|
||||
return &conf, cache, nil
|
||||
}
|
||||
|
||||
func shortNameAliasesConfPathAndLock(ctx *types.SystemContext) (string, lockfile.Locker, error) {
|
||||
shortNameAliasesConfPath, err := shortNameAliasesConfPath(ctx)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
// Make sure the path to file exists.
|
||||
if err := os.MkdirAll(filepath.Dir(shortNameAliasesConfPath), 0700); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
lockPath := shortNameAliasesConfPath + ".lock"
|
||||
locker, err := lockfile.GetLockfile(lockPath)
|
||||
return shortNameAliasesConfPath, locker, err
|
||||
}
|
||||
252
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go
generated
vendored
252
vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go
generated
vendored
@@ -154,6 +154,19 @@ type V2RegistriesConf struct {
|
||||
Registries []Registry `toml:"registry"`
|
||||
// An array of host[:port] (not prefix!) entries to use for resolving unqualified image references
|
||||
UnqualifiedSearchRegistries []string `toml:"unqualified-search-registries"`
|
||||
|
||||
// ShortNameMode defines how short-name resolution should be handled by
|
||||
// _consumers_ of this package. Depending on the mode, the user should
|
||||
// be prompted with a choice of using one of the unqualified-search
|
||||
// registries when referring to a short name.
|
||||
//
|
||||
// Valid modes are: * "prompt": prompt if stdout is a TTY, otherwise
|
||||
// use all unqualified-search registries * "enforcing": always prompt
|
||||
// and error if stdout is not a TTY * "disabled": do not prompt and
|
||||
// potentially use all unqualified-search registries
|
||||
ShortNameMode string `toml:"short-name-mode"`
|
||||
|
||||
shortNameAliasConf
|
||||
}
|
||||
|
||||
// Nonempty returns true if config contains at least one configuration entry.
|
||||
@@ -162,10 +175,23 @@ func (config *V2RegistriesConf) Nonempty() bool {
|
||||
len(config.UnqualifiedSearchRegistries) != 0)
|
||||
}
|
||||
|
||||
// tomlConfig is the data type used to unmarshal the toml config.
|
||||
type tomlConfig struct {
|
||||
V2RegistriesConf
|
||||
V1RegistriesConf // for backwards compatibility with sysregistries v1
|
||||
// parsedConfig is the result of parsing, and possibly merging, configuration files;
|
||||
// it is the boundary between the process of reading+ingesting the files, and
|
||||
// later interpreting the configuraiton based on caller’s requests.
|
||||
type parsedConfig struct {
|
||||
// NOTE: Update also parsedConfig.updateWithConfigurationFrom!
|
||||
|
||||
// partialV2 must continue to exist to maintain the return value of TryUpdatingCache
|
||||
// for compatibility with existing callers.
|
||||
// We store the authoritative Registries and UnqualifiedSearchRegistries values there as well.
|
||||
partialV2 V2RegistriesConf
|
||||
// Absolute path to the configuration file that set the UnqualifiedSearchRegistries.
|
||||
unqualifiedSearchRegistriesOrigin string
|
||||
// Result of parsing of partialV2.ShortNameMode.
|
||||
// NOTE: May be ShortNameModeInvalid to represent ShortNameMode == "" in intermediate values;
|
||||
// the full configuration in configCache / getConfig() always contains a valid value.
|
||||
shortNameMode types.ShortNameMode
|
||||
aliasCache *shortNameAliasCache
|
||||
}
|
||||
|
||||
// InvalidRegistries represents an invalid registry configurations. An example
|
||||
@@ -254,7 +280,7 @@ var anchoredDomainRegexp = regexp.MustCompile("^" + reference.DomainRegexp.Strin
|
||||
|
||||
// postProcess checks the consistency of all the configuration, looks for conflicts,
|
||||
// and normalizes the configuration (e.g., sets the Prefix to Location if not set).
|
||||
func (config *V2RegistriesConf) postProcess() error {
|
||||
func (config *V2RegistriesConf) postProcessRegistries() error {
|
||||
regMap := make(map[string][]*Registry)
|
||||
|
||||
for i := range config.Registries {
|
||||
@@ -301,6 +327,7 @@ func (config *V2RegistriesConf) postProcess() error {
|
||||
msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'insecure' setting", reg.Location)
|
||||
return &InvalidRegistries{s: msg}
|
||||
}
|
||||
|
||||
if reg.Blocked != other.Blocked {
|
||||
msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'blocked' setting", reg.Location)
|
||||
return &InvalidRegistries{s: msg}
|
||||
@@ -323,16 +350,25 @@ func (config *V2RegistriesConf) postProcess() error {
|
||||
// rendering later items with the same prefix non-existent. We cannot error
|
||||
// out anymore as this might break existing users, so let's just ignore them
|
||||
// to guarantee that the same prefix exists only once.
|
||||
knownPrefixes := make(map[string]bool)
|
||||
uniqueRegistries := []Registry{}
|
||||
//
|
||||
// As a side effect of parsedConfig.updateWithConfigurationFrom, the Registries slice
|
||||
// is always sorted. To be consistent in situations where it is not called (no drop-ins),
|
||||
// sort it here as well.
|
||||
prefixes := []string{}
|
||||
uniqueRegistries := make(map[string]Registry)
|
||||
for i := range config.Registries {
|
||||
// TODO: should we warn if we see the same prefix being used multiple times?
|
||||
if _, exists := knownPrefixes[config.Registries[i].Prefix]; !exists {
|
||||
knownPrefixes[config.Registries[i].Prefix] = true
|
||||
uniqueRegistries = append(uniqueRegistries, config.Registries[i])
|
||||
prefix := config.Registries[i].Prefix
|
||||
if _, exists := uniqueRegistries[prefix]; !exists {
|
||||
uniqueRegistries[prefix] = config.Registries[i]
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
}
|
||||
config.Registries = uniqueRegistries
|
||||
sort.Strings(prefixes)
|
||||
config.Registries = []Registry{}
|
||||
for _, prefix := range prefixes {
|
||||
config.Registries = append(config.Registries, uniqueRegistries[prefix])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -385,6 +421,7 @@ func newConfigWrapper(ctx *types.SystemContext) configWrapper {
|
||||
} else {
|
||||
wrapper.userConfigDirPath = userRegistriesDirPath
|
||||
}
|
||||
|
||||
return wrapper
|
||||
} else if ctx != nil && ctx.RootForImplicitAbsolutePaths != "" {
|
||||
wrapper.configPath = filepath.Join(ctx.RootForImplicitAbsolutePaths, systemRegistriesConfPath)
|
||||
@@ -426,7 +463,7 @@ var configMutex = sync.Mutex{}
|
||||
// configCache caches already loaded configs with config paths as keys and is
|
||||
// used to avoid redundantly parsing configs. Concurrent accesses to the cache
|
||||
// are synchronized via configMutex.
|
||||
var configCache = make(map[configWrapper]*V2RegistriesConf)
|
||||
var configCache = make(map[configWrapper]*parsedConfig)
|
||||
|
||||
// InvalidateCache invalidates the registry cache. This function is meant to be
|
||||
// used for long-running processes that need to reload potential changes made to
|
||||
@@ -434,11 +471,11 @@ var configCache = make(map[configWrapper]*V2RegistriesConf)
|
||||
func InvalidateCache() {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
configCache = make(map[configWrapper]*V2RegistriesConf)
|
||||
configCache = make(map[configWrapper]*parsedConfig)
|
||||
}
|
||||
|
||||
// getConfig returns the config object corresponding to ctx, loading it if it is not yet cached.
|
||||
func getConfig(ctx *types.SystemContext) (*V2RegistriesConf, error) {
|
||||
func getConfig(ctx *types.SystemContext) (*parsedConfig, error) {
|
||||
wrapper := newConfigWrapper(ctx)
|
||||
configMutex.Lock()
|
||||
if config, inCache := configCache[wrapper]; inCache {
|
||||
@@ -504,27 +541,37 @@ func dropInConfigs(wrapper configWrapper) ([]string, error) {
|
||||
// TryUpdatingCache loads the configuration from the provided `SystemContext`
|
||||
// without using the internal cache. On success, the loaded configuration will
|
||||
// be added into the internal registry cache.
|
||||
// It returns the resulting configuration; this is DEPRECATED and may not correctly
|
||||
// reflect any future data handled by this package.
|
||||
func TryUpdatingCache(ctx *types.SystemContext) (*V2RegistriesConf, error) {
|
||||
return tryUpdatingCache(ctx, newConfigWrapper(ctx))
|
||||
config, err := tryUpdatingCache(ctx, newConfigWrapper(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config.partialV2, err
|
||||
}
|
||||
|
||||
// tryUpdatingCache implements TryUpdatingCache with an additional configWrapper
|
||||
// argument to avoid redundantly calculating the config paths.
|
||||
func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*V2RegistriesConf, error) {
|
||||
func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*parsedConfig, error) {
|
||||
configMutex.Lock()
|
||||
defer configMutex.Unlock()
|
||||
|
||||
// load the config
|
||||
config := &tomlConfig{}
|
||||
if err := config.loadConfig(wrapper.configPath, false); err != nil {
|
||||
config, err := loadConfigFile(wrapper.configPath, false)
|
||||
if err != nil {
|
||||
// Continue with an empty []Registry if we use the default config, which
|
||||
// implies that the config path of the SystemContext isn't set.
|
||||
//
|
||||
// Note: if ctx.SystemRegistriesConfPath points to the default config,
|
||||
// we will still return an error.
|
||||
if os.IsNotExist(err) && (ctx == nil || ctx.SystemRegistriesConfPath == "") {
|
||||
config = &tomlConfig{}
|
||||
config.V2RegistriesConf = V2RegistriesConf{Registries: []Registry{}}
|
||||
config = &parsedConfig{}
|
||||
config.partialV2 = V2RegistriesConf{Registries: []Registry{}}
|
||||
config.aliasCache, err = newShortNameAliasCache("", &shortNameAliasConf{})
|
||||
if err != nil {
|
||||
return nil, err // Should never happen
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "error loading registries configuration %q", wrapper.configPath)
|
||||
}
|
||||
@@ -537,16 +584,20 @@ func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*V2Regis
|
||||
}
|
||||
for _, path := range dinConfigs {
|
||||
// Enforce v2 format for drop-in-configs.
|
||||
if err := config.loadConfig(path, true); err != nil {
|
||||
dropIn, err := loadConfigFile(path, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error loading drop-in registries configuration %q", path)
|
||||
}
|
||||
config.updateWithConfigurationFrom(dropIn)
|
||||
}
|
||||
|
||||
v2Config := &config.V2RegistriesConf
|
||||
if config.shortNameMode == types.ShortNameModeInvalid {
|
||||
config.shortNameMode = defaultShortNameMode
|
||||
}
|
||||
|
||||
// populate the cache
|
||||
configCache[wrapper] = v2Config
|
||||
return v2Config, nil
|
||||
configCache[wrapper] = config
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetRegistries loads and returns the registries specified in the config.
|
||||
@@ -557,17 +608,53 @@ func GetRegistries(ctx *types.SystemContext) ([]Registry, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config.Registries, nil
|
||||
return config.partialV2.Registries, nil
|
||||
}
|
||||
|
||||
// UnqualifiedSearchRegistries returns a list of host[:port] entries to try
|
||||
// for unqualified image search, in the returned order)
|
||||
func UnqualifiedSearchRegistries(ctx *types.SystemContext) ([]string, error) {
|
||||
registries, _, err := UnqualifiedSearchRegistriesWithOrigin(ctx)
|
||||
return registries, err
|
||||
}
|
||||
|
||||
// UnqualifiedSearchRegistriesWithOrigin returns a list of host[:port] entries
|
||||
// to try for unqualified image search, in the returned order. It also returns
|
||||
// a human-readable description of where these entries are specified (e.g., a
|
||||
// registries.conf file).
|
||||
func UnqualifiedSearchRegistriesWithOrigin(ctx *types.SystemContext) ([]string, string, error) {
|
||||
config, err := getConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
return config.UnqualifiedSearchRegistries, nil
|
||||
return config.partialV2.UnqualifiedSearchRegistries, config.unqualifiedSearchRegistriesOrigin, nil
|
||||
}
|
||||
|
||||
// parseShortNameMode translates the string into well-typed
|
||||
// types.ShortNameMode.
|
||||
func parseShortNameMode(mode string) (types.ShortNameMode, error) {
|
||||
switch mode {
|
||||
case "disabled":
|
||||
return types.ShortNameModeDisabled, nil
|
||||
case "enforcing":
|
||||
return types.ShortNameModeEnforcing, nil
|
||||
case "permissive":
|
||||
return types.ShortNameModePermissive, nil
|
||||
default:
|
||||
return types.ShortNameModeInvalid, errors.Errorf("invalid short-name mode: %q", mode)
|
||||
}
|
||||
}
|
||||
|
||||
// GetShortNameMode returns the configured types.ShortNameMode.
|
||||
func GetShortNameMode(ctx *types.SystemContext) (types.ShortNameMode, error) {
|
||||
if ctx != nil && ctx.ShortNameMode != nil {
|
||||
return *ctx.ShortNameMode, nil
|
||||
}
|
||||
config, err := getConfig(ctx)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return config.shortNameMode, err
|
||||
}
|
||||
|
||||
// refMatchesPrefix returns true iff ref,
|
||||
@@ -609,7 +696,7 @@ func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) {
|
||||
|
||||
reg := Registry{}
|
||||
prefixLen := 0
|
||||
for _, r := range config.Registries {
|
||||
for _, r := range config.partialV2.Registries {
|
||||
if refMatchesPrefix(ref, r.Prefix) {
|
||||
length := len(r.Prefix)
|
||||
if length > prefixLen {
|
||||
@@ -624,55 +711,87 @@ func FindRegistry(ctx *types.SystemContext, ref string) (*Registry, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// loadConfig loads and unmarshals the configuration at the specified path. Note
|
||||
// that v1 configs are translated into v2 and are cleared. Use forceV2 if the
|
||||
// config must in the v2 format.
|
||||
//
|
||||
// Note that specified fields in path will replace already set fields in the
|
||||
// tomlConfig. Only the [[registry]] tables are merged by prefix.
|
||||
func (c *tomlConfig) loadConfig(path string, forceV2 bool) error {
|
||||
// loadConfigFile loads and unmarshals a single config file.
|
||||
// Use forceV2 if the config must in the v2 format.
|
||||
func loadConfigFile(path string, forceV2 bool) (*parsedConfig, error) {
|
||||
logrus.Debugf("Loading registries configuration %q", path)
|
||||
|
||||
// Save the registries before decoding the file where they could be lost.
|
||||
// We merge them later again.
|
||||
registryMap := make(map[string]Registry)
|
||||
for i := range c.Registries {
|
||||
registryMap[c.Registries[i].Prefix] = c.Registries[i]
|
||||
// tomlConfig allows us to unmarshal either V1 or V2 simultaneously.
|
||||
type tomlConfig struct {
|
||||
V2RegistriesConf
|
||||
V1RegistriesConf // for backwards compatibility with sysregistries v1
|
||||
}
|
||||
|
||||
// Load the tomlConfig. Note that `DecodeFile` will overwrite set fields.
|
||||
c.Registries = nil // important to clear the memory to prevent us from overlapping fields
|
||||
_, err := toml.DecodeFile(path, c)
|
||||
var combinedTOML tomlConfig
|
||||
_, err := toml.DecodeFile(path, &combinedTOML)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.V1RegistriesConf.Nonempty() {
|
||||
if combinedTOML.V1RegistriesConf.Nonempty() {
|
||||
// Enforce the v2 format if requested.
|
||||
if forceV2 {
|
||||
return &InvalidRegistries{s: "registry must be in v2 format but is in v1"}
|
||||
return nil, &InvalidRegistries{s: "registry must be in v2 format but is in v1"}
|
||||
}
|
||||
|
||||
// Convert a v1 config into a v2 config.
|
||||
if c.V2RegistriesConf.Nonempty() {
|
||||
return &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"}
|
||||
if combinedTOML.V2RegistriesConf.Nonempty() {
|
||||
return nil, &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"}
|
||||
}
|
||||
v2, err := c.V1RegistriesConf.ConvertToV2()
|
||||
converted, err := combinedTOML.V1RegistriesConf.ConvertToV2()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
c.V1RegistriesConf = V1RegistriesConf{}
|
||||
c.V2RegistriesConf = *v2
|
||||
combinedTOML.V1RegistriesConf = V1RegistriesConf{}
|
||||
combinedTOML.V2RegistriesConf = *converted
|
||||
}
|
||||
|
||||
res := parsedConfig{partialV2: combinedTOML.V2RegistriesConf}
|
||||
|
||||
// Post process registries, set the correct prefixes, sanity checks, etc.
|
||||
if err := c.postProcess(); err != nil {
|
||||
return err
|
||||
if err := res.partialV2.postProcessRegistries(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.unqualifiedSearchRegistriesOrigin = path
|
||||
|
||||
if len(res.partialV2.ShortNameMode) > 0 {
|
||||
mode, err := parseShortNameMode(res.partialV2.ShortNameMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.shortNameMode = mode
|
||||
} else {
|
||||
res.shortNameMode = types.ShortNameModeInvalid
|
||||
}
|
||||
|
||||
// Parse and validate short-name aliases.
|
||||
cache, err := newShortNameAliasCache(path, &res.partialV2.shortNameAliasConf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error validating short-name aliases")
|
||||
}
|
||||
res.aliasCache = cache
|
||||
// Clear conf.partialV2.shortNameAliasConf to make it available for garbage collection and
|
||||
// reduce memory consumption. We're consulting aliasCache for lookups.
|
||||
res.partialV2.shortNameAliasConf = shortNameAliasConf{}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// updateWithConfigurationFrom updates c with configuration from updates.
|
||||
//
|
||||
// Fields present in updates will typically replace already set fields in c.
|
||||
// The [[registry]] and alias tables are merged.
|
||||
func (c *parsedConfig) updateWithConfigurationFrom(updates *parsedConfig) {
|
||||
// == Merge Registries:
|
||||
registryMap := make(map[string]Registry)
|
||||
for i := range c.partialV2.Registries {
|
||||
registryMap[c.partialV2.Registries[i].Prefix] = c.partialV2.Registries[i]
|
||||
}
|
||||
// Merge the freshly loaded registries.
|
||||
for i := range c.Registries {
|
||||
registryMap[c.Registries[i].Prefix] = c.Registries[i]
|
||||
for i := range updates.partialV2.Registries {
|
||||
registryMap[updates.partialV2.Registries[i].Prefix] = updates.partialV2.Registries[i]
|
||||
}
|
||||
|
||||
// Go maps have a non-deterministic order when iterating the keys, so
|
||||
@@ -686,10 +805,27 @@ func (c *tomlConfig) loadConfig(path string, forceV2 bool) error {
|
||||
}
|
||||
sort.Strings(prefixes)
|
||||
|
||||
c.Registries = []Registry{}
|
||||
c.partialV2.Registries = []Registry{}
|
||||
for _, prefix := range prefixes {
|
||||
c.Registries = append(c.Registries, registryMap[prefix])
|
||||
c.partialV2.Registries = append(c.partialV2.Registries, registryMap[prefix])
|
||||
}
|
||||
|
||||
return nil
|
||||
// == Merge UnqualifiedSearchRegistries:
|
||||
// This depends on an subtlety of the behavior of the TOML decoder, where a missing array field
|
||||
// is not modified while unmarshaling (in our case remains to nil), while an [] is unmarshaled
|
||||
// as a non-nil []string{}.
|
||||
if updates.partialV2.UnqualifiedSearchRegistries != nil {
|
||||
c.partialV2.UnqualifiedSearchRegistries = updates.partialV2.UnqualifiedSearchRegistries
|
||||
c.unqualifiedSearchRegistriesOrigin = updates.unqualifiedSearchRegistriesOrigin
|
||||
}
|
||||
|
||||
// == Merge shortNameMode:
|
||||
// We don’t maintain c.partialV2.ShortNameMode.
|
||||
if updates.shortNameMode != types.ShortNameModeInvalid {
|
||||
c.shortNameMode = updates.shortNameMode
|
||||
}
|
||||
|
||||
// == Merge aliasCache:
|
||||
// We don’t maintain (in fact we actively clear) c.partialV2.shortNameAliasConf.
|
||||
c.aliasCache.updateWithConfigurationFrom(updates.aliasCache)
|
||||
}
|
||||
|
||||
34
vendor/github.com/containers/image/v5/types/types.go
generated
vendored
34
vendor/github.com/containers/image/v5/types/types.go
generated
vendored
@@ -486,6 +486,36 @@ func NewOptionalBool(b bool) OptionalBool {
|
||||
return o
|
||||
}
|
||||
|
||||
// ShortNameMode defines the mode of short-name resolution.
|
||||
//
|
||||
// The use of unqualified-search registries entails an ambiguity as it's
|
||||
// unclear from which registry a given image, referenced by a short name, may
|
||||
// be pulled from.
|
||||
//
|
||||
// The ShortNameMode type defines how short names should resolve.
|
||||
type ShortNameMode int
|
||||
|
||||
const (
|
||||
ShortNameModeInvalid ShortNameMode = iota
|
||||
// Use all configured unqualified-search registries without prompting
|
||||
// the user.
|
||||
ShortNameModeDisabled
|
||||
// If stdout and stdin are a TTY, prompt the user to select a configured
|
||||
// unqualified-search registry. Otherwise, use all configured
|
||||
// unqualified-search registries.
|
||||
//
|
||||
// Note that if only one unqualified-search registry is set, it will be
|
||||
// used without prompting.
|
||||
ShortNameModePermissive
|
||||
// Always prompt the user to select a configured unqualified-search
|
||||
// registry. Throw an error if stdout or stdin is not a TTY as
|
||||
// prompting isn't possible.
|
||||
//
|
||||
// Note that if only one unqualified-search registry is set, it will be
|
||||
// used without prompting.
|
||||
ShortNameModeEnforcing
|
||||
)
|
||||
|
||||
// SystemContext allows parameterizing access to implicitly-accessed resources,
|
||||
// like configuration files in /etc and users' login state in their home directory.
|
||||
// Various components can share the same field only if their semantics is exactly
|
||||
@@ -509,6 +539,10 @@ type SystemContext struct {
|
||||
SystemRegistriesConfPath string
|
||||
// Path to the system-wide registries configuration directory
|
||||
SystemRegistriesConfDirPath string
|
||||
// Path to the user-specific short-names configuration file
|
||||
UserShortNameAliasConfPath string
|
||||
// If set, short-name resolution in pkg/shortnames must follow the specified mode
|
||||
ShortNameMode *ShortNameMode
|
||||
// If not "", overrides the default path for the authentication file, but only new format files
|
||||
AuthFilePath string
|
||||
// if not "", overrides the default path for the authentication file, but with the legacy format;
|
||||
|
||||
2
vendor/github.com/containers/image/v5/version/version.go
generated
vendored
2
vendor/github.com/containers/image/v5/version/version.go
generated
vendored
@@ -6,7 +6,7 @@ const (
|
||||
// VersionMajor is for an API incompatible changes
|
||||
VersionMajor = 5
|
||||
// VersionMinor is for functionality in a backwards-compatible manner
|
||||
VersionMinor = 7
|
||||
VersionMinor = 8
|
||||
// VersionPatch is for backwards-compatible bug fixes
|
||||
VersionPatch = 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user