diff options
Diffstat (limited to 'vendor')
-rw-r--r-- | vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go | 369 |
1 files changed, 369 insertions, 0 deletions
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 000000000..9509c6024 --- /dev/null +++ b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go @@ -0,0 +1,369 @@ +package sysregistriesv2 + +import ( + "fmt" + "io/ioutil" + "net/url" + "path/filepath" + "strings" + "sync" + + "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" + +// Mirror represents a mirror. Mirrors can be used as pull-through caches for +// registries. +type Mirror struct { + // The mirror's URL. + URL string `toml:"url"` + // If true, certs verification will be skipped and HTTP (non-TLS) + // connections will be allowed. + Insecure bool `toml:"insecure"` +} + +// Registry represents a registry. +type Registry struct { + // Serializable registry URL. + URL string `toml:"url"` + // The registry's mirrors. + Mirrors []Mirror `toml:"mirror"` + // 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="example.com/foo/bar"` + // and we pull from "example.com/bar/myimage:latest", the image will + // effectively be pulled from "example.com/foo/bar/myimage:latest". + // If no Prefix is specified, it defaults to the specified URL. + Prefix string `toml:"prefix"` +} + +// 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 { + Registries []Registry `toml:"registry"` + // backwards compatability to sysregistries v1 + V1Registries struct { + Search v1TOMLregistries `toml:"search"` + Insecure v1TOMLregistries `toml:"insecure"` + Block v1TOMLregistries `toml:"block"` + } `toml:"registries"` +} + +// InvalidRegistries represents an invalid registry configurations. An example +// is when "registry.com" is defined multiple times in the configuration but +// with conflicting security settings. +type InvalidRegistries struct { + s string +} + +// Error returns the error string. +func (e *InvalidRegistries) Error() string { + return e.s +} + +// parseURL parses the input string, performs some sanity checks and returns +// the sanitized input string. An error is returned in case parsing fails or +// or if URI scheme or user is set. +func parseURL(input string) (string, error) { + trimmed := strings.TrimRight(input, "/") + + if trimmed == "" { + return "", &InvalidRegistries{s: "invalid URL: cannot be empty"} + } + + // Ultimately, we expect input of the form example.com[/namespace/…], a prefix + // of a fully-expended reference (containers/image/docker/Reference.String()). + // c/image/docker/Reference does not currently provide such a parser. + // So, we use url.Parse("http://"+trimmed) below to ~verify the format, possibly + // letting some invalid input in, trading that off for a simpler parser. + // + // url.Parse("http://"+trimmed) is, sadly, too permissive, notably for + // trimmed == "http://example.com/…", url.Parse("http://http://example.com/…") + // is accepted and parsed as + // {Scheme: "http", Host: "http:", Path: "//example.com/…"}. + // + // So, first we do an explicit check for an unwanted scheme prefix: + + // This will parse trimmed=="http://example.com/…" with Scheme: "http". Perhaps surprisingly, + // it also succeeds for the input we want to accept, in different ways: + // "example.com" -> {Scheme:"", Opaque:"", Path:"example.com"} + // "example.com/repo" -> {Scheme:"", Opaque:"", Path:"example.com/repo"} + // "example.com:5000" -> {Scheme:"example.com", Opaque:"5000"} + // "example.com:5000/repo" -> {Scheme:"example.com", Opaque:"5000/repo"} + uri, err := url.Parse(trimmed) + if err != nil { + return "", &InvalidRegistries{s: fmt.Sprintf("invalid URL '%s': %v", input, err)} + } + + // Check if a URI Scheme is set. + // Note that URLs that do not start with a slash after the scheme are + // interpreted as `scheme:opaque[?query][#fragment]`; see above for examples. + if uri.Scheme != "" && uri.Opaque == "" { + msg := fmt.Sprintf("invalid URL '%s': URI schemes are not supported", input) + return "", &InvalidRegistries{s: msg} + } + + uri, err = url.Parse("http://" + trimmed) + if err != nil { + msg := fmt.Sprintf("invalid URL '%s': sanitized URL did not parse: %v", input, err) + return "", &InvalidRegistries{s: msg} + } + + if uri.User != nil { + msg := fmt.Sprintf("invalid URL '%s': user/password are not supported", trimmed) + return "", &InvalidRegistries{s: msg} + } + + return trimmed, 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(url string) (*Registry, error) { // Note: _pointer_ to a long-lived object + var err error + url, err = parseURL(url) + if err != nil { + return nil, err + } + reg, exists := regMap[url] + if !exists { + reg = &Registry{ + URL: url, + Mirrors: []Mirror{}, + Prefix: url, + } + regMap[url] = 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 +} + +// postProcessRegistries checks the consistency of all registries (e.g., set +// the Prefix to URL if not set) and applies conflict checks. It returns an +// array of cleaned registries and error in case of conflicts. +func postProcessRegistries(regs []Registry) ([]Registry, error) { + var registries []Registry + regMap := make(map[string][]Registry) + + for _, reg := range regs { + var err error + + // make sure URL and Prefix are valid + reg.URL, err = parseURL(reg.URL) + if err != nil { + return nil, err + } + + if reg.Prefix == "" { + reg.Prefix = reg.URL + } else { + reg.Prefix, err = parseURL(reg.Prefix) + if err != nil { + return nil, err + } + } + + // make sure mirrors are valid + for _, mir := range reg.Mirrors { + mir.URL, err = parseURL(mir.URL) + if err != nil { + return nil, err + } + } + registries = append(registries, reg) + regMap[reg.URL] = append(regMap[reg.URL], reg) + } + + // Given a registry can be mentioned multiple times (e.g., to have + // multiple prefixes backed by different mirrors), we need to make sure + // there are no conflicts among them. + // + // Note: we need to iterate over the registries array to ensure a + // deterministic behavior which is not guaranteed by maps. + for _, reg := range registries { + others, _ := regMap[reg.URL] + for _, other := range others { + if reg.Insecure != other.Insecure { + msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'insecure' setting", reg.URL) + + return nil, &InvalidRegistries{s: msg} + } + if reg.Blocked != other.Blocked { + msg := fmt.Sprintf("registry '%s' is defined multiple times with conflicting 'blocked' setting", reg.URL) + return nil, &InvalidRegistries{s: msg} + } + } + } + + return registries, nil +} + +// getConfigPath returns the system-registries config path if specified. +// Otherwise, systemRegistriesConfPath is returned. +func getConfigPath(ctx *types.SystemContext) string { + confPath := systemRegistriesConfPath + if ctx != nil { + if ctx.SystemRegistriesConfPath != "" { + confPath = ctx.SystemRegistriesConfPath + } else if ctx.RootForImplicitAbsolutePaths != "" { + confPath = filepath.Join(ctx.RootForImplicitAbsolutePaths, systemRegistriesConfPath) + } + } + return confPath +} + +// configMutex is used to synchronize concurrent accesses to configCache. +var configMutex = sync.Mutex{} + +// configCache caches already loaded configs with config paths as keys and is +// used to avoid redudantly parsing configs. Concurrent accesses to the cache +// are synchronized via configMutex. +var configCache = make(map[string][]Registry) + +// GetRegistries loads and returns the registries specified in the config. +func GetRegistries(ctx *types.SystemContext) ([]Registry, error) { + configPath := getConfigPath(ctx) + + configMutex.Lock() + defer configMutex.Unlock() + // if the config has already been loaded, return the cached registries + if registries, inCache := configCache[configPath]; inCache { + return registries, nil + } + + // load the config + config, err := loadRegistryConf(configPath) + if err != nil { + return nil, err + } + + registries := config.Registries + + // 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, &InvalidRegistries{s: "mixing sysregistry v1/v2 is not supported"} + } + registries = v1Registries + } + + registries, err = postProcessRegistries(registries) + if err != nil { + return nil, err + } + + // populate the cache + configCache[configPath] = registries + + return registries, err +} + +// 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(configPath string) ([]byte, error) { + configBytes, err := ioutil.ReadFile(configPath) + 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(configPath string) (*tomlConfig, error) { + config := &tomlConfig{} + + configBytes, err := readConf(configPath) + if err != nil { + return nil, err + } + + err = toml.Unmarshal(configBytes, &config) + return config, err +} |