diff --git a/cmd/podman/search.go b/cmd/podman/search.go index 80366175356..e6d4002609a 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -7,11 +7,12 @@ import ( "strings" "github.com/containers/image/docker" + "github.com/containers/image/pkg/sysregistriesv2" + "github.com/containers/image/types" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/podman/formats" "github.com/projectatomic/libpod/cmd/podman/libpodruntime" "github.com/projectatomic/libpod/libpod/common" - sysreg "github.com/projectatomic/libpod/pkg/registries" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -107,14 +108,17 @@ func searchCmd(c *cli.Context) error { filter: c.StringSlice("filter"), } - var registries []string + var searchRegistries []string if len(c.StringSlice("registry")) > 0 { - registries = c.StringSlice("registry") + searchRegistries = c.StringSlice("registry") } else { - registries, err = sysreg.GetRegistries() + registries, err := sysregistriesv2.GetRegistries(&types.SystemContext{}) if err != nil { return errors.Wrapf(err, "error getting registries to search") } + for _, reg := range sysregistriesv2.FindUnqualifiedSearchRegistries(registries) { + searchRegistries = append(searchRegistries, reg.URL.String()) + } } filter, err := parseSearchFilter(&opts) @@ -122,7 +126,7 @@ func searchCmd(c *cli.Context) error { return err } - return generateSearchOutput(term, registries, opts, *filter) + return generateSearchOutput(term, searchRegistries, opts, *filter) } func genSearchFormat(format string) string { diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index cfbc5316b2b..2f95563c669 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -204,7 +204,7 @@ BuildRequires: golang(github.com/containers/image/docker/tarfile) BuildRequires: golang(github.com/containers/image/image) BuildRequires: golang(github.com/containers/image/oci/archive) BuildRequires: golang(github.com/containers/image/pkg/strslice) -BuildRequires: golang(github.com/containers/image/pkg/sysregistries) +BuildRequires: golang(github.com/containers/image/pkg/sysregistriesv2) BuildRequires: golang(github.com/containers/image/signature) BuildRequires: golang(github.com/containers/image/storage) BuildRequires: golang(github.com/containers/image/tarball) @@ -256,7 +256,7 @@ Requires: golang(github.com/containers/image/docker/tarfile) Requires: golang(github.com/containers/image/image) Requires: golang(github.com/containers/image/oci/archive) Requires: golang(github.com/containers/image/pkg/strslice) -Requires: golang(github.com/containers/image/pkg/sysregistries) +Requires: golang(github.com/containers/image/pkg/sysregistries2) Requires: golang(github.com/containers/image/signature) Requires: golang(github.com/containers/image/storage) Requires: golang(github.com/containers/image/tarball) diff --git a/libpod/image/pull.go b/libpod/image/pull.go index cdecd4e0e1f..f2d997c77aa 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -14,13 +14,12 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/docker/tarfile" ociarchive "github.com/containers/image/oci/archive" - "github.com/containers/image/pkg/sysregistries" + "github.com/containers/image/pkg/sysregistriesv2" is "github.com/containers/image/storage" "github.com/containers/image/tarball" "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/pkg/errors" - "github.com/projectatomic/libpod/pkg/registries" "github.com/projectatomic/libpod/pkg/util" "github.com/sirupsen/logrus" ) @@ -179,10 +178,16 @@ func (i *Image) pullImage(ctx context.Context, writer io.Writer, authfile, signa } defer policyContext.Destroy() - insecureRegistries, err := registries.GetInsecureRegistries() + registries, err := sysregistriesv2.GetRegistries(&types.SystemContext{}) if err != nil { return "", err } + insecureRegistries := []string{} + for _, reg := range registries { + if reg.Insecure { + insecureRegistries = append(insecureRegistries, reg.URL.String()) + } + } for _, imageInfo := range pullStructs { copyOptions := getCopyOptions(writer, signaturePolicyPath, dockerOptions, nil, signingOptions, authfile, "", false) @@ -238,12 +243,14 @@ func (i *Image) createNamesToPull() ([]*pullStruct, error) { if len(envOverride) > 0 { registryConfigPath = envOverride } - searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) + registries, err := sysregistriesv2.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) if err != nil { return nil, err } + searchRegistries := sysregistriesv2.FindUnqualifiedSearchRegistries(registries) + for _, registry := range searchRegistries { - decomposedImage.registry = registry + decomposedImage.registry = registry.URL.String() srcRef, err := alltransports.ParseImageName(decomposedImage.assembleWithTransport()) if err != nil { return nil, errors.Wrapf(err, "unable to parse '%s'", i.InputName) diff --git a/libpod/runtime.go b/libpod/runtime.go index 9de70da1b35..6234874a14c 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/BurntSushi/toml" + "github.com/containers/image/pkg/sysregistriesv2" is "github.com/containers/image/storage" "github.com/containers/image/types" "github.com/containers/storage" @@ -16,7 +17,6 @@ import ( "github.com/pkg/errors" "github.com/projectatomic/libpod/libpod/image" "github.com/projectatomic/libpod/pkg/hooks" - sysreg "github.com/projectatomic/libpod/pkg/registries" "github.com/sirupsen/logrus" "github.com/ulule/deepcopier" ) @@ -552,21 +552,30 @@ func (r *Runtime) Info() ([]InfoData, error) { } info = append(info, InfoData{Type: "store", Data: storeInfo}) - reg, err := sysreg.GetRegistries() + registries, err := sysregistriesv2.GetRegistries(&types.SystemContext{}) if err != nil { return nil, errors.Wrapf(err, "error getting registries") } - registries := make(map[string]interface{}) - registries["registries"] = reg - info = append(info, InfoData{Type: "registries", Data: registries}) - i, err := sysreg.GetInsecureRegistries() - if err != nil { - return nil, errors.Wrapf(err, "error getting registries") + // search registries + searchRegs := []string{} + for _, reg := range sysregistriesv2.FindUnqualifiedSearchRegistries(registries) { + searchRegs = append(searchRegs, reg.URL.String()) + } + searchInfo := make(map[string]interface{}) + searchInfo["registries"] = searchRegs + info = append(info, InfoData{Type: "registries", Data: searchInfo}) + + // insecure registries + insecureRegs := []string{} + for _, reg := range registries { + if reg.Insecure { + insecureRegs = append(insecureRegs, reg.URL.String()) + } } - insecureRegistries := make(map[string]interface{}) - insecureRegistries["registries"] = i - info = append(info, InfoData{Type: "insecure registries", Data: insecureRegistries}) + insecureInfo := make(map[string]interface{}) + insecureInfo["registries"] = insecureRegs + info = append(info, InfoData{Type: "insecure registries", Data: insecureInfo}) return info, nil } diff --git a/libpod/runtime_img_test.go b/libpod/runtime_img_test.go deleted file mode 100644 index c608c1b2588..00000000000 --- a/libpod/runtime_img_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package libpod - -import ( - "io/ioutil" - "os" - "reflect" - "testing" - - sysreg "github.com/projectatomic/libpod/pkg/registries" - "github.com/stretchr/testify/assert" -) - -var ( - registry = `[registries.search] -registries = ['one'] - -[registries.insecure] -registries = ['two']` -) - -func createTmpFile(content []byte) (string, error) { - tmpfile, err := ioutil.TempFile(os.TempDir(), "unittest") - if err != nil { - return "", err - } - - if _, err := tmpfile.Write(content); err != nil { - return "", err - - } - if err := tmpfile.Close(); err != nil { - return "", err - } - return tmpfile.Name(), nil -} - -func TestGetRegistries(t *testing.T) { - registryPath, err := createTmpFile([]byte(registry)) - assert.NoError(t, err) - defer os.Remove(registryPath) - os.Setenv("REGISTRIES_CONFIG_PATH", registryPath) - registries, err := sysreg.GetRegistries() - assert.NoError(t, err) - assert.True(t, reflect.DeepEqual(registries, []string{"one"})) -} - -func TestGetInsecureRegistries(t *testing.T) { - registryPath, err := createTmpFile([]byte(registry)) - assert.NoError(t, err) - os.Setenv("REGISTRIES_CONFIG_PATH", registryPath) - defer os.Remove(registryPath) - registries, err := sysreg.GetInsecureRegistries() - assert.NoError(t, err) - assert.True(t, reflect.DeepEqual(registries, []string{"two"})) -} diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go deleted file mode 100644 index 8e43c8b91b2..00000000000 --- a/pkg/registries/registries.go +++ /dev/null @@ -1,37 +0,0 @@ -package registries - -import ( - "os" - - "github.com/containers/image/pkg/sysregistries" - "github.com/containers/image/types" - "github.com/pkg/errors" -) - -// GetRegistries obtains the list of registries defined in the global registries file. -func GetRegistries() ([]string, error) { - registryConfigPath := "" - envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") - if len(envOverride) > 0 { - registryConfigPath = envOverride - } - searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse the registries.conf file") - } - return searchRegistries, nil -} - -// GetInsecureRegistries obtains the list of inseure registries from the global registration file. -func GetInsecureRegistries() ([]string, error) { - registryConfigPath := "" - envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") - if len(envOverride) > 0 { - registryConfigPath = envOverride - } - registries, err := sysregistries.GetInsecureRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) - if err != nil { - return nil, errors.Wrapf(err, "unable to parse the registries.conf file") - } - return registries, nil -} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index e2a9c4d5b6e..bdc4c7d86e7 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -5,11 +5,12 @@ import ( "fmt" "github.com/containers/image/docker" + "github.com/containers/image/pkg/sysregistriesv2" + "github.com/containers/image/types" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/projectatomic/libpod/cmd/podman/libpodruntime" ioprojectatomicpodman "github.com/projectatomic/libpod/cmd/podman/varlink" "github.com/projectatomic/libpod/libpod/image" - sysreg "github.com/projectatomic/libpod/pkg/registries" "github.com/projectatomic/libpod/pkg/util" ) @@ -180,12 +181,17 @@ func (i *LibpodAPI) RemoveImage(call ioprojectatomicpodman.VarlinkCall, name str // Requires an image name and a search limit as int func (i *LibpodAPI) SearchImage(call ioprojectatomicpodman.VarlinkCall, name string, limit int64) error { sc := image.GetSystemContext("", "", false) - registries, err := sysreg.GetRegistries() + registries, err := sysregistriesv2.GetRegistries(&types.SystemContext{}) if err != nil { return call.ReplyErrorOccurred(fmt.Sprintf("unable to get system registries: %q", err)) } + searchRegistries := []string{} + for _, reg := range sysregistriesv2.FindUnqualifiedSearchRegistries(registries) { + searchRegistries = append(searchRegistries, reg.URL.String()) + } + var imageResults []ioprojectatomicpodman.ImageSearch - for _, reg := range registries { + for _, reg := range searchRegistries { results, err := docker.SearchRegistry(getContext(), sc, reg, name, int(limit)) if err != nil { return call.ReplyErrorOccurred(err.Error()) diff --git a/vendor.conf b/vendor.conf index 33a437adf17..d3ce6abef41 100644 --- a/vendor.conf +++ b/vendor.conf @@ -10,7 +10,7 @@ github.com/containerd/cgroups 7a5fdd8330119dc70d850260db8f3594d89d6943 github.com/containerd/continuity master github.com/containernetworking/cni v0.4.0 github.com/containernetworking/plugins master -github.com/containers/image 88423e35d5f11939b0db4fb8f2939fc04adf2463 +github.com/containers/image a74645804cf01ae934fd61ca53790ec1ec89683b github.com/containers/storage ce923c1ed9e51c8fe58e41a86abc05be7b824f62 github.com/coreos/go-systemd v14 github.com/cri-o/ocicni master diff --git a/vendor/github.com/containers/image/docker/daemon/daemon_transport.go b/vendor/github.com/containers/image/docker/daemon/daemon_transport.go index b083628ae32..1a265bf76ee 100644 --- a/vendor/github.com/containers/image/docker/daemon/daemon_transport.go +++ b/vendor/github.com/containers/image/docker/daemon/daemon_transport.go @@ -2,13 +2,15 @@ package daemon import ( "context" - "github.com/pkg/errors" + "fmt" + "github.com/containers/image/docker/policyconfiguration" "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/opencontainers/go-digest" + "github.com/pkg/errors" ) func init() { @@ -35,8 +37,15 @@ func (t daemonTransport) ParseReference(reference string) (types.ImageReference, // It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion. // scope passed to this function will not be "", that value is always allowed. func (t daemonTransport) ValidatePolicyConfigurationScope(scope string) error { - // See the explanation in daemonReference.PolicyConfigurationIdentity. - return errors.New(`docker-daemon: does not support any scopes except the default "" one`) + // ID values cannot be effectively namespaced, and are clearly invalid host:port values. + if _, err := digest.Parse(scope); err == nil { + return errors.Errorf(`docker-daemon: can not use algo:digest value %s as a namespace`, scope) + } + + // FIXME? We could be verifying the various character set and length restrictions + // from docker/distribution/reference.regexp.go, but other than that there + // are few semantically invalid strings. + return nil } // daemonReference is an ImageReference for images managed by a local Docker daemon @@ -88,6 +97,8 @@ func NewReference(id digest.Digest, ref reference.Named) (types.ImageReference, // A github.com/distribution/reference value can have a tag and a digest at the same time! // Most versions of docker/reference do not handle that (ignoring the tag), so reject such input. // This MAY be accepted in the future. + // (Even if it were supported, the semantics of policy namespaces are unclear - should we drop + // the tag or the digest first?) _, isTagged := ref.(reference.NamedTagged) _, isDigested := ref.(reference.Canonical) if isTagged && isDigested { @@ -137,9 +148,28 @@ func (ref daemonReference) DockerReference() reference.Named { // Returns "" if configuration identities for these references are not supported. func (ref daemonReference) PolicyConfigurationIdentity() string { // We must allow referring to images in the daemon by image ID, otherwise untagged images would not be accessible. - // But the existence of image IDs means that we can’t truly well namespace the input; the untagged images would have to fall into the default policy, - // which can be unexpected. So, punt. - return "" // This still allows using the default "" scope to define a policy for this transport. + // But the existence of image IDs means that we can’t truly well namespace the input: + // a single image can be namespaced either using the name or the ID depending on how it is named. + // + // That’s fairly unexpected, but we have to cope somehow. + // + // So, use the ordinary docker/policyconfiguration namespacing for named images. + // image IDs all fall into the root namespace. + // Users can set up the root namespace to be either untrusted or rejected, + // and to set up specific trust for named namespaces. This allows verifying image + // identity when a name is known, and unnamed images would be untrusted or rejected. + switch { + case ref.id != "": + return "" // This still allows using the default "" scope to define a global policy for ID-identified images. + case ref.ref != nil: + res, err := policyconfiguration.DockerReferenceIdentity(ref.ref) + if res == "" || err != nil { // Coverage: Should never happen, NewReference above should refuse values which could cause a failure. + panic(fmt.Sprintf("Internal inconsistency: policyconfiguration.DockerReferenceIdentity returned %#v, %v", res, err)) + } + return res + default: // Coverage: Should never happen, NewReference above should refuse such values. + panic("Internal inconsistency: daemonReference has empty id and nil ref") + } } // PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search @@ -149,7 +179,14 @@ func (ref daemonReference) PolicyConfigurationIdentity() string { // and each following element to be a prefix of the element preceding it. func (ref daemonReference) PolicyConfigurationNamespaces() []string { // See the explanation in daemonReference.PolicyConfigurationIdentity. - return []string{} + switch { + case ref.id != "": + return []string{} + case ref.ref != nil: + return policyconfiguration.DockerReferenceNamespaces(ref.ref) + default: // Coverage: Should never happen, NewReference above should refuse such values. + panic("Internal inconsistency: daemonReference has empty id and nil ref") + } } // NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport. diff --git a/vendor/github.com/containers/image/docker/docker_image.go b/vendor/github.com/containers/image/docker/docker_image.go index 7f8ad858ef1..01079124174 100644 --- a/vendor/github.com/containers/image/docker/docker_image.go +++ b/vendor/github.com/containers/image/docker/docker_image.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "strings" "github.com/containers/image/docker/reference" "github.com/containers/image/image" @@ -39,25 +41,67 @@ func (i *Image) SourceRefFullName() string { return i.src.ref.ref.Name() } -// GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any. +// GetRepositoryTags list all tags available in the repository. The tag +// provided inside the ImageReference will be ignored. (This is a +// backward-compatible shim method which calls the module-level +// GetRepositoryTags) func (i *Image) GetRepositoryTags(ctx context.Context) ([]string, error) { - path := fmt.Sprintf(tagsPath, reference.Path(i.src.ref.ref)) - // FIXME: Pass the context.Context - res, err := i.src.c.makeRequest(ctx, "GET", path, nil, nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - // print url also - return nil, errors.Errorf("Invalid status code returned when fetching tags list %d", res.StatusCode) + return GetRepositoryTags(ctx, i.src.c.sys, i.src.ref) +} + +// GetRepositoryTags list all tags available in the repository. The tag +// provided inside the ImageReference will be ignored. +func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) ([]string, error) { + dr, ok := ref.(dockerReference) + if !ok { + return nil, errors.Errorf("ref must be a dockerReference") } - type tagsRes struct { - Tags []string + + path := fmt.Sprintf(tagsPath, reference.Path(dr.ref)) + client, err := newDockerClientFromRef(sys, dr, false, "pull") + if err != nil { + return nil, errors.Wrap(err, "failed to create client") } - tags := &tagsRes{} - if err := json.NewDecoder(res.Body).Decode(tags); err != nil { - return nil, err + + tags := make([]string, 0) + + for { + res, err := client.makeRequest(ctx, "GET", path, nil, nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + // print url also + return nil, errors.Errorf("Invalid status code returned when fetching tags list %d", res.StatusCode) + } + + var tagsHolder struct { + Tags []string + } + if err = json.NewDecoder(res.Body).Decode(&tagsHolder); err != nil { + return nil, err + } + tags = append(tags, tagsHolder.Tags...) + + link := res.Header.Get("Link") + if link == "" { + break + } + + linkURLStr := strings.Trim(strings.Split(link, ";")[0], "<>") + linkURL, err := url.Parse(linkURLStr) + if err != nil { + return tags, err + } + + // can be relative or absolute, but we only want the path (and I + // guess we're in trouble if it forwards to a new place...) + path = linkURL.Path + if linkURL.RawQuery != "" { + path += "?" + path += linkURL.RawQuery + } } - return tags.Tags, nil + return tags, nil } diff --git a/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go new file mode 100644 index 00000000000..ce07824d222 --- /dev/null +++ b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go @@ -0,0 +1,322 @@ +package sysregistriesv2 + +import ( + "fmt" + "io/ioutil" + "net/url" + "path/filepath" + "strings" + + "github.com/BurntSushi/toml" + "github.com/containers/image/types" +) + +// systemRegistriesConfPath is the path to the system-wide registry +// configuration file and is used to add/subtract potential registries for +// obtaining images. You can override this at build time with +// -ldflags '-X github.com/containers/image/sysregistries.systemRegistriesConfPath=$your_path' +var systemRegistriesConfPath = builtinRegistriesConfPath + +// builtinRegistriesConfPath is the path to the registry configuration file. +// DO NOT change this, instead see systemRegistriesConfPath above. +const builtinRegistriesConfPath = "/etc/containers/registries.conf" + +// tomlURL is an abstraction required to unmarshal the non-primitive and +// non-local url.URL type when loading the toml config. +type tomlURL struct { + url url.URL +} + +// UnmashalText interprets and parses text as a net.URL type and assigns it to +// the tomlURL's url field. +func (r *tomlURL) UnmarshalText(text []byte) (err error) { + r.url, err = parseURL(string(text)) + return err +} + +// mirror is an internal type including all mirror data but the URL. +type mirror struct { + // If true, certs verification will be skipped and HTTP (non-TLS) + // connections will be allowed. + Insecure bool `toml:"insecure"` +} + +// Mirror represents a mirror. Mirrors can be used as pull-through caches for +// registries. +type Mirror struct { + // The mirror's URL. + URL url.URL + mirror +} + +// tomlMirror is a serializable Mirror. +type tomlMirror struct { + // Serializable mirror URL. + URL tomlURL `toml:"url"` + mirror +} + +// toMirror transforms tmirror to a Mirror. +func (tmir *tomlMirror) toMirror() (Mirror, error) { + if len(tmir.URL.url.String()) == 0 { + return Mirror{}, fmt.Errorf("mirror must include a URL") + } + mir := Mirror{URL: tmir.URL.url, mirror: tmir.mirror} + return mir, nil +} + +// registry is an internal type including all registry data but the URL and the +// array of associated mirrors. +type registry struct { + // If true, pulling from the registry will be blocked. + Blocked bool `toml:"blocked"` + // If true, certs verification will be skipped and HTTP (non-TLS) + // connections will be allowed. + Insecure bool `toml:"insecure"` + // If true, the registry can be used when pulling an unqualified image. + Search bool `toml:"unqualified-search"` + // Prefix is used for matching images, and to translate one namespace to + // another. If `Prefix="example.com/bar"`, `URL="https://example.com/foo/bar"` + // and we pull from "example.com/bar/myimage:latest", the image will + // effectively be pulled from https://example.com/foo/bar/myimage:latest. + // If no Prefix is specified, it defaults to the specified URL. + Prefix string `toml:"prefix"` +} + +// Registry represents a registry. +type Registry struct { + // Serializable registry URL. + URL url.URL + // The registry's mirrors. + Mirrors []Mirror + registry +} + +// tomlRegistry is serializable Registry. +type tomlRegistry struct { + URL tomlURL `toml:"url"` + Mirrors []tomlMirror `toml:"mirror"` + registry +} + +// stripURIScheme strips the URI scheme from the given URL and returns it as +// as string. +func stripURIScheme(url url.URL) string { + return strings.TrimPrefix(url.String(), url.Scheme+"://") +} + +// toRegistry transforms treg to a Registry. +func (treg *tomlRegistry) toRegistry() (Registry, error) { + if len(treg.URL.url.String()) == 0 { + return Registry{}, fmt.Errorf("registry must include a URL") + } + reg := Registry{URL: treg.URL.url, registry: treg.registry} + // if no prefix is specified, default to the specified URL with + // stripped URI scheme + if reg.Prefix == "" { + reg.Prefix = stripURIScheme(reg.URL) + } + + for _, tmir := range treg.Mirrors { + mir, err := tmir.toMirror() + if err != nil { + return Registry{}, err + } + reg.Mirrors = append(reg.Mirrors, mir) + } + return reg, nil +} + +// backwards compatability to sysregistries v1 +type v1TOMLregistries struct { + Registries []string `toml:"registries"` +} + +// tomlConfig is the data type used to unmarshal the toml config. +type tomlConfig struct { + TOMLRegistries []tomlRegistry `toml:"registry"` + // backwards compatability to sysregistries v1 + V1Registries struct { + Search v1TOMLregistries `toml:"search"` + Insecure v1TOMLregistries `toml:"insecure"` + Block v1TOMLregistries `toml:"block"` + } `toml:"registries"` +} + +// parseURL parses the input string, performs some sanity checks and returns +// a url.URL. The input must be a valid URI with an "http" or "https" scheme, +// a specified host and an empty URI user. Otherwise, an error is returned. +func parseURL(input string) (url.URL, error) { + input = strings.TrimRight(input, "/") + + uri, err := url.Parse(input) + if err != nil { + return url.URL{}, fmt.Errorf("error parsing URL %s: %v", input, err) + } + + // only https and http are valid URI schemes + if uri.Scheme == "" { + return url.URL{}, fmt.Errorf("unspecified URI scheme: %s", input) + } + if uri.Scheme != "https" && uri.Scheme != "http" { + return url.URL{}, fmt.Errorf("unsupported URI scheme: %s", input) + } + + // a host must be specified + if uri.Host == "" { + return url.URL{}, fmt.Errorf("unspecified URI host: %s", input) + } + + // user must be empty + if uri.User != nil { + // strip password for security reasons + uri.User = url.UserPassword(uri.User.Username(), "xxxxxx") + return url.URL{}, fmt.Errorf("unsupported username/password: %q", uri) + } + + return *uri, nil +} + +// getV1Registries transforms v1 registries in the config into an array of v2 +// registries of type Registry. +func getV1Registries(config *tomlConfig) ([]Registry, error) { + regMap := make(map[string]*Registry) + + getRegistry := func(s string) (*Registry, error) { // Note: _pointer_ to a long-lived object + url, err := parseURL(s) + if err != nil { + return nil, err + } + prefix := stripURIScheme(url) + reg, exists := regMap[prefix] + if !exists { + reg = &Registry{URL: url, + Mirrors: []Mirror{}, + registry: registry{Prefix: prefix}} + regMap[prefix] = reg + } + return reg, nil + } + + for _, search := range config.V1Registries.Search.Registries { + reg, err := getRegistry(search) + if err != nil { + return nil, err + } + reg.Search = true + } + for _, blocked := range config.V1Registries.Block.Registries { + reg, err := getRegistry(blocked) + if err != nil { + return nil, err + } + reg.Blocked = true + } + for _, insecure := range config.V1Registries.Insecure.Registries { + reg, err := getRegistry(insecure) + if err != nil { + return nil, err + } + reg.Insecure = true + } + + registries := []Registry{} + for _, reg := range regMap { + registries = append(registries, *reg) + } + return registries, nil +} + +// GetRegistries loads and returns the registries specified in the config. +func GetRegistries(ctx *types.SystemContext) ([]Registry, error) { + config, err := loadRegistryConf(ctx) + if err != nil { + return nil, err + } + + registries := []Registry{} + for _, treg := range config.TOMLRegistries { + reg, err := treg.toRegistry() + if err != nil { + return nil, err + } + registries = append(registries, reg) + } + + // backwards compatibility for v1 configs + v1Registries, err := getV1Registries(config) + if err != nil { + return nil, err + } + if len(v1Registries) > 0 { + if len(registries) > 0 { + return nil, fmt.Errorf("mixing sysregistry v1/v2 is not supported") + } + registries = v1Registries + } + + return registries, nil +} + +// FindUnqualifiedSearchRegistries returns all registries that are configured +// for unqualified image search (i.e., with Registry.Search == true). +func FindUnqualifiedSearchRegistries(registries []Registry) []Registry { + unqualified := []Registry{} + for _, reg := range registries { + if reg.Search { + unqualified = append(unqualified, reg) + } + } + return unqualified +} + +// FindRegistry returns the Registry with the longest prefix for ref. If no +// Registry prefixes the image, nil is returned. +func FindRegistry(ref string, registries []Registry) *Registry { + reg := Registry{} + prefixLen := 0 + for _, r := range registries { + if strings.HasPrefix(ref, r.Prefix) { + length := len(r.Prefix) + if length > prefixLen { + reg = r + prefixLen = length + } + } + } + if prefixLen != 0 { + return ® + } + return nil +} + +// Reads the global registry file from the filesystem. Returns a byte array. +func readRegistryConf(ctx *types.SystemContext) ([]byte, error) { + dirPath := systemRegistriesConfPath + if ctx != nil { + if ctx.SystemRegistriesConfPath != "" { + dirPath = ctx.SystemRegistriesConfPath + } else if ctx.RootForImplicitAbsolutePaths != "" { + dirPath = filepath.Join(ctx.RootForImplicitAbsolutePaths, systemRegistriesConfPath) + } + } + configBytes, err := ioutil.ReadFile(dirPath) + return configBytes, err +} + +// Used in unittests to parse custom configs without a types.SystemContext. +var readConf = readRegistryConf + +// Loads the registry configuration file from the filesystem and then unmarshals +// it. Returns the unmarshalled object. +func loadRegistryConf(ctx *types.SystemContext) (*tomlConfig, error) { + config := &tomlConfig{} + + configBytes, err := readConf(ctx) + if err != nil { + return nil, err + } + + err = toml.Unmarshal(configBytes, &config) + return config, err +}