+
+ |
+ IRC
+ |
+
+ #docker-distribution on FreeNode
+ |
+
+
+ |
+ Issue Tracker
+ |
+
+ github.com/docker/distribution/issues
+ |
+
+
+ |
+ Google Groups
+ |
+
+ https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
+ |
+
+
+ |
+ Mailing List
+ |
+
+ docker@dockerproject.org
+ |
+
+
+
+
+## License
+
+This project is distributed under [Apache License, Version 2.0](LICENSE).
diff --git a/vendor/github.com/docker/distribution/ROADMAP.md b/vendor/github.com/docker/distribution/ROADMAP.md
new file mode 100644
index 000000000..701127afe
--- /dev/null
+++ b/vendor/github.com/docker/distribution/ROADMAP.md
@@ -0,0 +1,267 @@
+# Roadmap
+
+The Distribution Project consists of several components, some of which are
+still being defined. This document defines the high-level goals of the
+project, identifies the current components, and defines the release-
+relationship to the Docker Platform.
+
+* [Distribution Goals](#distribution-goals)
+* [Distribution Components](#distribution-components)
+* [Project Planning](#project-planning): release-relationship to the Docker Platform.
+
+This road map is a living document, providing an overview of the goals and
+considerations made in respect of the future of the project.
+
+## Distribution Goals
+
+- Replace the existing [docker registry](github.com/docker/docker-registry)
+ implementation as the primary implementation.
+- Replace the existing push and pull code in the docker engine with the
+ distribution package.
+- Define a strong data model for distributing docker images
+- Provide a flexible distribution tool kit for use in the docker platform
+- Unlock new distribution models
+
+## Distribution Components
+
+Components of the Distribution Project are managed via github [milestones](https://github.com/docker/distribution/milestones). Upcoming
+features and bugfixes for a component will be added to the relevant milestone. If a feature or
+bugfix is not part of a milestone, it is currently unscheduled for
+implementation.
+
+* [Registry](#registry)
+* [Distribution Package](#distribution-package)
+
+***
+
+### Registry
+
+The new Docker registry is the main portion of the distribution repository.
+Registry 2.0 is the first release of the next-generation registry. This was
+primarily focused on implementing the [new registry
+API](https://github.com/docker/distribution/blob/master/docs/spec/api.md),
+with a focus on security and performance.
+
+Following from the Distribution project goals above, we have a set of goals
+for registry v2 that we would like to follow in the design. New features
+should be compared against these goals.
+
+#### Data Storage and Distribution First
+
+The registry's first goal is to provide a reliable, consistent storage
+location for Docker images. The registry should only provide the minimal
+amount of indexing required to fetch image data and no more.
+
+This means we should be selective in new features and API additions, including
+those that may require expensive, ever growing indexes. Requests should be
+servable in "constant time".
+
+#### Content Addressability
+
+All data objects used in the registry API should be content addressable.
+Content identifiers should be secure and verifiable. This provides a secure,
+reliable base from which to build more advanced content distribution systems.
+
+#### Content Agnostic
+
+In the past, changes to the image format would require large changes in Docker
+and the Registry. By decoupling the distribution and image format, we can
+allow the formats to progress without having to coordinate between the two.
+This means that we should be focused on decoupling Docker from the registry
+just as much as decoupling the registry from Docker. Such an approach will
+allow us to unlock new distribution models that haven't been possible before.
+
+We can take this further by saying that the new registry should be content
+agnostic. The registry provides a model of names, tags, manifests and content
+addresses and that model can be used to work with content.
+
+#### Simplicity
+
+The new registry should be closer to a microservice component than its
+predecessor. This means it should have a narrower API and a low number of
+service dependencies. It should be easy to deploy.
+
+This means that other solutions should be explored before changing the API or
+adding extra dependencies. If functionality is required, can it be added as an
+extension or companion service.
+
+#### Extensibility
+
+The registry should provide extension points to add functionality. By keeping
+the scope narrow, but providing the ability to add functionality.
+
+Features like search, indexing, synchronization and registry explorers fall
+into this category. No such feature should be added unless we've found it
+impossible to do through an extension.
+
+#### Active Feature Discussions
+
+The following are feature discussions that are currently active.
+
+If you don't see your favorite, unimplemented feature, feel free to contact us
+via IRC or the mailing list and we can talk about adding it. The goal here is
+to make sure that new features go through a rigid design process before
+landing in the registry.
+
+##### Proxying to other Registries
+
+A _pull-through caching_ mode exists for the registry, but is restricted from
+within the docker client to only mirror the official Docker Hub. This functionality
+can be expanded when image provenance has been specified and implemented in the
+distribution project.
+
+##### Metadata storage
+
+Metadata for the registry is currently stored with the manifest and layer data on
+the storage backend. While this is a big win for simplicity and reliably maintaining
+state, it comes with the cost of consistency and high latency. The mutable registry
+metadata operations should be abstracted behind an API which will allow ACID compliant
+storage systems to handle metadata.
+
+##### Peer to Peer transfer
+
+Discussion has started here: https://docs.google.com/document/d/1rYDpSpJiQWmCQy8Cuiaa3NH-Co33oK_SC9HeXYo87QA/edit
+
+##### Indexing, Search and Discovery
+
+The original registry provided some implementation of search for use with
+private registries. Support has been elided from V2 since we'd like to both
+decouple search functionality from the registry. The makes the registry
+simpler to deploy, especially in use cases where search is not needed, and
+let's us decouple the image format from the registry.
+
+There are explorations into using the catalog API and notification system to
+build external indexes. The current line of thought is that we will define a
+common search API to index and query docker images. Such a system could be run
+as a companion to a registry or set of registries to power discovery.
+
+The main issue with search and discovery is that there are so many ways to
+accomplish it. There are two aspects to this project. The first is deciding on
+how it will be done, including an API definition that can work with changing
+data formats. The second is the process of integrating with `docker search`.
+We expect that someone attempts to address the problem with the existing tools
+and propose it as a standard search API or uses it to inform a standardization
+process. Once this has been explored, we integrate with the docker client.
+
+Please see the following for more detail:
+
+- https://github.com/docker/distribution/issues/206
+
+##### Deletes
+
+> __NOTE:__ Deletes are a much asked for feature. Before requesting this
+feature or participating in discussion, we ask that you read this section in
+full and understand the problems behind deletes.
+
+While, at first glance, implementing deleting seems simple, there are a number
+mitigating factors that make many solutions not ideal or even pathological in
+the context of a registry. The following paragraph discuss the background and
+approaches that could be applied to arrive at a solution.
+
+The goal of deletes in any system is to remove unused or unneeded data. Only
+data requested for deletion should be removed and no other data. Removing
+unintended data is worse than _not_ removing data that was requested for
+removal but ideally, both are supported. Generally, according to this rule, we
+err on holding data longer than needed, ensuring that it is only removed when
+we can be certain that it can be removed. With the current behavior, we opt to
+hold onto the data forever, ensuring that data cannot be incorrectly removed.
+
+To understand the problems with implementing deletes, one must understand the
+data model. All registry data is stored in a filesystem layout, implemented on
+a "storage driver", effectively a _virtual file system_ (VFS). The storage
+system must assume that this VFS layer will be eventually consistent and has
+poor read- after-write consistency, since this is the lower common denominator
+among the storage drivers. This is mitigated by writing values in reverse-
+dependent order, but makes wider transactional operations unsafe.
+
+Layered on the VFS model is a content-addressable _directed, acyclic graph_
+(DAG) made up of blobs. Manifests reference layers. Tags reference manifests.
+Since the same data can be referenced by multiple manifests, we only store
+data once, even if it is in different repositories. Thus, we have a set of
+blobs, referenced by tags and manifests. If we want to delete a blob we need
+to be certain that it is no longer referenced by another manifest or tag. When
+we delete a manifest, we also can try to delete the referenced blobs. Deciding
+whether or not a blob has an active reference is the crux of the problem.
+
+Conceptually, deleting a manifest and its resources is quite simple. Just find
+all the manifests, enumerate the referenced blobs and delete the blobs not in
+that set. An astute observer will recognize this as a garbage collection
+problem. As with garbage collection in programming languages, this is very
+simple when one always has a consistent view. When one adds parallelism and an
+inconsistent view of data, it becomes very challenging.
+
+A simple example can demonstrate this. Let's say we are deleting a manifest
+_A_ in one process. We scan the manifest and decide that all the blobs are
+ready for deletion. Concurrently, we have another process accepting a new
+manifest _B_ referencing one or more blobs from the manifest _A_. Manifest _B_
+is accepted and all the blobs are considered present, so the operation
+proceeds. The original process then deletes the referenced blobs, assuming
+they were unreferenced. The manifest _B_, which we thought had all of its data
+present, can no longer be served by the registry, since the dependent data has
+been deleted.
+
+Deleting data from the registry safely requires some way to coordinate this
+operation. The following approaches are being considered:
+
+- _Reference Counting_ - Maintain a count of references to each blob. This is
+ challenging for a number of reasons: 1. maintaining a consistent consensus
+ of reference counts across a set of Registries and 2. Building the initial
+ list of reference counts for an existing registry. These challenges can be
+ met with a consensus protocol like Paxos or Raft in the first case and a
+ necessary but simple scan in the second..
+- _Lock the World GC_ - Halt all writes to the data store. Walk the data store
+ and find all blob references. Delete all unreferenced blobs. This approach
+ is very simple but requires disabling writes for a period of time while the
+ service reads all data. This is slow and expensive but very accurate and
+ effective.
+- _Generational GC_ - Do something similar to above but instead of blocking
+ writes, writes are sent to another storage backend while reads are broadcast
+ to the new and old backends. GC is then performed on the read-only portion.
+ Because writes land in the new backend, the data in the read-only section
+ can be safely deleted. The main drawbacks of this approach are complexity
+ and coordination.
+- _Centralized Oracle_ - Using a centralized, transactional database, we can
+ know exactly which data is referenced at any given time. This avoids
+ coordination problem by managing this data in a single location. We trade
+ off metadata scalability for simplicity and performance. This is a very good
+ option for most registry deployments. This would create a bottleneck for
+ registry metadata. However, metadata is generally not the main bottleneck
+ when serving images.
+
+Please let us know if other solutions exist that we have yet to enumerate.
+Note that for any approach, implementation is a massive consideration. For
+example, a mark-sweep based solution may seem simple but the amount of work in
+coordination offset the extra work it might take to build a _Centralized
+Oracle_. We'll accept proposals for any solution but please coordinate with us
+before dropping code.
+
+At this time, we have traded off simplicity and ease of deployment for disk
+space. Simplicity and ease of deployment tend to reduce developer involvement,
+which is currently the most expensive resource in software engineering. Taking
+on any solution for deletes will greatly effect these factors, trading off
+very cheap disk space for a complex deployment and operational story.
+
+Please see the following issues for more detail:
+
+- https://github.com/docker/distribution/issues/422
+- https://github.com/docker/distribution/issues/461
+- https://github.com/docker/distribution/issues/462
+
+### Distribution Package
+
+At its core, the Distribution Project is a set of Go packages that make up
+Distribution Components. At this time, most of these packages make up the
+Registry implementation.
+
+The package itself is considered unstable. If you're using it, please take care to vendor the dependent version.
+
+For feature additions, please see the Registry section. In the future, we may break out a
+separate Roadmap for distribution-specific features that apply to more than
+just the registry.
+
+***
+
+### Project Planning
+
+An [Open-Source Planning Process](https://github.com/docker/distribution/wiki/Open-Source-Planning-Process) is used to define the Roadmap. [Project Pages](https://github.com/docker/distribution/wiki) define the goals for each Milestone and identify current progress.
+
diff --git a/vendor/github.com/docker/distribution/blobs.go b/vendor/github.com/docker/distribution/blobs.go
new file mode 100644
index 000000000..671372abf
--- /dev/null
+++ b/vendor/github.com/docker/distribution/blobs.go
@@ -0,0 +1,265 @@
+package distribution
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+
+ "github.com/distribution/reference"
+ "github.com/opencontainers/go-digest"
+ v1 "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+var (
+ // ErrBlobExists returned when blob already exists
+ ErrBlobExists = errors.New("blob exists")
+
+ // ErrBlobDigestUnsupported when blob digest is an unsupported version.
+ ErrBlobDigestUnsupported = errors.New("unsupported blob digest")
+
+ // ErrBlobUnknown when blob is not found.
+ ErrBlobUnknown = errors.New("unknown blob")
+
+ // ErrBlobUploadUnknown returned when upload is not found.
+ ErrBlobUploadUnknown = errors.New("blob upload unknown")
+
+ // ErrBlobInvalidLength returned when the blob has an expected length on
+ // commit, meaning mismatched with the descriptor or an invalid value.
+ ErrBlobInvalidLength = errors.New("blob invalid length")
+)
+
+// ErrBlobInvalidDigest returned when digest check fails.
+type ErrBlobInvalidDigest struct {
+ Digest digest.Digest
+ Reason error
+}
+
+func (err ErrBlobInvalidDigest) Error() string {
+ return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
+ err.Digest, err.Reason)
+}
+
+// ErrBlobMounted returned when a blob is mounted from another repository
+// instead of initiating an upload session.
+type ErrBlobMounted struct {
+ From reference.Canonical
+ Descriptor Descriptor
+}
+
+func (err ErrBlobMounted) Error() string {
+ return fmt.Sprintf("blob mounted from: %v to: %v",
+ err.From, err.Descriptor)
+}
+
+// Descriptor describes targeted content. Used in conjunction with a blob
+// store, a descriptor can be used to fetch, store and target any kind of
+// blob. The struct also describes the wire protocol format. Fields should
+// only be added but never changed.
+type Descriptor struct {
+ // MediaType describe the type of the content. All text based formats are
+ // encoded as utf-8.
+ MediaType string `json:"mediaType,omitempty"`
+
+ // Size in bytes of content.
+ Size int64 `json:"size,omitempty"`
+
+ // Digest uniquely identifies the content. A byte stream can be verified
+ // against this digest.
+ Digest digest.Digest `json:"digest,omitempty"`
+
+ // URLs contains the source URLs of this content.
+ URLs []string `json:"urls,omitempty"`
+
+ // Annotations contains arbitrary metadata relating to the targeted content.
+ Annotations map[string]string `json:"annotations,omitempty"`
+
+ // Platform describes the platform which the image in the manifest runs on.
+ // This should only be used when referring to a manifest.
+ Platform *v1.Platform `json:"platform,omitempty"`
+
+ // NOTE: Before adding a field here, please ensure that all
+ // other options have been exhausted. Much of the type relationships
+ // depend on the simplicity of this type.
+}
+
+// Descriptor returns the descriptor, to make it satisfy the Describable
+// interface. Note that implementations of Describable are generally objects
+// which can be described, not simply descriptors; this exception is in place
+// to make it more convenient to pass actual descriptors to functions that
+// expect Describable objects.
+func (d Descriptor) Descriptor() Descriptor {
+ return d
+}
+
+// BlobStatter makes blob descriptors available by digest. The service may
+// provide a descriptor of a different digest if the provided digest is not
+// canonical.
+type BlobStatter interface {
+ // Stat provides metadata about a blob identified by the digest. If the
+ // blob is unknown to the describer, ErrBlobUnknown will be returned.
+ Stat(ctx context.Context, dgst digest.Digest) (Descriptor, error)
+}
+
+// BlobDeleter enables deleting blobs from storage.
+type BlobDeleter interface {
+ Delete(ctx context.Context, dgst digest.Digest) error
+}
+
+// BlobEnumerator enables iterating over blobs from storage
+type BlobEnumerator interface {
+ Enumerate(ctx context.Context, ingester func(dgst digest.Digest) error) error
+}
+
+// BlobDescriptorService manages metadata about a blob by digest. Most
+// implementations will not expose such an interface explicitly. Such mappings
+// should be maintained by interacting with the BlobIngester. Hence, this is
+// left off of BlobService and BlobStore.
+type BlobDescriptorService interface {
+ BlobStatter
+
+ // SetDescriptor assigns the descriptor to the digest. The provided digest and
+ // the digest in the descriptor must map to identical content but they may
+ // differ on their algorithm. The descriptor must have the canonical
+ // digest of the content and the digest algorithm must match the
+ // annotators canonical algorithm.
+ //
+ // Such a facility can be used to map blobs between digest domains, with
+ // the restriction that the algorithm of the descriptor must match the
+ // canonical algorithm (ie sha256) of the annotator.
+ SetDescriptor(ctx context.Context, dgst digest.Digest, desc Descriptor) error
+
+ // Clear enables descriptors to be unlinked
+ Clear(ctx context.Context, dgst digest.Digest) error
+}
+
+// BlobDescriptorServiceFactory creates middleware for BlobDescriptorService.
+type BlobDescriptorServiceFactory interface {
+ BlobAccessController(svc BlobDescriptorService) BlobDescriptorService
+}
+
+// ReadSeekCloser is the primary reader type for blob data, combining
+// io.ReadSeeker with io.Closer.
+type ReadSeekCloser interface {
+ io.ReadSeeker
+ io.Closer
+}
+
+// BlobProvider describes operations for getting blob data.
+type BlobProvider interface {
+ // Get returns the entire blob identified by digest along with the descriptor.
+ Get(ctx context.Context, dgst digest.Digest) ([]byte, error)
+
+ // Open provides a ReadSeekCloser to the blob identified by the provided
+ // descriptor. If the blob is not known to the service, an error will be
+ // returned.
+ Open(ctx context.Context, dgst digest.Digest) (ReadSeekCloser, error)
+}
+
+// BlobServer can serve blobs via http.
+type BlobServer interface {
+ // ServeBlob attempts to serve the blob, identified by dgst, via http. The
+ // service may decide to redirect the client elsewhere or serve the data
+ // directly.
+ //
+ // This handler only issues successful responses, such as 2xx or 3xx,
+ // meaning it serves data or issues a redirect. If the blob is not
+ // available, an error will be returned and the caller may still issue a
+ // response.
+ //
+ // The implementation may serve the same blob from a different digest
+ // domain. The appropriate headers will be set for the blob, unless they
+ // have already been set by the caller.
+ ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error
+}
+
+// BlobIngester ingests blob data.
+type BlobIngester interface {
+ // Put inserts the content p into the blob service, returning a descriptor
+ // or an error.
+ Put(ctx context.Context, mediaType string, p []byte) (Descriptor, error)
+
+ // Create allocates a new blob writer to add a blob to this service. The
+ // returned handle can be written to and later resumed using an opaque
+ // identifier. With this approach, one can Close and Resume a BlobWriter
+ // multiple times until the BlobWriter is committed or cancelled.
+ Create(ctx context.Context, options ...BlobCreateOption) (BlobWriter, error)
+
+ // Resume attempts to resume a write to a blob, identified by an id.
+ Resume(ctx context.Context, id string) (BlobWriter, error)
+}
+
+// BlobCreateOption is a general extensible function argument for blob creation
+// methods. A BlobIngester may choose to honor any or none of the given
+// BlobCreateOptions, which can be specific to the implementation of the
+// BlobIngester receiving them.
+// TODO (brianbland): unify this with ManifestServiceOption in the future
+type BlobCreateOption interface {
+ Apply(interface{}) error
+}
+
+// CreateOptions is a collection of blob creation modifiers relevant to general
+// blob storage intended to be configured by the BlobCreateOption.Apply method.
+type CreateOptions struct {
+ Mount struct {
+ ShouldMount bool
+ From reference.Canonical
+ // Stat allows to pass precalculated descriptor to link and return.
+ // Blob access check will be skipped if set.
+ Stat *Descriptor
+ }
+}
+
+// BlobWriter provides a handle for inserting data into a blob store.
+// Instances should be obtained from BlobWriteService.Writer and
+// BlobWriteService.Resume. If supported by the store, a writer can be
+// recovered with the id.
+type BlobWriter interface {
+ io.WriteCloser
+ io.ReaderFrom
+
+ // Size returns the number of bytes written to this blob.
+ Size() int64
+
+ // ID returns the identifier for this writer. The ID can be used with the
+ // Blob service to later resume the write.
+ ID() string
+
+ // StartedAt returns the time this blob write was started.
+ StartedAt() time.Time
+
+ // Commit completes the blob writer process. The content is verified
+ // against the provided provisional descriptor, which may result in an
+ // error. Depending on the implementation, written data may be validated
+ // against the provisional descriptor fields. If MediaType is not present,
+ // the implementation may reject the commit or assign "application/octet-
+ // stream" to the blob. The returned descriptor may have a different
+ // digest depending on the blob store, referred to as the canonical
+ // descriptor.
+ Commit(ctx context.Context, provisional Descriptor) (canonical Descriptor, err error)
+
+ // Cancel ends the blob write without storing any data and frees any
+ // associated resources. Any data written thus far will be lost. Cancel
+ // implementations should allow multiple calls even after a commit that
+ // result in a no-op. This allows use of Cancel in a defer statement,
+ // increasing the assurance that it is correctly called.
+ Cancel(ctx context.Context) error
+}
+
+// BlobService combines the operations to access, read and write blobs. This
+// can be used to describe remote blob services.
+type BlobService interface {
+ BlobStatter
+ BlobProvider
+ BlobIngester
+}
+
+// BlobStore represent the entire suite of blob related operations. Such an
+// implementation can access, read, write, delete and serve blobs.
+type BlobStore interface {
+ BlobService
+ BlobServer
+ BlobDeleter
+}
diff --git a/vendor/github.com/docker/distribution/configuration/configuration.go b/vendor/github.com/docker/distribution/configuration/configuration.go
new file mode 100644
index 000000000..7076df85d
--- /dev/null
+++ b/vendor/github.com/docker/distribution/configuration/configuration.go
@@ -0,0 +1,706 @@
+package configuration
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "reflect"
+ "strings"
+ "time"
+)
+
+// Configuration is a versioned registry configuration, intended to be provided by a yaml file, and
+// optionally modified by environment variables.
+//
+// Note that yaml field names should never include _ characters, since this is the separator used
+// in environment variable names.
+type Configuration struct {
+ // Version is the version which defines the format of the rest of the configuration
+ Version Version `yaml:"version"`
+
+ // Log supports setting various parameters related to the logging
+ // subsystem.
+ Log struct {
+ // AccessLog configures access logging.
+ AccessLog struct {
+ // Disabled disables access logging.
+ Disabled bool `yaml:"disabled,omitempty"`
+ } `yaml:"accesslog,omitempty"`
+
+ // Level is the granularity at which registry operations are logged.
+ Level Loglevel `yaml:"level,omitempty"`
+
+ // Formatter overrides the default formatter with another. Options
+ // include "text", "json" and "logstash".
+ Formatter string `yaml:"formatter,omitempty"`
+
+ // Fields allows users to specify static string fields to include in
+ // the logger context.
+ Fields map[string]interface{} `yaml:"fields,omitempty"`
+
+ // Hooks allows users to configure the log hooks, to enabling the
+ // sequent handling behavior, when defined levels of log message emit.
+ Hooks []LogHook `yaml:"hooks,omitempty"`
+ }
+
+ // Loglevel is the level at which registry operations are logged.
+ //
+ // Deprecated: Use Log.Level instead.
+ Loglevel Loglevel `yaml:"loglevel,omitempty"`
+
+ // Storage is the configuration for the registry's storage driver
+ Storage Storage `yaml:"storage"`
+
+ // Auth allows configuration of various authorization methods that may be
+ // used to gate requests.
+ Auth Auth `yaml:"auth,omitempty"`
+
+ // Middleware lists all middlewares to be used by the registry.
+ Middleware map[string][]Middleware `yaml:"middleware,omitempty"`
+
+ // Reporting is the configuration for error reporting
+ Reporting Reporting `yaml:"reporting,omitempty"`
+
+ // HTTP contains configuration parameters for the registry's http
+ // interface.
+ HTTP struct {
+ // Addr specifies the bind address for the registry instance.
+ Addr string `yaml:"addr,omitempty"`
+
+ // Net specifies the net portion of the bind address. A default empty value means tcp.
+ Net string `yaml:"net,omitempty"`
+
+ // Host specifies an externally-reachable address for the registry, as a fully
+ // qualified URL.
+ Host string `yaml:"host,omitempty"`
+
+ Prefix string `yaml:"prefix,omitempty"`
+
+ // Secret specifies the secret key which HMAC tokens are created with.
+ Secret string `yaml:"secret,omitempty"`
+
+ // RelativeURLs specifies that relative URLs should be returned in
+ // Location headers
+ RelativeURLs bool `yaml:"relativeurls,omitempty"`
+
+ // Amount of time to wait for connection to drain before shutting down when registry
+ // receives a stop signal
+ DrainTimeout time.Duration `yaml:"draintimeout,omitempty"`
+
+ // TLS instructs the http server to listen with a TLS configuration.
+ // This only support simple tls configuration with a cert and key.
+ // Mostly, this is useful for testing situations or simple deployments
+ // that require tls. If more complex configurations are required, use
+ // a proxy or make a proposal to add support here.
+ TLS struct {
+ // Certificate specifies the path to an x509 certificate file to
+ // be used for TLS.
+ Certificate string `yaml:"certificate,omitempty"`
+
+ // Key specifies the path to the x509 key file, which should
+ // contain the private portion for the file specified in
+ // Certificate.
+ Key string `yaml:"key,omitempty"`
+
+ // Specifies the CA certs for client authentication
+ // A file may contain multiple CA certificates encoded as PEM
+ ClientCAs []string `yaml:"clientcas,omitempty"`
+
+ // Specifies the lowest TLS version allowed
+ MinimumTLS string `yaml:"minimumtls,omitempty"`
+
+ // Specifies a list of cipher suites allowed
+ CipherSuites []string `yaml:"ciphersuites,omitempty"`
+
+ // LetsEncrypt is used to configuration setting up TLS through
+ // Let's Encrypt instead of manually specifying certificate and
+ // key. If a TLS certificate is specified, the Let's Encrypt
+ // section will not be used.
+ LetsEncrypt struct {
+ // CacheFile specifies cache file to use for lets encrypt
+ // certificates and keys.
+ CacheFile string `yaml:"cachefile,omitempty"`
+
+ // Email is the email to use during Let's Encrypt registration
+ Email string `yaml:"email,omitempty"`
+
+ // Hosts specifies the hosts which are allowed to obtain Let's
+ // Encrypt certificates.
+ Hosts []string `yaml:"hosts,omitempty"`
+ } `yaml:"letsencrypt,omitempty"`
+ } `yaml:"tls,omitempty"`
+
+ // Headers is a set of headers to include in HTTP responses. A common
+ // use case for this would be security headers such as
+ // Strict-Transport-Security. The map keys are the header names, and
+ // the values are the associated header payloads.
+ Headers http.Header `yaml:"headers,omitempty"`
+
+ // Debug configures the http debug interface, if specified. This can
+ // include services such as pprof, expvar and other data that should
+ // not be exposed externally. Left disabled by default.
+ Debug struct {
+ // Addr specifies the bind address for the debug server.
+ Addr string `yaml:"addr,omitempty"`
+ // Prometheus configures the Prometheus telemetry endpoint.
+ Prometheus struct {
+ Enabled bool `yaml:"enabled,omitempty"`
+ Path string `yaml:"path,omitempty"`
+ } `yaml:"prometheus,omitempty"`
+ } `yaml:"debug,omitempty"`
+
+ // HTTP2 configuration options
+ HTTP2 struct {
+ // Specifies whether the registry should disallow clients attempting
+ // to connect via http2. If set to true, only http/1.1 is supported.
+ Disabled bool `yaml:"disabled,omitempty"`
+ } `yaml:"http2,omitempty"`
+ } `yaml:"http,omitempty"`
+
+ // Notifications specifies configuration about various endpoint to which
+ // registry events are dispatched.
+ Notifications Notifications `yaml:"notifications,omitempty"`
+
+ // Redis configures the redis pool available to the registry webapp.
+ Redis struct {
+ // Addr specifies the the redis instance available to the application.
+ Addr string `yaml:"addr,omitempty"`
+
+ // Password string to use when making a connection.
+ Password string `yaml:"password,omitempty"`
+
+ // DB specifies the database to connect to on the redis instance.
+ DB int `yaml:"db,omitempty"`
+
+ DialTimeout time.Duration `yaml:"dialtimeout,omitempty"` // timeout for connect
+ ReadTimeout time.Duration `yaml:"readtimeout,omitempty"` // timeout for reads of data
+ WriteTimeout time.Duration `yaml:"writetimeout,omitempty"` // timeout for writes of data
+
+ // Pool configures the behavior of the redis connection pool.
+ Pool struct {
+ // MaxIdle sets the maximum number of idle connections.
+ MaxIdle int `yaml:"maxidle,omitempty"`
+
+ // MaxActive sets the maximum number of connections that should be
+ // opened before blocking a connection request.
+ MaxActive int `yaml:"maxactive,omitempty"`
+
+ // IdleTimeout sets the amount time to wait before closing
+ // inactive connections.
+ IdleTimeout time.Duration `yaml:"idletimeout,omitempty"`
+ } `yaml:"pool,omitempty"`
+ } `yaml:"redis,omitempty"`
+
+ Health Health `yaml:"health,omitempty"`
+ Catalog Catalog `yaml:"catalog,omitempty"`
+
+ Proxy Proxy `yaml:"proxy,omitempty"`
+
+ // Compatibility is used for configurations of working with older or deprecated features.
+ Compatibility struct {
+ // Schema1 configures how schema1 manifests will be handled
+ Schema1 struct {
+ // TrustKey is the signing key to use for adding the signature to
+ // schema1 manifests.
+ TrustKey string `yaml:"signingkeyfile,omitempty"`
+ // Enabled determines if schema1 manifests should be pullable
+ Enabled bool `yaml:"enabled,omitempty"`
+ } `yaml:"schema1,omitempty"`
+ } `yaml:"compatibility,omitempty"`
+
+ // Validation configures validation options for the registry.
+ Validation struct {
+ // Enabled enables the other options in this section. This field is
+ // deprecated in favor of Disabled.
+ Enabled bool `yaml:"enabled,omitempty"`
+ // Disabled disables the other options in this section.
+ Disabled bool `yaml:"disabled,omitempty"`
+ // Manifests configures manifest validation.
+ Manifests struct {
+ // URLs configures validation for URLs in pushed manifests.
+ URLs struct {
+ // Allow specifies regular expressions (https://godoc.org/regexp/syntax)
+ // that URLs in pushed manifests must match.
+ Allow []string `yaml:"allow,omitempty"`
+ // Deny specifies regular expressions (https://godoc.org/regexp/syntax)
+ // that URLs in pushed manifests must not match.
+ Deny []string `yaml:"deny,omitempty"`
+ } `yaml:"urls,omitempty"`
+ } `yaml:"manifests,omitempty"`
+ } `yaml:"validation,omitempty"`
+
+ // Policy configures registry policy options.
+ Policy struct {
+ // Repository configures policies for repositories
+ Repository struct {
+ // Classes is a list of repository classes which the
+ // registry allows content for. This class is matched
+ // against the configuration media type inside uploaded
+ // manifests. When non-empty, the registry will enforce
+ // the class in authorized resources.
+ Classes []string `yaml:"classes"`
+ } `yaml:"repository,omitempty"`
+ } `yaml:"policy,omitempty"`
+}
+
+// Catalog is composed of MaxEntries.
+// Catalog endpoint (/v2/_catalog) configuration, it provides the configuration
+// options to control the maximum number of entries returned by the catalog endpoint.
+type Catalog struct {
+ // Max number of entries returned by the catalog endpoint. Requesting n entries
+ // to the catalog endpoint will return at most MaxEntries entries.
+ // An empty or a negative value will set a default of 1000 maximum entries by default.
+ MaxEntries int `yaml:"maxentries,omitempty"`
+}
+
+// LogHook is composed of hook Level and Type.
+// After hooks configuration, it can execute the next handling automatically,
+// when defined levels of log message emitted.
+// Example: hook can sending an email notification when error log happens in app.
+type LogHook struct {
+ // Disable lets user select to enable hook or not.
+ Disabled bool `yaml:"disabled,omitempty"`
+
+ // Type allows user to select which type of hook handler they want.
+ Type string `yaml:"type,omitempty"`
+
+ // Levels set which levels of log message will let hook executed.
+ Levels []string `yaml:"levels,omitempty"`
+
+ // MailOptions allows user to configure email parameters.
+ MailOptions MailOptions `yaml:"options,omitempty"`
+}
+
+// MailOptions provides the configuration sections to user, for specific handler.
+type MailOptions struct {
+ SMTP struct {
+ // Addr defines smtp host address
+ Addr string `yaml:"addr,omitempty"`
+
+ // Username defines user name to smtp host
+ Username string `yaml:"username,omitempty"`
+
+ // Password defines password of login user
+ Password string `yaml:"password,omitempty"`
+
+ // Insecure defines if smtp login skips the secure certification.
+ Insecure bool `yaml:"insecure,omitempty"`
+ } `yaml:"smtp,omitempty"`
+
+ // From defines mail sending address
+ From string `yaml:"from,omitempty"`
+
+ // To defines mail receiving address
+ To []string `yaml:"to,omitempty"`
+}
+
+// FileChecker is a type of entry in the health section for checking files.
+type FileChecker struct {
+ // Interval is the duration in between checks
+ Interval time.Duration `yaml:"interval,omitempty"`
+ // File is the path to check
+ File string `yaml:"file,omitempty"`
+ // Threshold is the number of times a check must fail to trigger an
+ // unhealthy state
+ Threshold int `yaml:"threshold,omitempty"`
+}
+
+// HTTPChecker is a type of entry in the health section for checking HTTP URIs.
+type HTTPChecker struct {
+ // Timeout is the duration to wait before timing out the HTTP request
+ Timeout time.Duration `yaml:"timeout,omitempty"`
+ // StatusCode is the expected status code
+ StatusCode int
+ // Interval is the duration in between checks
+ Interval time.Duration `yaml:"interval,omitempty"`
+ // URI is the HTTP URI to check
+ URI string `yaml:"uri,omitempty"`
+ // Headers lists static headers that should be added to all requests
+ Headers http.Header `yaml:"headers"`
+ // Threshold is the number of times a check must fail to trigger an
+ // unhealthy state
+ Threshold int `yaml:"threshold,omitempty"`
+}
+
+// TCPChecker is a type of entry in the health section for checking TCP servers.
+type TCPChecker struct {
+ // Timeout is the duration to wait before timing out the TCP connection
+ Timeout time.Duration `yaml:"timeout,omitempty"`
+ // Interval is the duration in between checks
+ Interval time.Duration `yaml:"interval,omitempty"`
+ // Addr is the TCP address to check
+ Addr string `yaml:"addr,omitempty"`
+ // Threshold is the number of times a check must fail to trigger an
+ // unhealthy state
+ Threshold int `yaml:"threshold,omitempty"`
+}
+
+// Health provides the configuration section for health checks.
+type Health struct {
+ // FileCheckers is a list of paths to check
+ FileCheckers []FileChecker `yaml:"file,omitempty"`
+ // HTTPCheckers is a list of URIs to check
+ HTTPCheckers []HTTPChecker `yaml:"http,omitempty"`
+ // TCPCheckers is a list of URIs to check
+ TCPCheckers []TCPChecker `yaml:"tcp,omitempty"`
+ // StorageDriver configures a health check on the configured storage
+ // driver
+ StorageDriver struct {
+ // Enabled turns on the health check for the storage driver
+ Enabled bool `yaml:"enabled,omitempty"`
+ // Interval is the duration in between checks
+ Interval time.Duration `yaml:"interval,omitempty"`
+ // Threshold is the number of times a check must fail to trigger an
+ // unhealthy state
+ Threshold int `yaml:"threshold,omitempty"`
+ } `yaml:"storagedriver,omitempty"`
+}
+
+// v0_1Configuration is a Version 0.1 Configuration struct
+// This is currently aliased to Configuration, as it is the current version
+type v0_1Configuration Configuration
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface
+// Unmarshals a string of the form X.Y into a Version, validating that X and Y can represent unsigned integers
+func (version *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ var versionString string
+ err := unmarshal(&versionString)
+ if err != nil {
+ return err
+ }
+
+ newVersion := Version(versionString)
+ if _, err := newVersion.major(); err != nil {
+ return err
+ }
+
+ if _, err := newVersion.minor(); err != nil {
+ return err
+ }
+
+ *version = newVersion
+ return nil
+}
+
+// CurrentVersion is the most recent Version that can be parsed
+var CurrentVersion = MajorMinorVersion(0, 1)
+
+// Loglevel is the level at which operations are logged
+// This can be error, warn, info, or debug
+type Loglevel string
+
+// UnmarshalYAML implements the yaml.Umarshaler interface
+// Unmarshals a string into a Loglevel, lowercasing the string and validating that it represents a
+// valid loglevel
+func (loglevel *Loglevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ var loglevelString string
+ err := unmarshal(&loglevelString)
+ if err != nil {
+ return err
+ }
+
+ loglevelString = strings.ToLower(loglevelString)
+ switch loglevelString {
+ case "error", "warn", "info", "debug":
+ default:
+ return fmt.Errorf("invalid loglevel %s Must be one of [error, warn, info, debug]", loglevelString)
+ }
+
+ *loglevel = Loglevel(loglevelString)
+ return nil
+}
+
+// Parameters defines a key-value parameters mapping
+type Parameters map[string]interface{}
+
+// Storage defines the configuration for registry object storage
+type Storage map[string]Parameters
+
+// Type returns the storage driver type, such as filesystem or s3
+func (storage Storage) Type() string {
+ var storageType []string
+
+ // Return only key in this map
+ for k := range storage {
+ switch k {
+ case "maintenance":
+ // allow configuration of maintenance
+ case "cache":
+ // allow configuration of caching
+ case "delete":
+ // allow configuration of delete
+ case "redirect":
+ // allow configuration of redirect
+ default:
+ storageType = append(storageType, k)
+ }
+ }
+ if len(storageType) > 1 {
+ panic("multiple storage drivers specified in configuration or environment: " + strings.Join(storageType, ", "))
+ }
+ if len(storageType) == 1 {
+ return storageType[0]
+ }
+ return ""
+}
+
+// Parameters returns the Parameters map for a Storage configuration
+func (storage Storage) Parameters() Parameters {
+ return storage[storage.Type()]
+}
+
+// setParameter changes the parameter at the provided key to the new value
+func (storage Storage) setParameter(key string, value interface{}) {
+ storage[storage.Type()][key] = value
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface
+// Unmarshals a single item map into a Storage or a string into a Storage type with no parameters
+func (storage *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ var storageMap map[string]Parameters
+ err := unmarshal(&storageMap)
+ if err == nil {
+ if len(storageMap) > 1 {
+ types := make([]string, 0, len(storageMap))
+ for k := range storageMap {
+ switch k {
+ case "maintenance":
+ // allow for configuration of maintenance
+ case "cache":
+ // allow configuration of caching
+ case "delete":
+ // allow configuration of delete
+ case "redirect":
+ // allow configuration of redirect
+ default:
+ types = append(types, k)
+ }
+ }
+
+ if len(types) > 1 {
+ return fmt.Errorf("must provide exactly one storage type. Provided: %v", types)
+ }
+ }
+ *storage = storageMap
+ return nil
+ }
+
+ var storageType string
+ err = unmarshal(&storageType)
+ if err == nil {
+ *storage = Storage{storageType: Parameters{}}
+ return nil
+ }
+
+ return err
+}
+
+// MarshalYAML implements the yaml.Marshaler interface
+func (storage Storage) MarshalYAML() (interface{}, error) {
+ if storage.Parameters() == nil {
+ return storage.Type(), nil
+ }
+ return map[string]Parameters(storage), nil
+}
+
+// Auth defines the configuration for registry authorization.
+type Auth map[string]Parameters
+
+// Type returns the auth type, such as htpasswd or token
+func (auth Auth) Type() string {
+ // Return only key in this map
+ for k := range auth {
+ return k
+ }
+ return ""
+}
+
+// Parameters returns the Parameters map for an Auth configuration
+func (auth Auth) Parameters() Parameters {
+ return auth[auth.Type()]
+}
+
+// setParameter changes the parameter at the provided key to the new value
+func (auth Auth) setParameter(key string, value interface{}) {
+ auth[auth.Type()][key] = value
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface
+// Unmarshals a single item map into a Storage or a string into a Storage type with no parameters
+func (auth *Auth) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ var m map[string]Parameters
+ err := unmarshal(&m)
+ if err == nil {
+ if len(m) > 1 {
+ types := make([]string, 0, len(m))
+ for k := range m {
+ types = append(types, k)
+ }
+
+ // TODO(stevvooe): May want to change this slightly for
+ // authorization to allow multiple challenges.
+ return fmt.Errorf("must provide exactly one type. Provided: %v", types)
+
+ }
+ *auth = m
+ return nil
+ }
+
+ var authType string
+ err = unmarshal(&authType)
+ if err == nil {
+ *auth = Auth{authType: Parameters{}}
+ return nil
+ }
+
+ return err
+}
+
+// MarshalYAML implements the yaml.Marshaler interface
+func (auth Auth) MarshalYAML() (interface{}, error) {
+ if auth.Parameters() == nil {
+ return auth.Type(), nil
+ }
+ return map[string]Parameters(auth), nil
+}
+
+// Notifications configures multiple http endpoints.
+type Notifications struct {
+ // EventConfig is the configuration for the event format that is sent to each Endpoint.
+ EventConfig Events `yaml:"events,omitempty"`
+ // Endpoints is a list of http configurations for endpoints that
+ // respond to webhook notifications. In the future, we may allow other
+ // kinds of endpoints, such as external queues.
+ Endpoints []Endpoint `yaml:"endpoints,omitempty"`
+}
+
+// Endpoint describes the configuration of an http webhook notification
+// endpoint.
+type Endpoint struct {
+ Name string `yaml:"name"` // identifies the endpoint in the registry instance.
+ Disabled bool `yaml:"disabled"` // disables the endpoint
+ URL string `yaml:"url"` // post url for the endpoint.
+ Headers http.Header `yaml:"headers"` // static headers that should be added to all requests
+ Timeout time.Duration `yaml:"timeout"` // HTTP timeout
+ Threshold int `yaml:"threshold"` // circuit breaker threshold before backing off on failure
+ Backoff time.Duration `yaml:"backoff"` // backoff duration
+ IgnoredMediaTypes []string `yaml:"ignoredmediatypes"` // target media types to ignore
+ Ignore Ignore `yaml:"ignore"` // ignore event types
+}
+
+// Events configures notification events.
+type Events struct {
+ IncludeReferences bool `yaml:"includereferences"` // include reference data in manifest events
+}
+
+// Ignore configures mediaTypes and actions of the event, that it won't be propagated
+type Ignore struct {
+ MediaTypes []string `yaml:"mediatypes"` // target media types to ignore
+ Actions []string `yaml:"actions"` // ignore action types
+}
+
+// Reporting defines error reporting methods.
+type Reporting struct {
+ // Bugsnag configures error reporting for Bugsnag (bugsnag.com).
+ Bugsnag BugsnagReporting `yaml:"bugsnag,omitempty"`
+ // NewRelic configures error reporting for NewRelic (newrelic.com)
+ NewRelic NewRelicReporting `yaml:"newrelic,omitempty"`
+}
+
+// BugsnagReporting configures error reporting for Bugsnag (bugsnag.com).
+type BugsnagReporting struct {
+ // APIKey is the Bugsnag api key.
+ APIKey string `yaml:"apikey,omitempty"`
+ // ReleaseStage tracks where the registry is deployed.
+ // Examples: production, staging, development
+ ReleaseStage string `yaml:"releasestage,omitempty"`
+ // Endpoint is used for specifying an enterprise Bugsnag endpoint.
+ Endpoint string `yaml:"endpoint,omitempty"`
+}
+
+// NewRelicReporting configures error reporting for NewRelic (newrelic.com)
+type NewRelicReporting struct {
+ // LicenseKey is the NewRelic user license key
+ LicenseKey string `yaml:"licensekey,omitempty"`
+ // Name is the component name of the registry in NewRelic
+ Name string `yaml:"name,omitempty"`
+ // Verbose configures debug output to STDOUT
+ Verbose bool `yaml:"verbose,omitempty"`
+}
+
+// Middleware configures named middlewares to be applied at injection points.
+type Middleware struct {
+ // Name the middleware registers itself as
+ Name string `yaml:"name"`
+ // Flag to disable middleware easily
+ Disabled bool `yaml:"disabled,omitempty"`
+ // Map of parameters that will be passed to the middleware's initialization function
+ Options Parameters `yaml:"options"`
+}
+
+// Proxy configures the registry as a pull through cache
+type Proxy struct {
+ // RemoteURL is the URL of the remote registry
+ RemoteURL string `yaml:"remoteurl"`
+
+ // Username of the hub user
+ Username string `yaml:"username"`
+
+ // Password of the hub user
+ Password string `yaml:"password"`
+}
+
+// Parse parses an input configuration yaml document into a Configuration struct
+// This should generally be capable of handling old configuration format versions
+//
+// Environment variables may be used to override configuration parameters other than version,
+// following the scheme below:
+// Configuration.Abc may be replaced by the value of REGISTRY_ABC,
+// Configuration.Abc.Xyz may be replaced by the value of REGISTRY_ABC_XYZ, and so forth
+func Parse(rd io.Reader) (*Configuration, error) {
+ in, err := ioutil.ReadAll(rd)
+ if err != nil {
+ return nil, err
+ }
+
+ p := NewParser("registry", []VersionedParseInfo{
+ {
+ Version: MajorMinorVersion(0, 1),
+ ParseAs: reflect.TypeOf(v0_1Configuration{}),
+ ConversionFunc: func(c interface{}) (interface{}, error) {
+ if v0_1, ok := c.(*v0_1Configuration); ok {
+ if v0_1.Log.Level == Loglevel("") {
+ if v0_1.Loglevel != Loglevel("") {
+ v0_1.Log.Level = v0_1.Loglevel
+ } else {
+ v0_1.Log.Level = Loglevel("info")
+ }
+ }
+ if v0_1.Loglevel != Loglevel("") {
+ v0_1.Loglevel = Loglevel("")
+ }
+
+ if v0_1.Catalog.MaxEntries <= 0 {
+ v0_1.Catalog.MaxEntries = 1000
+ }
+
+ if v0_1.Storage.Type() == "" {
+ return nil, errors.New("no storage configuration provided")
+ }
+ return (*Configuration)(v0_1), nil
+ }
+ return nil, fmt.Errorf("expected *v0_1Configuration, received %#v", c)
+ },
+ },
+ })
+
+ config := new(Configuration)
+ err = p.Parse(in, config)
+ if err != nil {
+ return nil, err
+ }
+
+ return config, nil
+}
diff --git a/vendor/github.com/docker/distribution/configuration/parser.go b/vendor/github.com/docker/distribution/configuration/parser.go
new file mode 100644
index 000000000..2bf59d219
--- /dev/null
+++ b/vendor/github.com/docker/distribution/configuration/parser.go
@@ -0,0 +1,283 @@
+package configuration
+
+import (
+ "fmt"
+ "os"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+ "gopkg.in/yaml.v2"
+)
+
+// Version is a major/minor version pair of the form Major.Minor
+// Major version upgrades indicate structure or type changes
+// Minor version upgrades should be strictly additive
+type Version string
+
+// MajorMinorVersion constructs a Version from its Major and Minor components
+func MajorMinorVersion(major, minor uint) Version {
+ return Version(fmt.Sprintf("%d.%d", major, minor))
+}
+
+func (version Version) major() (uint, error) {
+ majorPart := strings.Split(string(version), ".")[0]
+ major, err := strconv.ParseUint(majorPart, 10, 0)
+ return uint(major), err
+}
+
+// Major returns the major version portion of a Version
+func (version Version) Major() uint {
+ major, _ := version.major()
+ return major
+}
+
+func (version Version) minor() (uint, error) {
+ minorPart := strings.Split(string(version), ".")[1]
+ minor, err := strconv.ParseUint(minorPart, 10, 0)
+ return uint(minor), err
+}
+
+// Minor returns the minor version portion of a Version
+func (version Version) Minor() uint {
+ minor, _ := version.minor()
+ return minor
+}
+
+// VersionedParseInfo defines how a specific version of a configuration should
+// be parsed into the current version
+type VersionedParseInfo struct {
+ // Version is the version which this parsing information relates to
+ Version Version
+ // ParseAs defines the type which a configuration file of this version
+ // should be parsed into
+ ParseAs reflect.Type
+ // ConversionFunc defines a method for converting the parsed configuration
+ // (of type ParseAs) into the current configuration version
+ // Note: this method signature is very unclear with the absence of generics
+ ConversionFunc func(interface{}) (interface{}, error)
+}
+
+type envVar struct {
+ name string
+ value string
+}
+
+type envVars []envVar
+
+func (a envVars) Len() int { return len(a) }
+func (a envVars) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a envVars) Less(i, j int) bool { return a[i].name < a[j].name }
+
+// Parser can be used to parse a configuration file and environment of a defined
+// version into a unified output structure
+type Parser struct {
+ prefix string
+ mapping map[Version]VersionedParseInfo
+ env envVars
+}
+
+// NewParser returns a *Parser with the given environment prefix which handles
+// versioned configurations which match the given parseInfos
+func NewParser(prefix string, parseInfos []VersionedParseInfo) *Parser {
+ p := Parser{prefix: prefix, mapping: make(map[Version]VersionedParseInfo)}
+
+ for _, parseInfo := range parseInfos {
+ p.mapping[parseInfo.Version] = parseInfo
+ }
+
+ for _, env := range os.Environ() {
+ envParts := strings.SplitN(env, "=", 2)
+ p.env = append(p.env, envVar{envParts[0], envParts[1]})
+ }
+
+ // We must sort the environment variables lexically by name so that
+ // more specific variables are applied before less specific ones
+ // (i.e. REGISTRY_STORAGE before
+ // REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY). This sucks, but it's a
+ // lot simpler and easier to get right than unmarshalling map entries
+ // into temporaries and merging with the existing entry.
+ sort.Sort(p.env)
+
+ return &p
+}
+
+// Parse reads in the given []byte and environment and writes the resulting
+// configuration into the input v
+//
+// Environment variables may be used to override configuration parameters other
+// than version, following the scheme below:
+// v.Abc may be replaced by the value of PREFIX_ABC,
+// v.Abc.Xyz may be replaced by the value of PREFIX_ABC_XYZ, and so forth
+func (p *Parser) Parse(in []byte, v interface{}) error {
+ var versionedStruct struct {
+ Version Version
+ }
+
+ if err := yaml.Unmarshal(in, &versionedStruct); err != nil {
+ return err
+ }
+
+ parseInfo, ok := p.mapping[versionedStruct.Version]
+ if !ok {
+ return fmt.Errorf("unsupported version: %q", versionedStruct.Version)
+ }
+
+ parseAs := reflect.New(parseInfo.ParseAs)
+ err := yaml.Unmarshal(in, parseAs.Interface())
+ if err != nil {
+ return err
+ }
+
+ for _, envVar := range p.env {
+ pathStr := envVar.name
+ if strings.HasPrefix(pathStr, strings.ToUpper(p.prefix)+"_") {
+ path := strings.Split(pathStr, "_")
+
+ err = p.overwriteFields(parseAs, pathStr, path[1:], envVar.value)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ c, err := parseInfo.ConversionFunc(parseAs.Interface())
+ if err != nil {
+ return err
+ }
+ reflect.ValueOf(v).Elem().Set(reflect.Indirect(reflect.ValueOf(c)))
+ return nil
+}
+
+// overwriteFields replaces configuration values with alternate values specified
+// through the environment. Precondition: an empty path slice must never be
+// passed in.
+func (p *Parser) overwriteFields(v reflect.Value, fullpath string, path []string, payload string) error {
+ for v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ panic("encountered nil pointer while handling environment variable " + fullpath)
+ }
+ v = reflect.Indirect(v)
+ }
+ switch v.Kind() {
+ case reflect.Struct:
+ return p.overwriteStruct(v, fullpath, path, payload)
+ case reflect.Map:
+ return p.overwriteMap(v, fullpath, path, payload)
+ case reflect.Interface:
+ if v.NumMethod() == 0 {
+ if !v.IsNil() {
+ return p.overwriteFields(v.Elem(), fullpath, path, payload)
+ }
+ // Interface was empty; create an implicit map
+ var template map[string]interface{}
+ wrappedV := reflect.MakeMap(reflect.TypeOf(template))
+ v.Set(wrappedV)
+ return p.overwriteMap(wrappedV, fullpath, path, payload)
+ }
+ }
+ return nil
+}
+
+func (p *Parser) overwriteStruct(v reflect.Value, fullpath string, path []string, payload string) error {
+ // Generate case-insensitive map of struct fields
+ byUpperCase := make(map[string]int)
+ for i := 0; i < v.NumField(); i++ {
+ sf := v.Type().Field(i)
+ upper := strings.ToUpper(sf.Name)
+ if _, present := byUpperCase[upper]; present {
+ panic(fmt.Sprintf("field name collision in configuration object: %s", sf.Name))
+ }
+ byUpperCase[upper] = i
+ }
+
+ fieldIndex, present := byUpperCase[path[0]]
+ if !present {
+ logrus.Warnf("Ignoring unrecognized environment variable %s", fullpath)
+ return nil
+ }
+ field := v.Field(fieldIndex)
+ sf := v.Type().Field(fieldIndex)
+
+ if len(path) == 1 {
+ // Env var specifies this field directly
+ fieldVal := reflect.New(sf.Type)
+ err := yaml.Unmarshal([]byte(payload), fieldVal.Interface())
+ if err != nil {
+ return err
+ }
+ field.Set(reflect.Indirect(fieldVal))
+ return nil
+ }
+
+ // If the field is nil, must create an object
+ switch sf.Type.Kind() {
+ case reflect.Map:
+ if field.IsNil() {
+ field.Set(reflect.MakeMap(sf.Type))
+ }
+ case reflect.Ptr:
+ if field.IsNil() {
+ field.Set(reflect.New(sf.Type))
+ }
+ }
+
+ err := p.overwriteFields(field, fullpath, path[1:], payload)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (p *Parser) overwriteMap(m reflect.Value, fullpath string, path []string, payload string) error {
+ if m.Type().Key().Kind() != reflect.String {
+ // non-string keys unsupported
+ logrus.Warnf("Ignoring environment variable %s involving map with non-string keys", fullpath)
+ return nil
+ }
+
+ if len(path) > 1 {
+ // If a matching key exists, get its value and continue the
+ // overwriting process.
+ for _, k := range m.MapKeys() {
+ if strings.ToUpper(k.String()) == path[0] {
+ mapValue := m.MapIndex(k)
+ // If the existing value is nil, we want to
+ // recreate it instead of using this value.
+ if (mapValue.Kind() == reflect.Ptr ||
+ mapValue.Kind() == reflect.Interface ||
+ mapValue.Kind() == reflect.Map) &&
+ mapValue.IsNil() {
+ break
+ }
+ return p.overwriteFields(mapValue, fullpath, path[1:], payload)
+ }
+ }
+ }
+
+ // (Re)create this key
+ var mapValue reflect.Value
+ if m.Type().Elem().Kind() == reflect.Map {
+ mapValue = reflect.MakeMap(m.Type().Elem())
+ } else {
+ mapValue = reflect.New(m.Type().Elem())
+ }
+ if len(path) > 1 {
+ err := p.overwriteFields(mapValue, fullpath, path[1:], payload)
+ if err != nil {
+ return err
+ }
+ } else {
+ err := yaml.Unmarshal([]byte(payload), mapValue.Interface())
+ if err != nil {
+ return err
+ }
+ }
+
+ m.SetMapIndex(reflect.ValueOf(strings.ToLower(path[0])), reflect.Indirect(mapValue))
+
+ return nil
+}
diff --git a/vendor/github.com/docker/distribution/context/context.go b/vendor/github.com/docker/distribution/context/context.go
new file mode 100644
index 000000000..ab6865467
--- /dev/null
+++ b/vendor/github.com/docker/distribution/context/context.go
@@ -0,0 +1,73 @@
+package context
+
+import (
+ "context"
+ "sync"
+
+ "github.com/docker/distribution/uuid"
+)
+
+// instanceContext is a context that provides only an instance id. It is
+// provided as the main background context.
+type instanceContext struct {
+ context.Context
+ id string // id of context, logged as "instance.id"
+ once sync.Once // once protect generation of the id
+}
+
+func (ic *instanceContext) Value(key interface{}) interface{} {
+ if key == "instance.id" {
+ ic.once.Do(func() {
+ // We want to lazy initialize the UUID such that we don't
+ // call a random generator from the package initialization
+ // code. For various reasons random could not be available
+ // https://github.com/docker/distribution/issues/782
+ ic.id = uuid.Generate().String()
+ })
+ return ic.id
+ }
+
+ return ic.Context.Value(key)
+}
+
+var background = &instanceContext{
+ Context: context.Background(),
+}
+
+// Background returns a non-nil, empty Context. The background context
+// provides a single key, "instance.id" that is globally unique to the
+// process.
+func Background() context.Context {
+ return background
+}
+
+// stringMapContext is a simple context implementation that checks a map for a
+// key, falling back to a parent if not present.
+type stringMapContext struct {
+ context.Context
+ m map[string]interface{}
+}
+
+// WithValues returns a context that proxies lookups through a map. Only
+// supports string keys.
+func WithValues(ctx context.Context, m map[string]interface{}) context.Context {
+ mo := make(map[string]interface{}, len(m)) // make our own copy.
+ for k, v := range m {
+ mo[k] = v
+ }
+
+ return stringMapContext{
+ Context: ctx,
+ m: mo,
+ }
+}
+
+func (smc stringMapContext) Value(key interface{}) interface{} {
+ if ks, ok := key.(string); ok {
+ if v, ok := smc.m[ks]; ok {
+ return v
+ }
+ }
+
+ return smc.Context.Value(key)
+}
diff --git a/vendor/github.com/docker/distribution/context/doc.go b/vendor/github.com/docker/distribution/context/doc.go
new file mode 100644
index 000000000..51376dd69
--- /dev/null
+++ b/vendor/github.com/docker/distribution/context/doc.go
@@ -0,0 +1,88 @@
+// Package context provides several utilities for working with
+// Go's context in http requests. Primarily, the focus is on logging relevant
+// request information but this package is not limited to that purpose.
+//
+// The easiest way to get started is to get the background context:
+//
+// ctx := context.Background()
+//
+// The returned context should be passed around your application and be the
+// root of all other context instances. If the application has a version, this
+// line should be called before anything else:
+//
+// ctx := context.WithVersion(context.Background(), version)
+//
+// The above will store the version in the context and will be available to
+// the logger.
+//
+// # Logging
+//
+// The most useful aspect of this package is GetLogger. This function takes
+// any context.Context interface and returns the current logger from the
+// context. Canonical usage looks like this:
+//
+// GetLogger(ctx).Infof("something interesting happened")
+//
+// GetLogger also takes optional key arguments. The keys will be looked up in
+// the context and reported with the logger. The following example would
+// return a logger that prints the version with each log message:
+//
+// ctx := context.Context(context.Background(), "version", version)
+// GetLogger(ctx, "version").Infof("this log message has a version field")
+//
+// The above would print out a log message like this:
+//
+// INFO[0000] this log message has a version field version=v2.0.0-alpha.2.m
+//
+// When used with WithLogger, we gain the ability to decorate the context with
+// loggers that have information from disparate parts of the call stack.
+// Following from the version example, we can build a new context with the
+// configured logger such that we always print the version field:
+//
+// ctx = WithLogger(ctx, GetLogger(ctx, "version"))
+//
+// Since the logger has been pushed to the context, we can now get the version
+// field for free with our log messages. Future calls to GetLogger on the new
+// context will have the version field:
+//
+// GetLogger(ctx).Infof("this log message has a version field")
+//
+// This becomes more powerful when we start stacking loggers. Let's say we
+// have the version logger from above but also want a request id. Using the
+// context above, in our request scoped function, we place another logger in
+// the context:
+//
+// ctx = context.WithValue(ctx, "http.request.id", "unique id") // called when building request context
+// ctx = WithLogger(ctx, GetLogger(ctx, "http.request.id"))
+//
+// When GetLogger is called on the new context, "http.request.id" will be
+// included as a logger field, along with the original "version" field:
+//
+// INFO[0000] this log message has a version field http.request.id=unique id version=v2.0.0-alpha.2.m
+//
+// Note that this only affects the new context, the previous context, with the
+// version field, can be used independently. Put another way, the new logger,
+// added to the request context, is unique to that context and can have
+// request scoped variables.
+//
+// # HTTP Requests
+//
+// This package also contains several methods for working with http requests.
+// The concepts are very similar to those described above. We simply place the
+// request in the context using WithRequest. This makes the request variables
+// available. GetRequestLogger can then be called to get request specific
+// variables in a log line:
+//
+// ctx = WithRequest(ctx, req)
+// GetRequestLogger(ctx).Infof("request variables")
+//
+// Like above, if we want to include the request data in all log messages in
+// the context, we push the logger to a new context and use that one:
+//
+// ctx = WithLogger(ctx, GetRequestLogger(ctx))
+//
+// The concept is fairly powerful and ensures that calls throughout the stack
+// can be traced in log messages. Using the fields like "http.request.id", one
+// can analyze call flow for a particular request with a simple grep of the
+// logs.
+package context
diff --git a/vendor/github.com/docker/distribution/context/http.go b/vendor/github.com/docker/distribution/context/http.go
new file mode 100644
index 000000000..48e354865
--- /dev/null
+++ b/vendor/github.com/docker/distribution/context/http.go
@@ -0,0 +1,333 @@
+package context
+
+import (
+ "context"
+ "errors"
+ "net"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/docker/distribution/uuid"
+ "github.com/gorilla/mux"
+ log "github.com/sirupsen/logrus"
+)
+
+// Common errors used with this package.
+var (
+ ErrNoRequestContext = errors.New("no http request in context")
+ ErrNoResponseWriterContext = errors.New("no http response in context")
+)
+
+func parseIP(ipStr string) net.IP {
+ ip := net.ParseIP(ipStr)
+ if ip == nil {
+ log.Warnf("invalid remote IP address: %q", ipStr)
+ }
+ return ip
+}
+
+// RemoteAddr extracts the remote address of the request, taking into
+// account proxy headers.
+func RemoteAddr(r *http.Request) string {
+ if prior := r.Header.Get("X-Forwarded-For"); prior != "" {
+ proxies := strings.Split(prior, ",")
+ if len(proxies) > 0 {
+ remoteAddr := strings.Trim(proxies[0], " ")
+ if parseIP(remoteAddr) != nil {
+ return remoteAddr
+ }
+ }
+ }
+ // X-Real-Ip is less supported, but worth checking in the
+ // absence of X-Forwarded-For
+ if realIP := r.Header.Get("X-Real-Ip"); realIP != "" {
+ if parseIP(realIP) != nil {
+ return realIP
+ }
+ }
+
+ return r.RemoteAddr
+}
+
+// RemoteIP extracts the remote IP of the request, taking into
+// account proxy headers.
+func RemoteIP(r *http.Request) string {
+ addr := RemoteAddr(r)
+
+ // Try parsing it as "IP:port"
+ if ip, _, err := net.SplitHostPort(addr); err == nil {
+ return ip
+ }
+
+ return addr
+}
+
+// WithRequest places the request on the context. The context of the request
+// is assigned a unique id, available at "http.request.id". The request itself
+// is available at "http.request". Other common attributes are available under
+// the prefix "http.request.". If a request is already present on the context,
+// this method will panic.
+func WithRequest(ctx context.Context, r *http.Request) context.Context {
+ if ctx.Value("http.request") != nil {
+ // NOTE(stevvooe): This needs to be considered a programming error. It
+ // is unlikely that we'd want to have more than one request in
+ // context.
+ panic("only one request per context")
+ }
+
+ return &httpRequestContext{
+ Context: ctx,
+ startedAt: time.Now(),
+ id: uuid.Generate().String(),
+ r: r,
+ }
+}
+
+// GetRequest returns the http request in the given context. Returns
+// ErrNoRequestContext if the context does not have an http request associated
+// with it.
+func GetRequest(ctx context.Context) (*http.Request, error) {
+ if r, ok := ctx.Value("http.request").(*http.Request); r != nil && ok {
+ return r, nil
+ }
+ return nil, ErrNoRequestContext
+}
+
+// GetRequestID attempts to resolve the current request id, if possible. An
+// error is return if it is not available on the context.
+func GetRequestID(ctx context.Context) string {
+ return GetStringValue(ctx, "http.request.id")
+}
+
+// WithResponseWriter returns a new context and response writer that makes
+// interesting response statistics available within the context.
+func WithResponseWriter(ctx context.Context, w http.ResponseWriter) (context.Context, http.ResponseWriter) {
+ irw := instrumentedResponseWriter{
+ ResponseWriter: w,
+ Context: ctx,
+ }
+ return &irw, &irw
+}
+
+// GetResponseWriter returns the http.ResponseWriter from the provided
+// context. If not present, ErrNoResponseWriterContext is returned. The
+// returned instance provides instrumentation in the context.
+func GetResponseWriter(ctx context.Context) (http.ResponseWriter, error) {
+ v := ctx.Value("http.response")
+
+ rw, ok := v.(http.ResponseWriter)
+ if !ok || rw == nil {
+ return nil, ErrNoResponseWriterContext
+ }
+
+ return rw, nil
+}
+
+// getVarsFromRequest let's us change request vars implementation for testing
+// and maybe future changes.
+var getVarsFromRequest = mux.Vars
+
+// WithVars extracts gorilla/mux vars and makes them available on the returned
+// context. Variables are available at keys with the prefix "vars.". For
+// example, if looking for the variable "name", it can be accessed as
+// "vars.name". Implementations that are accessing values need not know that
+// the underlying context is implemented with gorilla/mux vars.
+func WithVars(ctx context.Context, r *http.Request) context.Context {
+ return &muxVarsContext{
+ Context: ctx,
+ vars: getVarsFromRequest(r),
+ }
+}
+
+// GetRequestLogger returns a logger that contains fields from the request in
+// the current context. If the request is not available in the context, no
+// fields will display. Request loggers can safely be pushed onto the context.
+func GetRequestLogger(ctx context.Context) Logger {
+ return GetLogger(ctx,
+ "http.request.id",
+ "http.request.method",
+ "http.request.host",
+ "http.request.uri",
+ "http.request.referer",
+ "http.request.useragent",
+ "http.request.remoteaddr",
+ "http.request.contenttype")
+}
+
+// GetResponseLogger reads the current response stats and builds a logger.
+// Because the values are read at call time, pushing a logger returned from
+// this function on the context will lead to missing or invalid data. Only
+// call this at the end of a request, after the response has been written.
+func GetResponseLogger(ctx context.Context) Logger {
+ l := getLogrusLogger(ctx,
+ "http.response.written",
+ "http.response.status",
+ "http.response.contenttype")
+
+ duration := Since(ctx, "http.request.startedat")
+
+ if duration > 0 {
+ l = l.WithField("http.response.duration", duration.String())
+ }
+
+ return l
+}
+
+// httpRequestContext makes information about a request available to context.
+type httpRequestContext struct {
+ context.Context
+
+ startedAt time.Time
+ id string
+ r *http.Request
+}
+
+// Value returns a keyed element of the request for use in the context. To get
+// the request itself, query "request". For other components, access them as
+// "request.