vendor docker/distribution fbb70dc3a14ca65cdac3aaf5e5122b03b42f6fbc
| ... | ... |
@@ -91,7 +91,7 @@ clone git github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 |
| 91 | 91 |
clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 |
| 92 | 92 |
|
| 93 | 93 |
# get graph and distribution packages |
| 94 |
-clone git github.com/docker/distribution 77b9d2997abcded79a5314970fe69a44c93c25fb |
|
| 94 |
+clone git github.com/docker/distribution fbb70dc3a14ca65cdac3aaf5e5122b03b42f6fbc |
|
| 95 | 95 |
clone git github.com/vbatts/tar-split v0.10.1 |
| 96 | 96 |
|
| 97 | 97 |
# get go-zfs packages |
| ... | ... |
@@ -11,7 +11,7 @@ Most people should use the [official Registry docker image](https://hub.docker.c |
| 11 | 11 |
|
| 12 | 12 |
People looking for advanced operational use cases might consider rolling their own image with a custom Dockerfile inheriting `FROM registry:2`. |
| 13 | 13 |
|
| 14 |
-OS X users who want to run natively can do so following [the instructions here](osx-setup-guide.md). |
|
| 14 |
+OS X users who want to run natively can do so following [the instructions here](https://github.com/docker/docker.github.io/blob/master/registry/recipes/osx-setup-guide.md). |
|
| 15 | 15 |
|
| 16 | 16 |
### Gotchas |
| 17 | 17 |
|
| ... | ... |
@@ -27,22 +27,25 @@ version/version.go: |
| 27 | 27 |
# Required for go 1.5 to build |
| 28 | 28 |
GO15VENDOREXPERIMENT := 1 |
| 29 | 29 |
|
| 30 |
+# Go files |
|
| 31 |
+GOFILES=$(shell find . -type f -name '*.go') |
|
| 32 |
+ |
|
| 30 | 33 |
# Package list |
| 31 |
-PKGS := $(shell go list -tags "${DOCKER_BUILDTAGS}" ./... | grep -v ^github.com/docker/distribution/vendor/)
|
|
| 34 |
+PKGS=$(shell go list -tags "${DOCKER_BUILDTAGS}" ./... | grep -v ^github.com/docker/distribution/vendor/)
|
|
| 32 | 35 |
|
| 33 | 36 |
# Resolving binary dependencies for specific targets |
| 34 |
-GOLINT := $(shell which golint || echo '') |
|
| 35 |
-GODEP := $(shell which godep || echo '') |
|
| 37 |
+GOLINT=$(shell which golint || echo '') |
|
| 38 |
+GODEP=$(shell which godep || echo '') |
|
| 36 | 39 |
|
| 37 |
-${PREFIX}/bin/registry: $(wildcard **/*.go)
|
|
| 40 |
+${PREFIX}/bin/registry: $(GOFILES)
|
|
| 38 | 41 |
@echo "+ $@" |
| 39 | 42 |
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry
|
| 40 | 43 |
|
| 41 |
-${PREFIX}/bin/digest: $(wildcard **/*.go)
|
|
| 44 |
+${PREFIX}/bin/digest: $(GOFILES)
|
|
| 42 | 45 |
@echo "+ $@" |
| 43 | 46 |
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/digest
|
| 44 | 47 |
|
| 45 |
-${PREFIX}/bin/registry-api-descriptor-template: $(wildcard **/*.go)
|
|
| 48 |
+${PREFIX}/bin/registry-api-descriptor-template: $(GOFILES)
|
|
| 46 | 49 |
@echo "+ $@" |
| 47 | 50 |
@go build -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry-api-descriptor-template
|
| 48 | 51 |
|
| ... | ... |
@@ -19,7 +19,7 @@ This repository contains the following components: |
| 19 | 19 |
| **registry** | An implementation of the [Docker Registry HTTP API V2](docs/spec/api.md) for use with docker 1.6+. | |
| 20 | 20 |
| **libraries** | A rich set of libraries for interacting with distribution components. Please see [godoc](https://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. | |
| 21 | 21 |
| **specifications** | _Distribution_ related specifications are available in [docs/spec](docs/spec) | |
| 22 |
-| **documentation** | Docker's full documentation set is available at [docs.docker.com](https://docs.docker.com). This repository [contains the subset](docs/index.md) related just to the registry. | |
|
| 22 |
+| **documentation** | Docker's full documentation set is available at [docs.docker.com](https://docs.docker.com). This repository [contains the subset](docs/) related just to the registry. | |
|
| 23 | 23 |
|
| 24 | 24 |
### How does this integrate with Docker engine? |
| 25 | 25 |
|
| ... | ... |
@@ -60,15 +60,15 @@ For information on upcoming functionality, please see [ROADMAP.md](ROADMAP.md). |
| 60 | 60 |
By default, Docker users pull images from Docker's public registry instance. |
| 61 | 61 |
[Installing Docker](https://docs.docker.com/engine/installation/) gives users this |
| 62 | 62 |
ability. Users can also push images to a repository on Docker's public registry, |
| 63 |
-if they have a [Docker Hub](https://hub.docker.com/) account. |
|
| 63 |
+if they have a [Docker Hub](https://hub.docker.com/) account. |
|
| 64 | 64 |
|
| 65 | 65 |
For some users and even companies, this default behavior is sufficient. For |
| 66 |
-others, it is not. |
|
| 66 |
+others, it is not. |
|
| 67 | 67 |
|
| 68 | 68 |
For example, users with their own software products may want to maintain a |
| 69 | 69 |
registry for private, company images. Also, you may wish to deploy your own |
| 70 | 70 |
image repository for images used to test or in continuous integration. For these |
| 71 |
-use cases and others, [deploying your own registry instance](docs/deploying.md) |
|
| 71 |
+use cases and others, [deploying your own registry instance](https://github.com/docker/docker.github.io/blob/master/registry/deploying.md) |
|
| 72 | 72 |
may be the better choice. |
| 73 | 73 |
|
| 74 | 74 |
### Migration to Registry 2.0 |
| ... | ... |
@@ -83,7 +83,7 @@ created. For more information see [docker/migrator] |
| 83 | 83 |
|
| 84 | 84 |
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute |
| 85 | 85 |
issues, fixes, and patches to this project. If you are contributing code, see |
| 86 |
-the instructions for [building a development environment](docs/recipes/building.md). |
|
| 86 |
+the instructions for [building a development environment](BUILDING.md). |
|
| 87 | 87 |
|
| 88 | 88 |
## Support |
| 89 | 89 |
|
| ... | ... |
@@ -9,11 +9,10 @@ import ( |
| 9 | 9 |
|
| 10 | 10 |
"github.com/docker/distribution" |
| 11 | 11 |
"github.com/docker/distribution/context" |
| 12 |
- "github.com/docker/distribution/reference" |
|
| 13 |
- "github.com/docker/libtrust" |
|
| 14 |
- |
|
| 15 | 12 |
"github.com/docker/distribution/digest" |
| 16 | 13 |
"github.com/docker/distribution/manifest" |
| 14 |
+ "github.com/docker/distribution/reference" |
|
| 15 |
+ "github.com/docker/libtrust" |
|
| 17 | 16 |
) |
| 18 | 17 |
|
| 19 | 18 |
type diffID digest.Digest |
| ... | ... |
@@ -95,7 +94,7 @@ func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Mani |
| 95 | 95 |
} |
| 96 | 96 |
|
| 97 | 97 |
if len(img.RootFS.DiffIDs) != len(mb.descriptors) {
|
| 98 |
- return nil, errors.New("number of descriptors and number of layers in rootfs must match")
|
|
| 98 |
+ return nil, fmt.Errorf("number of descriptors and number of layers in rootfs must match: len(%v) != len(%v)", img.RootFS.DiffIDs, mb.descriptors)
|
|
| 99 | 99 |
} |
| 100 | 100 |
|
| 101 | 101 |
// Generate IDs for each layer |
| ... | ... |
@@ -18,7 +18,7 @@ const ( |
| 18 | 18 |
MediaTypeConfig = "application/vnd.docker.container.image.v1+json" |
| 19 | 19 |
|
| 20 | 20 |
// MediaTypePluginConfig specifies the mediaType for plugin configuration. |
| 21 |
- MediaTypePluginConfig = "application/vnd.docker.plugin.v0+json" |
|
| 21 |
+ MediaTypePluginConfig = "application/vnd.docker.plugin.image.v0+json" |
|
| 22 | 22 |
|
| 23 | 23 |
// MediaTypeLayer is the mediaType used for layers referenced by the |
| 24 | 24 |
// manifest. |
| ... | ... |
@@ -69,7 +69,10 @@ type Manifest struct {
|
| 69 | 69 |
|
| 70 | 70 |
// References returnes the descriptors of this manifests references. |
| 71 | 71 |
func (m Manifest) References() []distribution.Descriptor {
|
| 72 |
- return m.Layers |
|
| 72 |
+ references := make([]distribution.Descriptor, 0, 1+len(m.Layers)) |
|
| 73 |
+ references = append(references, m.Config) |
|
| 74 |
+ references = append(references, m.Layers...) |
|
| 75 |
+ return references |
|
| 73 | 76 |
} |
| 74 | 77 |
|
| 75 | 78 |
// Target returns the target of this signed manifest. |
| ... | ... |
@@ -12,8 +12,13 @@ import ( |
| 12 | 12 |
// references and an optional target |
| 13 | 13 |
type Manifest interface {
|
| 14 | 14 |
// References returns a list of objects which make up this manifest. |
| 15 |
- // The references are strictly ordered from base to head. A reference |
|
| 16 |
- // is anything which can be represented by a distribution.Descriptor |
|
| 15 |
+ // A reference is anything which can be represented by a |
|
| 16 |
+ // distribution.Descriptor. These can consist of layers, resources or other |
|
| 17 |
+ // manifests. |
|
| 18 |
+ // |
|
| 19 |
+ // While no particular order is required, implementations should return |
|
| 20 |
+ // them from highest to lowest priority. For example, one might want to |
|
| 21 |
+ // return the base layer before the top layer. |
|
| 17 | 22 |
References() []Descriptor |
| 18 | 23 |
|
| 19 | 24 |
// Payload provides the serialized format of the manifest, in addition to |
| ... | ... |
@@ -36,6 +41,9 @@ type ManifestBuilder interface {
|
| 36 | 36 |
// AppendReference includes the given object in the manifest after any |
| 37 | 37 |
// existing dependencies. If the add fails, such as when adding an |
| 38 | 38 |
// unsupported dependency, an error may be returned. |
| 39 |
+ // |
|
| 40 |
+ // The destination of the reference is dependent on the manifest type and |
|
| 41 |
+ // the dependency type. |
|
| 39 | 42 |
AppendReference(dependency Describable) error |
| 40 | 43 |
} |
| 41 | 44 |
|
| ... | ... |
@@ -24,6 +24,7 @@ package reference |
| 24 | 24 |
import ( |
| 25 | 25 |
"errors" |
| 26 | 26 |
"fmt" |
| 27 |
+ "strings" |
|
| 27 | 28 |
|
| 28 | 29 |
"github.com/docker/distribution/digest" |
| 29 | 30 |
) |
| ... | ... |
@@ -43,6 +44,9 @@ var ( |
| 43 | 43 |
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. |
| 44 | 44 |
ErrDigestInvalidFormat = errors.New("invalid digest format")
|
| 45 | 45 |
|
| 46 |
+ // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. |
|
| 47 |
+ ErrNameContainsUppercase = errors.New("repository name must be lowercase")
|
|
| 48 |
+ |
|
| 46 | 49 |
// ErrNameEmpty is returned for empty, invalid repository names. |
| 47 | 50 |
ErrNameEmpty = errors.New("repository name must have at least one component")
|
| 48 | 51 |
|
| ... | ... |
@@ -134,7 +138,7 @@ type Canonical interface {
|
| 134 | 134 |
func SplitHostname(named Named) (string, string) {
|
| 135 | 135 |
name := named.Name() |
| 136 | 136 |
match := anchoredNameRegexp.FindStringSubmatch(name) |
| 137 |
- if match == nil || len(match) != 3 {
|
|
| 137 |
+ if len(match) != 3 {
|
|
| 138 | 138 |
return "", name |
| 139 | 139 |
} |
| 140 | 140 |
return match[1], match[2] |
| ... | ... |
@@ -149,7 +153,9 @@ func Parse(s string) (Reference, error) {
|
| 149 | 149 |
if s == "" {
|
| 150 | 150 |
return nil, ErrNameEmpty |
| 151 | 151 |
} |
| 152 |
- // TODO(dmcgowan): Provide more specific and helpful error |
|
| 152 |
+ if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
|
|
| 153 |
+ return nil, ErrNameContainsUppercase |
|
| 154 |
+ } |
|
| 153 | 155 |
return nil, ErrReferenceInvalidFormat |
| 154 | 156 |
} |
| 155 | 157 |
|
| 156 | 158 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+package auth |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "net/url" |
|
| 4 |
+ "strings" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// FROM: https://golang.org/src/net/http/http.go |
|
| 8 |
+// Given a string of the form "host", "host:port", or "[ipv6::address]:port", |
|
| 9 |
+// return true if the string includes a port. |
|
| 10 |
+func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
|
| 11 |
+ |
|
| 12 |
+// FROM: http://golang.org/src/net/http/transport.go |
|
| 13 |
+var portMap = map[string]string{
|
|
| 14 |
+ "http": "80", |
|
| 15 |
+ "https": "443", |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+// canonicalAddr returns url.Host but always with a ":port" suffix |
|
| 19 |
+// FROM: http://golang.org/src/net/http/transport.go |
|
| 20 |
+func canonicalAddr(url *url.URL) string {
|
|
| 21 |
+ addr := url.Host |
|
| 22 |
+ if !hasPort(addr) {
|
|
| 23 |
+ return addr + ":" + portMap[url.Scheme] |
|
| 24 |
+ } |
|
| 25 |
+ return addr |
|
| 26 |
+} |
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
"net/http" |
| 6 | 6 |
"net/url" |
| 7 | 7 |
"strings" |
| 8 |
+ "sync" |
|
| 8 | 9 |
) |
| 9 | 10 |
|
| 10 | 11 |
// Challenge carries information from a WWW-Authenticate response header. |
| ... | ... |
@@ -43,29 +44,45 @@ type ChallengeManager interface {
|
| 43 | 43 |
// perform requests on the endpoints or cache the responses |
| 44 | 44 |
// to a backend. |
| 45 | 45 |
func NewSimpleChallengeManager() ChallengeManager {
|
| 46 |
- return simpleChallengeManager{}
|
|
| 46 |
+ return &simpleChallengeManager{
|
|
| 47 |
+ Challanges: make(map[string][]Challenge), |
|
| 48 |
+ } |
|
| 47 | 49 |
} |
| 48 | 50 |
|
| 49 |
-type simpleChallengeManager map[string][]Challenge |
|
| 51 |
+type simpleChallengeManager struct {
|
|
| 52 |
+ sync.RWMutex |
|
| 53 |
+ Challanges map[string][]Challenge |
|
| 54 |
+} |
|
| 50 | 55 |
|
| 51 |
-func (m simpleChallengeManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
|
|
| 56 |
+func normalizeURL(endpoint *url.URL) {
|
|
| 52 | 57 |
endpoint.Host = strings.ToLower(endpoint.Host) |
| 58 |
+ endpoint.Host = canonicalAddr(endpoint) |
|
| 59 |
+} |
|
| 53 | 60 |
|
| 54 |
- challenges := m[endpoint.String()] |
|
| 61 |
+func (m *simpleChallengeManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
|
|
| 62 |
+ normalizeURL(&endpoint) |
|
| 63 |
+ |
|
| 64 |
+ m.RLock() |
|
| 65 |
+ defer m.RUnlock() |
|
| 66 |
+ challenges := m.Challanges[endpoint.String()] |
|
| 55 | 67 |
return challenges, nil |
| 56 | 68 |
} |
| 57 | 69 |
|
| 58 |
-func (m simpleChallengeManager) AddResponse(resp *http.Response) error {
|
|
| 70 |
+func (m *simpleChallengeManager) AddResponse(resp *http.Response) error {
|
|
| 59 | 71 |
challenges := ResponseChallenges(resp) |
| 60 | 72 |
if resp.Request == nil {
|
| 61 | 73 |
return fmt.Errorf("missing request reference")
|
| 62 | 74 |
} |
| 63 | 75 |
urlCopy := url.URL{
|
| 64 | 76 |
Path: resp.Request.URL.Path, |
| 65 |
- Host: strings.ToLower(resp.Request.URL.Host), |
|
| 77 |
+ Host: resp.Request.URL.Host, |
|
| 66 | 78 |
Scheme: resp.Request.URL.Scheme, |
| 67 | 79 |
} |
| 68 |
- m[urlCopy.String()] = challenges |
|
| 80 |
+ normalizeURL(&urlCopy) |
|
| 81 |
+ |
|
| 82 |
+ m.Lock() |
|
| 83 |
+ defer m.Unlock() |
|
| 84 |
+ m.Challanges[urlCopy.String()] = challenges |
|
| 69 | 85 |
return nil |
| 70 | 86 |
} |
| 71 | 87 |
|
| ... | ... |
@@ -301,18 +301,20 @@ func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, er |
| 301 | 301 |
return distribution.Descriptor{}, err
|
| 302 | 302 |
} |
| 303 | 303 |
|
| 304 |
- req, err := http.NewRequest("HEAD", u, nil)
|
|
| 305 |
- if err != nil {
|
|
| 306 |
- return distribution.Descriptor{}, err
|
|
| 307 |
- } |
|
| 304 |
+ newRequest := func(method string) (*http.Response, error) {
|
|
| 305 |
+ req, err := http.NewRequest(method, u, nil) |
|
| 306 |
+ if err != nil {
|
|
| 307 |
+ return nil, err |
|
| 308 |
+ } |
|
| 308 | 309 |
|
| 309 |
- for _, t := range distribution.ManifestMediaTypes() {
|
|
| 310 |
- req.Header.Add("Accept", t)
|
|
| 310 |
+ for _, t := range distribution.ManifestMediaTypes() {
|
|
| 311 |
+ req.Header.Add("Accept", t)
|
|
| 312 |
+ } |
|
| 313 |
+ resp, err := t.client.Do(req) |
|
| 314 |
+ return resp, err |
|
| 311 | 315 |
} |
| 312 | 316 |
|
| 313 |
- var attempts int |
|
| 314 |
- resp, err := t.client.Do(req) |
|
| 315 |
-check: |
|
| 317 |
+ resp, err := newRequest("HEAD")
|
|
| 316 | 318 |
if err != nil {
|
| 317 | 319 |
return distribution.Descriptor{}, err
|
| 318 | 320 |
} |
| ... | ... |
@@ -321,23 +323,20 @@ check: |
| 321 | 321 |
switch {
|
| 322 | 322 |
case resp.StatusCode >= 200 && resp.StatusCode < 400: |
| 323 | 323 |
return descriptorFromResponse(resp) |
| 324 |
- case resp.StatusCode == http.StatusMethodNotAllowed: |
|
| 325 |
- req, err = http.NewRequest("GET", u, nil)
|
|
| 324 |
+ default: |
|
| 325 |
+ // if the response is an error - there will be no body to decode. |
|
| 326 |
+ // Issue a GET request: |
|
| 327 |
+ // - for data from a server that does not handle HEAD |
|
| 328 |
+ // - to get error details in case of a failure |
|
| 329 |
+ resp, err = newRequest("GET")
|
|
| 326 | 330 |
if err != nil {
|
| 327 | 331 |
return distribution.Descriptor{}, err
|
| 328 | 332 |
} |
| 333 |
+ defer resp.Body.Close() |
|
| 329 | 334 |
|
| 330 |
- for _, t := range distribution.ManifestMediaTypes() {
|
|
| 331 |
- req.Header.Add("Accept", t)
|
|
| 332 |
- } |
|
| 333 |
- |
|
| 334 |
- resp, err = t.client.Do(req) |
|
| 335 |
- attempts++ |
|
| 336 |
- if attempts > 1 {
|
|
| 337 |
- return distribution.Descriptor{}, err
|
|
| 335 |
+ if resp.StatusCode >= 200 && resp.StatusCode < 400 {
|
|
| 336 |
+ return descriptorFromResponse(resp) |
|
| 338 | 337 |
} |
| 339 |
- goto check |
|
| 340 |
- default: |
|
| 341 | 338 |
return distribution.Descriptor{}, HandleErrorResponse(resp)
|
| 342 | 339 |
} |
| 343 | 340 |
} |
| ... | ... |
@@ -181,6 +181,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
| 181 | 181 |
// context.GetLogger(hrs.context).Infof("Range: %s", req.Header.Get("Range"))
|
| 182 | 182 |
} |
| 183 | 183 |
|
| 184 |
+ req.Header.Add("Accept-Encoding", "identity")
|
|
| 184 | 185 |
resp, err := hrs.client.Do(req) |
| 185 | 186 |
if err != nil {
|
| 186 | 187 |
return nil, err |
| ... | ... |
@@ -77,37 +77,46 @@ type repositoryScopedInMemoryBlobDescriptorCache struct {
|
| 77 | 77 |
} |
| 78 | 78 |
|
| 79 | 79 |
func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
| 80 |
- if rsimbdcp.repository == nil {
|
|
| 80 |
+ rsimbdcp.parent.mu.Lock() |
|
| 81 |
+ repo := rsimbdcp.repository |
|
| 82 |
+ rsimbdcp.parent.mu.Unlock() |
|
| 83 |
+ |
|
| 84 |
+ if repo == nil {
|
|
| 81 | 85 |
return distribution.Descriptor{}, distribution.ErrBlobUnknown
|
| 82 | 86 |
} |
| 83 | 87 |
|
| 84 |
- return rsimbdcp.repository.Stat(ctx, dgst) |
|
| 88 |
+ return repo.Stat(ctx, dgst) |
|
| 85 | 89 |
} |
| 86 | 90 |
|
| 87 | 91 |
func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) Clear(ctx context.Context, dgst digest.Digest) error {
|
| 88 |
- if rsimbdcp.repository == nil {
|
|
| 92 |
+ rsimbdcp.parent.mu.Lock() |
|
| 93 |
+ repo := rsimbdcp.repository |
|
| 94 |
+ rsimbdcp.parent.mu.Unlock() |
|
| 95 |
+ |
|
| 96 |
+ if repo == nil {
|
|
| 89 | 97 |
return distribution.ErrBlobUnknown |
| 90 | 98 |
} |
| 91 | 99 |
|
| 92 |
- return rsimbdcp.repository.Clear(ctx, dgst) |
|
| 100 |
+ return repo.Clear(ctx, dgst) |
|
| 93 | 101 |
} |
| 94 | 102 |
|
| 95 | 103 |
func (rsimbdcp *repositoryScopedInMemoryBlobDescriptorCache) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
| 96 |
- if rsimbdcp.repository == nil {
|
|
| 104 |
+ rsimbdcp.parent.mu.Lock() |
|
| 105 |
+ repo := rsimbdcp.repository |
|
| 106 |
+ if repo == nil {
|
|
| 97 | 107 |
// allocate map since we are setting it now. |
| 98 |
- rsimbdcp.parent.mu.Lock() |
|
| 99 | 108 |
var ok bool |
| 100 | 109 |
// have to read back value since we may have allocated elsewhere. |
| 101 |
- rsimbdcp.repository, ok = rsimbdcp.parent.repositories[rsimbdcp.repo] |
|
| 110 |
+ repo, ok = rsimbdcp.parent.repositories[rsimbdcp.repo] |
|
| 102 | 111 |
if !ok {
|
| 103 |
- rsimbdcp.repository = newMapBlobDescriptorCache() |
|
| 104 |
- rsimbdcp.parent.repositories[rsimbdcp.repo] = rsimbdcp.repository |
|
| 112 |
+ repo = newMapBlobDescriptorCache() |
|
| 113 |
+ rsimbdcp.parent.repositories[rsimbdcp.repo] = repo |
|
| 105 | 114 |
} |
| 106 |
- |
|
| 107 |
- rsimbdcp.parent.mu.Unlock() |
|
| 115 |
+ rsimbdcp.repository = repo |
|
| 108 | 116 |
} |
| 117 |
+ rsimbdcp.parent.mu.Unlock() |
|
| 109 | 118 |
|
| 110 |
- if err := rsimbdcp.repository.SetDescriptor(ctx, dgst, desc); err != nil {
|
|
| 119 |
+ if err := repo.SetDescriptor(ctx, dgst, desc); err != nil {
|
|
| 111 | 120 |
return err |
| 112 | 121 |
} |
| 113 | 122 |
|