func (t *mockTagAdder) Add(ref reference.Named, id image.ID, force bool) error { if t.refs == nil { t.refs = make(map[string]string) } t.refs[ref.String()] = id.String() return nil }
// GetRemoteTags retrieves all tags from the given repository. It queries each // of the registries supplied in the registries argument, and returns data from // the first one that answers the query successfully. It returns a map with // tag names as the keys and image IDs as the values. func (r *Session) GetRemoteTags(registries []string, repositoryRef reference.Named) (map[string]string, error) { repository := repositoryRef.Name() if strings.Count(repository, "/") == 0 { // This will be removed once the registry supports auto-resolution on // the "library" namespace repository = "library/" + repository } for _, host := range registries { endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) res, err := r.client.Get(endpoint) if err != nil { return nil, err } logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) defer res.Body.Close() if res.StatusCode == 404 { return nil, ErrRepoNotFound } if res.StatusCode != 200 { continue } result := make(map[string]string) if err := json.NewDecoder(res.Body).Decode(&result); err != nil { return nil, err } return result, nil } return nil, fmt.Errorf("Could not reach any registry endpoint") }
// ReferencesByName returns the references for a given repository name. // If there are no references known for this repository name, // ReferencesByName returns nil. func (store *store) ReferencesByName(ref reference.Named) []Association { store.mu.RLock() defer store.mu.RUnlock() repository, exists := store.Repositories[ref.Name()] if !exists { return nil } var associations []Association for refStr, refID := range repository { ref, err := reference.ParseNamed(refStr) if err != nil { // Should never happen return nil } associations = append(associations, Association{ Ref: ref, ImageID: refID, }) } return associations }
func (b *bridge) createManifestEvent(action string, repo reference.Named, sm distribution.Manifest) (*Event, error) { event := b.createEvent(action) event.Target.Repository = repo.Name() mt, p, err := sm.Payload() if err != nil { return nil, err } // Ensure we have the canonical manifest descriptor here _, desc, err := distribution.UnmarshalManifest(mt, p) if err != nil { return nil, err } event.Target.MediaType = mt event.Target.Length = desc.Size event.Target.Size = desc.Size event.Target.Digest = desc.Digest ref, err := reference.WithDigest(repo, event.Target.Digest) if err != nil { return nil, err } event.Target.URL, err = b.ub.BuildManifestURL(ref) if err != nil { return nil, err } return event, nil }
func (b *bridge) createManifestDeleteEventAndWrite(action string, repo reference.Named, dgst digest.Digest) error { event := b.createEvent(action) event.Target.Repository = repo.Name() event.Target.Digest = dgst return b.sink.Write(*event) }
// normalizeLibraryRepoName removes the library prefix from // the repository name for official repos. func normalizeLibraryRepoName(name reference.Named) (reference.Named, error) { if strings.HasPrefix(name.Name(), "library/") { // If pull "library/foo", it's stored locally under "foo" return reference.WithName(strings.SplitN(name.Name(), "/", 2)[1]) } return name, nil }
func joinHost(host string, ref reference.Named) (reference.Named, error) { if host == "" { return ref, nil } return updateName(ref, strings.Join([]string{host, ref.Name()}, "/")) }
func newRandomSchemaV1Manifest(name reference.Named, tag string, blobCount int) (*schema1.SignedManifest, digest.Digest, []byte) { blobs := make([]schema1.FSLayer, blobCount) history := make([]schema1.History, blobCount) for i := 0; i < blobCount; i++ { dgst, blob := newRandomBlob((i % 5) * 16) blobs[i] = schema1.FSLayer{BlobSum: dgst} history[i] = schema1.History{V1Compatibility: fmt.Sprintf("{\"Hex\": \"%x\"}", blob)} } m := schema1.Manifest{ Name: name.String(), Tag: tag, Architecture: "x86", FSLayers: blobs, History: history, Versioned: manifest.Versioned{ SchemaVersion: 1, }, } pk, err := libtrust.GenerateECP256PrivateKey() if err != nil { panic(err) } sm, err := schema1.Sign(&m, pk) if err != nil { panic(err) } return sm, digest.FromBytes(sm.Canonical), sm.Canonical }
func addTestManifestWithEtag(repo reference.Named, reference string, content []byte, m *testutil.RequestResponseMap, dgst string) { actualDigest := digest.FromBytes(content) getReqWithEtag := testutil.Request{ Method: "GET", Route: "/v2/" + repo.Name() + "/manifests/" + reference, Headers: http.Header(map[string][]string{ "If-None-Match": {fmt.Sprintf(`"%s"`, dgst)}, }), } var getRespWithEtag testutil.Response if actualDigest.String() == dgst { getRespWithEtag = testutil.Response{ StatusCode: http.StatusNotModified, Body: []byte{}, Headers: http.Header(map[string][]string{ "Content-Length": {"0"}, "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, "Content-Type": {schema1.MediaTypeSignedManifest}, }), } } else { getRespWithEtag = testutil.Response{ StatusCode: http.StatusOK, Body: content, Headers: http.Header(map[string][]string{ "Content-Length": {fmt.Sprint(len(content))}, "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, "Content-Type": {schema1.MediaTypeSignedManifest}, }), } } *m = append(*m, testutil.RequestResponseMapping{Request: getReqWithEtag, Response: getRespWithEtag}) }
func addTestManifest(repo reference.Named, reference string, mediatype string, content []byte, m *testutil.RequestResponseMap) { *m = append(*m, testutil.RequestResponseMapping{ Request: testutil.Request{ Method: "GET", Route: "/v2/" + repo.Name() + "/manifests/" + reference, }, Response: testutil.Response{ StatusCode: http.StatusOK, Body: content, Headers: http.Header(map[string][]string{ "Content-Length": {fmt.Sprint(len(content))}, "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, "Content-Type": {mediatype}, "Docker-Content-Digest": {contentDigestString(mediatype, content)}, }), }, }) *m = append(*m, testutil.RequestResponseMapping{ Request: testutil.Request{ Method: "HEAD", Route: "/v2/" + repo.Name() + "/manifests/" + reference, }, Response: testutil.Response{ StatusCode: http.StatusOK, Headers: http.Header(map[string][]string{ "Content-Length": {fmt.Sprint(len(content))}, "Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)}, "Content-Type": {mediatype}, "Docker-Content-Digest": {digest.Canonical.FromBytes(content).String()}, }), }, }) }
// Delete deletes a reference from the store. It returns true if a deletion // happened, or false otherwise. func (store *store) Delete(ref reference.Named) (bool, error) { ref = defaultTagIfNameOnly(ref) store.mu.Lock() defer store.mu.Unlock() repoName := ref.Name() repository, exists := store.Repositories[repoName] if !exists { return false, ErrDoesNotExist } refStr := ref.String() if id, exists := repository[refStr]; exists { delete(repository, refStr) if len(repository) == 0 { delete(store.Repositories, repoName) } if store.referencesByIDCache[id] != nil { delete(store.referencesByIDCache[id], refStr) if len(store.referencesByIDCache[id]) == 0 { delete(store.referencesByIDCache, id) } } return true, store.save() } return false, ErrDoesNotExist }
func (r *testRegistry) Repository(ctx context.Context, ref reference.Named) (distribution.Repository, error) { repo, err := r.Namespace.Repository(ctx, ref) if err != nil { return nil, err } kFakeClient := ktestclient.NewSimpleFake() parts := strings.SplitN(ref.Name(), "/", 3) if len(parts) != 2 { return nil, fmt.Errorf("failed to parse repository name %q", ref.Name()) } return &repository{ Repository: repo, ctx: ctx, quotaClient: kFakeClient, limitClient: kFakeClient, registryOSClient: r.osClient, registryAddr: "localhost:5000", namespace: parts[0], name: parts[1], blobrepositorycachettl: r.blobrepositorycachettl, cachedLayers: cachedLayers, pullthrough: r.pullthrough, }, nil }
func (b *bridge) BlobMounted(repo reference.Named, desc distribution.Descriptor, fromRepo reference.Named) error { event, err := b.createBlobEvent(EventActionMount, repo, desc) if err != nil { return err } event.Target.FromRepository = fromRepo.Name() return b.sink.Write(*event) }
// BuildTagsURL constructs a url to list the tags in the named repository. func (ub *URLBuilder) BuildTagsURL(name reference.Named) (string, error) { route := ub.cloneRoute(RouteNameTags) tagsURL, err := route.URL("name", name.Name()) if err != nil { return "", err } return tagsURL.String(), nil }
func digestRef(ref reference.Named, digst string) (reference.Canonical, error) { rn, err := reference.ParseNamed(ref.Name()) if err != nil { return nil, err } d := digest.Digest(digst) return reference.WithDigest(rn, d) }
func validateRemoteName(remoteName reference.Named) error { remoteNameStr := remoteName.Name() if !strings.Contains(remoteNameStr, "/") { // the repository name must not be a valid image ID if err := v1.ValidateID(remoteNameStr); err == nil { return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", remoteName) } } return nil }
// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid, // including any url values. This should generally not be used by clients, as // this url is provided by server implementations during the blob upload // process. func (ub *URLBuilder) BuildBlobUploadChunkURL(name reference.Named, uuid string, values ...url.Values) (string, error) { route := ub.cloneRoute(RouteNameBlobUploadChunk) uploadURL, err := route.URL("name", name.Name(), "uuid", uuid) if err != nil { return "", err } return appendValuesURL(uploadURL, values...).String(), nil }
// AddManifest schedules a manifest cleanup after ttl expires func (ttles *TTLExpirationScheduler) AddManifest(repoName reference.Named, ttl time.Duration) error { ttles.Lock() defer ttles.Unlock() if ttles.stopped { return fmt.Errorf("scheduler not started") } ttles.add(repoName.Name(), ttl, entryTypeManifest) return nil }
// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo func (config *ServiceConfig) NewRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { if err := validateNoSchema(reposName.Name()); err != nil { return nil, err } repoInfo := &RepositoryInfo{} var ( indexName string err error ) indexName, repoInfo.RemoteName, err = loadRepositoryName(reposName) if err != nil { return nil, err } repoInfo.Index, err = config.NewIndexInfo(indexName) if err != nil { return nil, err } if repoInfo.Index.Official { repoInfo.LocalName, err = normalizeLibraryRepoName(repoInfo.RemoteName) if err != nil { return nil, err } repoInfo.RemoteName = repoInfo.LocalName // If the normalized name does not contain a '/' (e.g. "foo") // then it is an official repo. if strings.IndexRune(repoInfo.RemoteName.Name(), '/') == -1 { repoInfo.Official = true // Fix up remote name for official repos. repoInfo.RemoteName, err = reference.WithName("library/" + repoInfo.RemoteName.Name()) if err != nil { return nil, err } } repoInfo.CanonicalName, err = reference.WithName("docker.io/" + repoInfo.RemoteName.Name()) if err != nil { return nil, err } } else { repoInfo.LocalName, err = localNameFromRemote(repoInfo.Index.Name, repoInfo.RemoteName) if err != nil { return nil, err } repoInfo.CanonicalName = repoInfo.LocalName } return repoInfo, nil }
// TagImage creates a tag in the repository reponame, pointing to the image named // imageName. func (daemon *Daemon) TagImage(newTag reference.Named, imageName string) error { imageID, err := daemon.GetImageID(imageName) if err != nil { return err } newTag = registry.NormalizeLocalReference(newTag) if err := daemon.tagStore.AddTag(newTag, imageID, true); err != nil { return err } daemon.EventsService.Log("tag", newTag.String(), "") return nil }
func getRepoIndexFromUnnormalizedRef(ref distreference.Named) (*registrytypes.IndexInfo, error) { named, err := reference.ParseNamed(ref.Name()) if err != nil { return nil, err } repoInfo, err := registry.ParseRepositoryInfo(named) if err != nil { return nil, err } return repoInfo.Index, nil }
// loadRepositoryName returns the repo name splitted into index name // and remote repo name. It returns an error if the name is not valid. func loadRepositoryName(reposName reference.Named) (string, reference.Named, error) { if err := validateNoSchema(reposName.Name()); err != nil { return "", nil, err } indexName, remoteName, err := splitReposName(reposName) if indexName, err = ValidateIndexName(indexName); err != nil { return "", nil, err } if err = validateRemoteName(remoteName); err != nil { return "", nil, err } return indexName, remoteName, nil }
// TagImage creates a tag in the repository reponame, pointing to the image named // imageName. If force is true, an existing tag with the same name may be // overwritten. func (daemon *Daemon) TagImage(newTag reference.Named, imageName string, force bool) error { if _, isDigested := newTag.(reference.Digested); isDigested { return errors.New("refusing to create a tag with a digest reference") } if newTag.Name() == string(digest.Canonical) { return errors.New("refusing to create an ambiguous tag using digest algorithm as name") } newTag = registry.NormalizeLocalReference(newTag) imageID, err := daemon.GetImageID(imageName) if err != nil { return err } daemon.EventsService.Log("tag", newTag.String(), "") return daemon.tagStore.Add(newTag, imageID, force) }
func (s *Service) lookupV1Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) { var cfg = tlsconfig.ServerDefault tlsConfig := &cfg nameString := repoName.Name() if strings.HasPrefix(nameString, DefaultNamespace+"/") { endpoints = append(endpoints, APIEndpoint{ URL: DefaultV1Registry, Version: APIVersion1, Official: true, TrimHostname: true, TLSConfig: tlsConfig, }) return endpoints, nil } slashIndex := strings.IndexRune(nameString, '/') if slashIndex <= 0 { return nil, fmt.Errorf("invalid repo name: missing '/': %s", nameString) } hostname := nameString[:slashIndex] tlsConfig, err = s.TLSConfig(hostname) if err != nil { return nil, err } endpoints = []APIEndpoint{ { URL: "https://" + hostname, Version: APIVersion1, TrimHostname: true, TLSConfig: tlsConfig, }, } if tlsConfig.InsecureSkipVerify { endpoints = append(endpoints, APIEndpoint{ // or this URL: "http://" + hostname, Version: APIVersion1, TrimHostname: true, // used to check if supposed to be secure via InsecureSkipVerify TLSConfig: tlsConfig, }) } return endpoints, nil }
// Repository returns an instance of the repository tied to the registry. // Instances should not be shared between goroutines but are cheap to // allocate. In general, they should be request scoped. func (reg *registry) Repository(ctx context.Context, canonicalName reference.Named) (distribution.Repository, error) { var descriptorCache distribution.BlobDescriptorService if reg.blobDescriptorCacheProvider != nil { var err error descriptorCache, err = reg.blobDescriptorCacheProvider.RepositoryScoped(canonicalName.Name()) if err != nil { return nil, err } } return &repository{ ctx: ctx, registry: reg, name: canonicalName, descriptorCache: descriptorCache, }, nil }
// Get retrieves an item from the store by reference. func (store *store) Get(ref reference.Named) (image.ID, error) { ref = defaultTagIfNameOnly(ref) store.mu.RLock() defer store.mu.RUnlock() repository, exists := store.Repositories[ref.Name()] if !exists || repository == nil { return "", ErrDoesNotExist } id, exists := repository[ref.String()] if !exists { return "", ErrDoesNotExist } return id, nil }
// NewReferenceManifestBuilder is used to build new manifests for the current // schema version using schema1 dependencies. func NewReferenceManifestBuilder(pk libtrust.PrivateKey, ref reference.Named, architecture string) distribution.ManifestBuilder { tag := "" if tagged, isTagged := ref.(reference.Tagged); isTagged { tag = tagged.Tag() } return &referenceManifestBuilder{ Manifest: Manifest{ Versioned: manifest.Versioned{ SchemaVersion: 1, }, Name: ref.Name(), Tag: tag, Architecture: architecture, }, pk: pk, } }
// BuildManifestURL constructs a url for the manifest identified by name and // reference. The argument reference may be either a tag or digest. func (ub *URLBuilder) BuildManifestURL(ref reference.Named) (string, error) { route := ub.cloneRoute(RouteNameManifest) tagOrDigest := "" switch v := ref.(type) { case reference.Tagged: tagOrDigest = v.Tag() case reference.Digested: tagOrDigest = v.Digest().String() } manifestURL, err := route.URL("name", ref.Name(), "reference", tagOrDigest) if err != nil { return "", err } return manifestURL.String(), nil }
func (b *bridge) createBlobEvent(action string, repo reference.Named, desc distribution.Descriptor) (*Event, error) { event := b.createEvent(action) event.Target.Descriptor = desc event.Target.Length = desc.Size event.Target.Repository = repo.Name() ref, err := reference.WithDigest(repo, desc.Digest) if err != nil { return nil, err } event.Target.URL, err = b.ub.BuildBlobURL(ref) if err != nil { return nil, err } return event, nil }
// PushRegistryTag pushes a tag on the registry. // Remote has the format '<user>/<repo> func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error { // "jsonify" the string revision = "\"" + revision + "\"" path := fmt.Sprintf("repositories/%s/tags/%s", remote.Name(), tag) req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision)) if err != nil { return err } req.Header.Add("Content-type", "application/json") req.ContentLength = int64(len(revision)) res, err := r.client.Do(req) if err != nil { return err } res.Body.Close() if res.StatusCode != 200 && res.StatusCode != 201 { return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote.Name()), res) } return nil }