moby--moby/graph/tags.go

447 lines
10 KiB
Go
Raw Normal View History

package graph
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"github.com/docker/docker/daemon/events"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/stringid"
Deprecating ResolveRepositoryName Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository Moving --registry-mirror configuration to registry config Created resolve_repository job Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name. Adding test for RepositoryInfo Adding tests for opts.StringSetOpts and registry.ValidateMirror Fixing search term use of repoInfo Adding integration tests for registry mirror configuration Normalizing LookupImage image name to match LocalName parsing rules Normalizing repository LocalName to avoid multiple references to an official image Removing errorOut use in tests Removing TODO comment gofmt changes golint comments cleanup. renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig Splitting out builtins.Registry and registry.NewService calls Stray whitespace cleanup Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test Factoring out ValidateRepositoryName from NewRepositoryInfo Removing unused IndexServerURL Allowing json marshaling of ServiceConfig. Exposing ServiceConfig in /info Switching to CamelCase for json marshaling PR cleanup; removing 'Is' prefix from boolean members. Removing unneeded json tags. Removing non-cleanup related fix for 'localhost:[port]' in splitReposName Merge fixes for gh9735 Fixing integration test Reapplying #9754 Adding comment on config.IndexConfigs use from isSecureIndex Remove unused error return value from isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com> Adding back comment in isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com>
2014-10-07 01:54:52 +00:00
"github.com/docker/docker/registry"
"github.com/docker/docker/trust"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
)
2013-06-04 18:00:22 +00:00
const DEFAULTTAG = "latest"
var (
//FIXME these 2 regexes also exist in registry/v2/regexp.go
validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
validDigest = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
)
type TagStore struct {
Deprecating ResolveRepositoryName Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository Moving --registry-mirror configuration to registry config Created resolve_repository job Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name. Adding test for RepositoryInfo Adding tests for opts.StringSetOpts and registry.ValidateMirror Fixing search term use of repoInfo Adding integration tests for registry mirror configuration Normalizing LookupImage image name to match LocalName parsing rules Normalizing repository LocalName to avoid multiple references to an official image Removing errorOut use in tests Removing TODO comment gofmt changes golint comments cleanup. renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig Splitting out builtins.Registry and registry.NewService calls Stray whitespace cleanup Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test Factoring out ValidateRepositoryName from NewRepositoryInfo Removing unused IndexServerURL Allowing json marshaling of ServiceConfig. Exposing ServiceConfig in /info Switching to CamelCase for json marshaling PR cleanup; removing 'Is' prefix from boolean members. Removing unneeded json tags. Removing non-cleanup related fix for 'localhost:[port]' in splitReposName Merge fixes for gh9735 Fixing integration test Reapplying #9754 Adding comment on config.IndexConfigs use from isSecureIndex Remove unused error return value from isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com> Adding back comment in isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com>
2014-10-07 01:54:52 +00:00
path string
graph *Graph
Repositories map[string]Repository
trustKey libtrust.PrivateKey
sync.Mutex
// FIXME: move push/pull-related fields
// to a helper type
pullingPool map[string]chan struct{}
pushingPool map[string]chan struct{}
registryService *registry.Service
eventsService *events.Events
trustService *trust.TrustStore
}
type Repository map[string]string
// update Repository mapping with content of u
func (r Repository) Update(u Repository) {
for k, v := range u {
r[k] = v
}
}
// return true if the contents of u Repository, are wholly contained in r Repository
func (r Repository) Contains(u Repository) bool {
for k, v := range u {
// if u's key is not present in r OR u's key is present, but not the same value
if rv, ok := r[k]; !ok || (ok && rv != v) {
return false
}
}
return true
}
type TagStoreConfig struct {
Graph *Graph
Key libtrust.PrivateKey
Registry *registry.Service
Events *events.Events
Trust *trust.TrustStore
}
func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) {
abspath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
store := &TagStore{
path: abspath,
graph: cfg.Graph,
trustKey: cfg.Key,
Repositories: make(map[string]Repository),
pullingPool: make(map[string]chan struct{}),
pushingPool: make(map[string]chan struct{}),
registryService: cfg.Registry,
eventsService: cfg.Events,
trustService: cfg.Trust,
}
// Load the json file if it exists, otherwise create it.
if err := store.reload(); os.IsNotExist(err) {
if err := store.save(); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
return store, nil
}
func (store *TagStore) save() error {
// Store the json ball
jsonData, err := json.Marshal(store)
if err != nil {
return err
}
if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
return err
}
return nil
}
func (store *TagStore) reload() error {
f, err := os.Open(store.path)
if err != nil {
return err
}
defer f.Close()
if err := json.NewDecoder(f).Decode(&store); err != nil {
return err
}
return nil
}
func (store *TagStore) LookupImage(name string) (*image.Image, error) {
2013-11-29 19:06:35 +00:00
// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
// (so we can pass all errors here)
repoName, ref := parsers.ParseRepositoryTag(name)
if ref == "" {
ref = DEFAULTTAG
}
var (
err error
img *image.Image
)
img, err = store.GetImage(repoName, ref)
if err != nil {
return nil, err
}
if img != nil {
return img, err
2013-11-29 19:06:35 +00:00
}
// name must be an image ID.
store.Lock()
defer store.Unlock()
if img, err = store.graph.Get(name); err != nil {
2013-11-29 19:06:35 +00:00
return nil, err
}
return img, nil
}
// Return a reverse-lookup table of all the names which refer to each image
// Eg. {"43b5f19b10584": {"base:latest", "base:v1"}}
2013-06-04 18:00:22 +00:00
func (store *TagStore) ByID() map[string][]string {
store.Lock()
defer store.Unlock()
2013-06-04 18:00:22 +00:00
byID := make(map[string][]string)
for repoName, repository := range store.Repositories {
for tag, id := range repository {
name := utils.ImageReference(repoName, tag)
2013-06-04 18:00:22 +00:00
if _, exists := byID[id]; !exists {
byID[id] = []string{name}
} else {
2013-06-04 18:00:22 +00:00
byID[id] = append(byID[id], name)
sort.Strings(byID[id])
}
}
}
2013-06-04 18:00:22 +00:00
return byID
}
func (store *TagStore) ImageName(id string) string {
2013-06-04 18:00:22 +00:00
if names, exists := store.ByID()[id]; exists && len(names) > 0 {
return names[0]
}
return stringid.TruncateID(id)
}
2013-05-30 22:53:45 +00:00
func (store *TagStore) DeleteAll(id string) error {
2013-06-10 21:05:54 +00:00
names, exists := store.ByID()[id]
2013-05-30 22:53:45 +00:00
if !exists || len(names) == 0 {
return nil
}
for _, name := range names {
if strings.Contains(name, ":") {
nameParts := strings.Split(name, ":")
if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil {
2013-05-30 22:53:45 +00:00
return err
}
} else {
if _, err := store.Delete(name, ""); err != nil {
2013-05-30 22:53:45 +00:00
return err
}
}
}
return nil
}
func (store *TagStore) Delete(repoName, ref string) (bool, error) {
store.Lock()
defer store.Unlock()
deleted := false
if err := store.reload(); err != nil {
return false, err
2013-05-15 13:11:39 +00:00
}
Deprecating ResolveRepositoryName Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository Moving --registry-mirror configuration to registry config Created resolve_repository job Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name. Adding test for RepositoryInfo Adding tests for opts.StringSetOpts and registry.ValidateMirror Fixing search term use of repoInfo Adding integration tests for registry mirror configuration Normalizing LookupImage image name to match LocalName parsing rules Normalizing repository LocalName to avoid multiple references to an official image Removing errorOut use in tests Removing TODO comment gofmt changes golint comments cleanup. renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig Splitting out builtins.Registry and registry.NewService calls Stray whitespace cleanup Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test Factoring out ValidateRepositoryName from NewRepositoryInfo Removing unused IndexServerURL Allowing json marshaling of ServiceConfig. Exposing ServiceConfig in /info Switching to CamelCase for json marshaling PR cleanup; removing 'Is' prefix from boolean members. Removing unneeded json tags. Removing non-cleanup related fix for 'localhost:[port]' in splitReposName Merge fixes for gh9735 Fixing integration test Reapplying #9754 Adding comment on config.IndexConfigs use from isSecureIndex Remove unused error return value from isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com> Adding back comment in isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com>
2014-10-07 01:54:52 +00:00
repoName = registry.NormalizeLocalName(repoName)
if ref == "" {
// Delete the whole repository.
delete(store.Repositories, repoName)
return true, store.save()
}
repoRefs, exists := store.Repositories[repoName]
if !exists {
return false, fmt.Errorf("No such repository: %s", repoName)
}
if _, exists := repoRefs[ref]; exists {
delete(repoRefs, ref)
if len(repoRefs) == 0 {
2013-05-15 13:11:39 +00:00
delete(store.Repositories, repoName)
}
deleted = true
2013-05-15 13:11:39 +00:00
}
return deleted, store.save()
}
func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error {
return store.SetLoad(repoName, tag, imageName, force, nil)
}
func (store *TagStore) SetLoad(repoName, tag, imageName string, force bool, out io.Writer) error {
img, err := store.LookupImage(imageName)
store.Lock()
defer store.Unlock()
if err != nil {
return err
}
if tag == "" {
2013-06-04 18:00:22 +00:00
tag = DEFAULTTAG
}
if err := validateRepoName(repoName); err != nil {
return err
}
if err := ValidateTagName(tag); err != nil {
return err
}
if err := store.reload(); err != nil {
return err
}
var repo Repository
Deprecating ResolveRepositoryName Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository Moving --registry-mirror configuration to registry config Created resolve_repository job Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name. Adding test for RepositoryInfo Adding tests for opts.StringSetOpts and registry.ValidateMirror Fixing search term use of repoInfo Adding integration tests for registry mirror configuration Normalizing LookupImage image name to match LocalName parsing rules Normalizing repository LocalName to avoid multiple references to an official image Removing errorOut use in tests Removing TODO comment gofmt changes golint comments cleanup. renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig Splitting out builtins.Registry and registry.NewService calls Stray whitespace cleanup Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test Factoring out ValidateRepositoryName from NewRepositoryInfo Removing unused IndexServerURL Allowing json marshaling of ServiceConfig. Exposing ServiceConfig in /info Switching to CamelCase for json marshaling PR cleanup; removing 'Is' prefix from boolean members. Removing unneeded json tags. Removing non-cleanup related fix for 'localhost:[port]' in splitReposName Merge fixes for gh9735 Fixing integration test Reapplying #9754 Adding comment on config.IndexConfigs use from isSecureIndex Remove unused error return value from isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com> Adding back comment in isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com>
2014-10-07 01:54:52 +00:00
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists {
repo = r
if old, exists := store.Repositories[repoName][tag]; exists {
if !force {
return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old)
}
if old != img.ID && out != nil {
fmt.Fprintf(out, "The image %s:%s already exists, renaming the old one with ID %s to empty string\n", repoName, tag, old[:12])
}
}
} else {
repo = make(map[string]string)
store.Repositories[repoName] = repo
}
2013-06-04 18:00:22 +00:00
repo[tag] = img.ID
return store.save()
}
// SetDigest creates a digest reference to an image ID.
func (store *TagStore) SetDigest(repoName, digest, imageName string) error {
img, err := store.LookupImage(imageName)
if err != nil {
return err
}
if err := validateRepoName(repoName); err != nil {
return err
}
if err := validateDigest(digest); err != nil {
return err
}
store.Lock()
defer store.Unlock()
if err := store.reload(); err != nil {
return err
}
repoName = registry.NormalizeLocalName(repoName)
repoRefs, exists := store.Repositories[repoName]
if !exists {
repoRefs = Repository{}
store.Repositories[repoName] = repoRefs
} else if oldID, exists := repoRefs[digest]; exists && oldID != img.ID {
return fmt.Errorf("Conflict: Digest %s is already set to image %s", digest, oldID)
}
repoRefs[digest] = img.ID
return store.save()
}
func (store *TagStore) Get(repoName string) (Repository, error) {
store.Lock()
defer store.Unlock()
if err := store.reload(); err != nil {
return nil, err
}
Deprecating ResolveRepositoryName Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository Moving --registry-mirror configuration to registry config Created resolve_repository job Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name. Adding test for RepositoryInfo Adding tests for opts.StringSetOpts and registry.ValidateMirror Fixing search term use of repoInfo Adding integration tests for registry mirror configuration Normalizing LookupImage image name to match LocalName parsing rules Normalizing repository LocalName to avoid multiple references to an official image Removing errorOut use in tests Removing TODO comment gofmt changes golint comments cleanup. renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig Splitting out builtins.Registry and registry.NewService calls Stray whitespace cleanup Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test Factoring out ValidateRepositoryName from NewRepositoryInfo Removing unused IndexServerURL Allowing json marshaling of ServiceConfig. Exposing ServiceConfig in /info Switching to CamelCase for json marshaling PR cleanup; removing 'Is' prefix from boolean members. Removing unneeded json tags. Removing non-cleanup related fix for 'localhost:[port]' in splitReposName Merge fixes for gh9735 Fixing integration test Reapplying #9754 Adding comment on config.IndexConfigs use from isSecureIndex Remove unused error return value from isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com> Adding back comment in isSecureIndex Signed-off-by: Don Kjer <don.kjer@gmail.com>
2014-10-07 01:54:52 +00:00
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists {
return r, nil
}
return nil, nil
}
func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) {
repo, err := store.Get(repoName)
if err != nil {
return nil, err
}
if repo == nil {
return nil, nil
}
store.Lock()
defer store.Unlock()
if imgID, exists := repo[refOrID]; exists {
return store.graph.Get(imgID)
}
// If no matching tag is found, search through images for a matching image id
2013-06-03 20:00:15 +00:00
for _, revision := range repo {
if strings.HasPrefix(revision, refOrID) {
2013-06-03 20:00:15 +00:00
return store.graph.Get(revision)
}
}
return nil, nil
}
func (store *TagStore) GetRepoRefs() map[string][]string {
store.Lock()
reporefs := make(map[string][]string)
for name, repository := range store.Repositories {
for tag, id := range repository {
shortID := stringid.TruncateID(id)
reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag))
}
}
store.Unlock()
return reporefs
}
// Validate the name of a repository
func validateRepoName(name string) error {
if name == "" {
return fmt.Errorf("Repository name can't be empty")
}
if name == "scratch" {
return fmt.Errorf("'scratch' is a reserved name")
}
return nil
}
// ValidateTagName validates the name of a tag
func ValidateTagName(name string) error {
if name == "" {
return fmt.Errorf("tag name can't be empty")
}
if !validTagName.MatchString(name) {
return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 1, maximum 128 in length", name)
}
return nil
}
func validateDigest(dgst string) error {
if dgst == "" {
return errors.New("digest can't be empty")
}
if !validDigest.MatchString(dgst) {
return fmt.Errorf("illegal digest (%s): must be of the form [a-zA-Z0-9-_+.]+:[a-fA-F0-9]+", dgst)
}
return nil
}
func (store *TagStore) poolAdd(kind, key string) (chan struct{}, error) {
store.Lock()
defer store.Unlock()
if c, exists := store.pullingPool[key]; exists {
return c, fmt.Errorf("pull %s is already in progress", key)
}
if c, exists := store.pushingPool[key]; exists {
return c, fmt.Errorf("push %s is already in progress", key)
}
c := make(chan struct{})
switch kind {
case "pull":
store.pullingPool[key] = c
case "push":
store.pushingPool[key] = c
default:
return nil, fmt.Errorf("Unknown pool type")
}
return c, nil
}
func (store *TagStore) poolRemove(kind, key string) error {
store.Lock()
defer store.Unlock()
switch kind {
case "pull":
if c, exists := store.pullingPool[key]; exists {
close(c)
delete(store.pullingPool, key)
}
case "push":
if c, exists := store.pushingPool[key]; exists {
close(c)
delete(store.pushingPool, key)
}
default:
return fmt.Errorf("Unknown pool type")
}
return nil
}