github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/graph/tags.go (about) 1 package graph 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13 "sync" 14 15 "github.com/docker/distribution/digest" 16 "github.com/docker/docker/daemon/events" 17 "github.com/docker/docker/graph/tags" 18 "github.com/docker/docker/image" 19 "github.com/docker/docker/pkg/parsers" 20 "github.com/docker/docker/pkg/progressreader" 21 "github.com/docker/docker/pkg/stringid" 22 "github.com/docker/docker/registry" 23 "github.com/docker/docker/trust" 24 "github.com/docker/docker/utils" 25 "github.com/docker/libtrust" 26 ) 27 28 // TagStore manages repositories. It encompasses the Graph used for versioned 29 // storage, as well as various services involved in pushing and pulling 30 // repositories. 31 type TagStore struct { 32 path string 33 graph *Graph 34 // Repositories is a map of repositories, indexed by name. 35 Repositories map[string]Repository 36 trustKey libtrust.PrivateKey 37 sync.Mutex 38 // FIXME: move push/pull-related fields 39 // to a helper type 40 pullingPool map[string]*progressreader.Broadcaster 41 pushingPool map[string]*progressreader.Broadcaster 42 registryService *registry.Service 43 eventsService *events.Events 44 trustService *trust.Store 45 } 46 47 // Repository maps tags to image IDs. 48 type Repository map[string]string 49 50 // Update updates repository mapping with content of repository 'u'. 51 func (r Repository) Update(u Repository) { 52 for k, v := range u { 53 r[k] = v 54 } 55 } 56 57 // Contains returns true if the contents of Repository u are wholly contained 58 // in Repository r. 59 func (r Repository) Contains(u Repository) bool { 60 for k, v := range u { 61 // if u's key is not present in r OR u's key is present, but not the same value 62 if rv, ok := r[k]; !ok || (ok && rv != v) { 63 return false 64 } 65 } 66 return true 67 } 68 69 // TagStoreConfig provides parameters for a new TagStore. 70 type TagStoreConfig struct { 71 // Graph is the versioned image store 72 Graph *Graph 73 // Key is the private key to use for signing manifests. 74 Key libtrust.PrivateKey 75 // Registry is the registry service to use for TLS configuration and 76 // endpoint lookup. 77 Registry *registry.Service 78 // Events is the events service to use for logging. 79 Events *events.Events 80 // Trust is the trust service to use for push and pull operations. 81 Trust *trust.Store 82 } 83 84 // NewTagStore creates a new TagStore at specified path, using the parameters 85 // and services provided in cfg. 86 func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) { 87 abspath, err := filepath.Abs(path) 88 if err != nil { 89 return nil, err 90 } 91 92 store := &TagStore{ 93 path: abspath, 94 graph: cfg.Graph, 95 trustKey: cfg.Key, 96 Repositories: make(map[string]Repository), 97 pullingPool: make(map[string]*progressreader.Broadcaster), 98 pushingPool: make(map[string]*progressreader.Broadcaster), 99 registryService: cfg.Registry, 100 eventsService: cfg.Events, 101 trustService: cfg.Trust, 102 } 103 // Load the json file if it exists, otherwise create it. 104 if err := store.reload(); os.IsNotExist(err) { 105 if err := store.save(); err != nil { 106 return nil, err 107 } 108 } else if err != nil { 109 return nil, err 110 } 111 return store, nil 112 } 113 114 func (store *TagStore) save() error { 115 // Store the json ball 116 jsonData, err := json.Marshal(store) 117 if err != nil { 118 return err 119 } 120 if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil { 121 return err 122 } 123 return nil 124 } 125 126 func (store *TagStore) reload() error { 127 f, err := os.Open(store.path) 128 if err != nil { 129 return err 130 } 131 defer f.Close() 132 if err := json.NewDecoder(f).Decode(&store); err != nil { 133 return err 134 } 135 return nil 136 } 137 138 // LookupImage returns pointer to an Image struct corresponding to the given 139 // name. The name can include an optional tag; otherwise the default tag will 140 // be used. 141 func (store *TagStore) LookupImage(name string) (*image.Image, error) { 142 // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else 143 // (so we can pass all errors here) 144 repoName, ref := parsers.ParseRepositoryTag(name) 145 if ref == "" { 146 ref = tags.DefaultTag 147 } 148 var ( 149 err error 150 img *image.Image 151 ) 152 153 img, err = store.GetImage(repoName, ref) 154 if err != nil { 155 return nil, err 156 } 157 158 if img != nil { 159 return img, nil 160 } 161 162 // name must be an image ID. 163 store.Lock() 164 defer store.Unlock() 165 if img, err = store.graph.Get(name); err != nil { 166 return nil, err 167 } 168 169 return img, nil 170 } 171 172 // ByID returns a reverse-lookup table of all the names which refer to each 173 // image - e.g. {"43b5f19b10584": {"base:latest", "base:v1"}} 174 func (store *TagStore) ByID() map[string][]string { 175 store.Lock() 176 defer store.Unlock() 177 byID := make(map[string][]string) 178 for repoName, repository := range store.Repositories { 179 for tag, id := range repository { 180 name := utils.ImageReference(repoName, tag) 181 if _, exists := byID[id]; !exists { 182 byID[id] = []string{name} 183 } else { 184 byID[id] = append(byID[id], name) 185 sort.Strings(byID[id]) 186 } 187 } 188 } 189 return byID 190 } 191 192 // HasReferences returns whether or not the given image is referenced in one or 193 // more repositories. 194 func (store *TagStore) HasReferences(img *image.Image) bool { 195 return len(store.ByID()[img.ID]) > 0 196 } 197 198 // ImageName returns name of an image, given the image's ID. 199 func (store *TagStore) ImageName(id string) string { 200 if names, exists := store.ByID()[id]; exists && len(names) > 0 { 201 return names[0] 202 } 203 return stringid.TruncateID(id) 204 } 205 206 // DeleteAll removes images identified by a specific ID from the store. 207 func (store *TagStore) DeleteAll(id string) error { 208 names, exists := store.ByID()[id] 209 if !exists || len(names) == 0 { 210 return nil 211 } 212 for _, name := range names { 213 if strings.Contains(name, ":") { 214 nameParts := strings.Split(name, ":") 215 if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil { 216 return err 217 } 218 } else { 219 if _, err := store.Delete(name, ""); err != nil { 220 return err 221 } 222 } 223 } 224 return nil 225 } 226 227 // Delete deletes a repository or a specific tag. If ref is empty, the entire 228 // repository named repoName will be deleted; otherwise only the tag named by 229 // ref will be deleted. 230 func (store *TagStore) Delete(repoName, ref string) (bool, error) { 231 store.Lock() 232 defer store.Unlock() 233 deleted := false 234 if err := store.reload(); err != nil { 235 return false, err 236 } 237 238 repoName = registry.NormalizeLocalName(repoName) 239 240 if ref == "" { 241 // Delete the whole repository. 242 delete(store.Repositories, repoName) 243 return true, store.save() 244 } 245 246 repoRefs, exists := store.Repositories[repoName] 247 if !exists { 248 return false, fmt.Errorf("No such repository: %s", repoName) 249 } 250 251 if _, exists := repoRefs[ref]; exists { 252 delete(repoRefs, ref) 253 if len(repoRefs) == 0 { 254 delete(store.Repositories, repoName) 255 } 256 deleted = true 257 } 258 259 return deleted, store.save() 260 } 261 262 // Tag creates a tag in the repository reponame, pointing to the image named 263 // imageName. If force is true, an existing tag with the same name may be 264 // overwritten. 265 func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error { 266 return store.setLoad(repoName, tag, imageName, force, nil) 267 } 268 269 // setLoad stores the image to the store. 270 // If the imageName is already in the repo then a '-f' flag should be used to replace existing image. 271 func (store *TagStore) setLoad(repoName, tag, imageName string, force bool, out io.Writer) error { 272 img, err := store.LookupImage(imageName) 273 store.Lock() 274 defer store.Unlock() 275 if err != nil { 276 return err 277 } 278 if tag == "" { 279 tag = tags.DefaultTag 280 } 281 if err := validateRepoName(repoName); err != nil { 282 return err 283 } 284 if err := tags.ValidateTagName(tag); err != nil { 285 if _, formatError := err.(tags.ErrTagInvalidFormat); !formatError { 286 return err 287 } 288 if _, dErr := digest.ParseDigest(tag); dErr != nil { 289 // Still return the tag validation error. 290 // It's more likely to be a user generated issue. 291 return err 292 } 293 } 294 if err := store.reload(); err != nil { 295 return err 296 } 297 var repo Repository 298 repoName = registry.NormalizeLocalName(repoName) 299 if r, exists := store.Repositories[repoName]; exists { 300 repo = r 301 if old, exists := store.Repositories[repoName][tag]; exists { 302 303 if !force { 304 return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old) 305 } 306 307 if old != img.ID && out != nil { 308 309 fmt.Fprintf(out, "The image %s:%s already exists, renaming the old one with ID %s to empty string\n", repoName, tag, old[:12]) 310 311 } 312 } 313 } else { 314 repo = make(map[string]string) 315 store.Repositories[repoName] = repo 316 } 317 repo[tag] = img.ID 318 return store.save() 319 } 320 321 // SetDigest creates a digest reference to an image ID. 322 func (store *TagStore) SetDigest(repoName, digest, imageName string) error { 323 img, err := store.LookupImage(imageName) 324 if err != nil { 325 return err 326 } 327 328 if err := validateRepoName(repoName); err != nil { 329 return err 330 } 331 332 if err := validateDigest(digest); err != nil { 333 return err 334 } 335 336 store.Lock() 337 defer store.Unlock() 338 if err := store.reload(); err != nil { 339 return err 340 } 341 342 repoName = registry.NormalizeLocalName(repoName) 343 repoRefs, exists := store.Repositories[repoName] 344 if !exists { 345 repoRefs = Repository{} 346 store.Repositories[repoName] = repoRefs 347 } else if oldID, exists := repoRefs[digest]; exists && oldID != img.ID { 348 return fmt.Errorf("Conflict: Digest %s is already set to image %s", digest, oldID) 349 } 350 351 repoRefs[digest] = img.ID 352 return store.save() 353 } 354 355 // Get returns the Repository tag/image map for a given repository. 356 func (store *TagStore) Get(repoName string) (Repository, error) { 357 store.Lock() 358 defer store.Unlock() 359 if err := store.reload(); err != nil { 360 return nil, err 361 } 362 repoName = registry.NormalizeLocalName(repoName) 363 if r, exists := store.Repositories[repoName]; exists { 364 return r, nil 365 } 366 return nil, nil 367 } 368 369 // GetImage returns a pointer to an Image structure describing the image 370 // referred to by refOrID inside repository repoName. 371 func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) { 372 repo, err := store.Get(repoName) 373 374 if err != nil { 375 return nil, err 376 } 377 if repo == nil { 378 return nil, nil 379 } 380 381 store.Lock() 382 defer store.Unlock() 383 if imgID, exists := repo[refOrID]; exists { 384 return store.graph.Get(imgID) 385 } 386 387 // If no matching tag is found, search through images for a matching image id 388 // iff it looks like a short ID or would look like a short ID 389 if stringid.IsShortID(stringid.TruncateID(refOrID)) { 390 for _, revision := range repo { 391 if strings.HasPrefix(revision, refOrID) { 392 return store.graph.Get(revision) 393 } 394 } 395 } 396 397 return nil, nil 398 } 399 400 // GetRepoRefs returns a map with image IDs as keys, and slices listing 401 // repo/tag references as the values. It covers all repositories. 402 func (store *TagStore) GetRepoRefs() map[string][]string { 403 store.Lock() 404 reporefs := make(map[string][]string) 405 406 for name, repository := range store.Repositories { 407 for tag, id := range repository { 408 shortID := stringid.TruncateID(id) 409 reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag)) 410 } 411 } 412 store.Unlock() 413 return reporefs 414 } 415 416 // validateRepoName validates the name of a repository. 417 func validateRepoName(name string) error { 418 if name == "" { 419 return fmt.Errorf("Repository name can't be empty") 420 } 421 if name == "scratch" { 422 return fmt.Errorf("'scratch' is a reserved name") 423 } 424 return nil 425 } 426 427 func validateDigest(dgst string) error { 428 if dgst == "" { 429 return errors.New("digest can't be empty") 430 } 431 if _, err := digest.ParseDigest(dgst); err != nil { 432 return err 433 } 434 return nil 435 } 436 437 // poolAdd checks if a push or pull is already running, and returns 438 // (broadcaster, true) if a running operation is found. Otherwise, it creates a 439 // new one and returns (broadcaster, false). 440 func (store *TagStore) poolAdd(kind, key string) (*progressreader.Broadcaster, bool) { 441 store.Lock() 442 defer store.Unlock() 443 444 if p, exists := store.pullingPool[key]; exists { 445 return p, true 446 } 447 if p, exists := store.pushingPool[key]; exists { 448 return p, true 449 } 450 451 broadcaster := progressreader.NewBroadcaster() 452 453 switch kind { 454 case "pull": 455 store.pullingPool[key] = broadcaster 456 case "push": 457 store.pushingPool[key] = broadcaster 458 default: 459 panic("Unknown pool type") 460 } 461 462 return broadcaster, false 463 } 464 465 func (store *TagStore) poolRemoveWithError(kind, key string, broadcasterResult error) error { 466 store.Lock() 467 defer store.Unlock() 468 switch kind { 469 case "pull": 470 if broadcaster, exists := store.pullingPool[key]; exists { 471 broadcaster.CloseWithError(broadcasterResult) 472 delete(store.pullingPool, key) 473 } 474 case "push": 475 if broadcaster, exists := store.pushingPool[key]; exists { 476 broadcaster.CloseWithError(broadcasterResult) 477 delete(store.pushingPool, key) 478 } 479 default: 480 return fmt.Errorf("Unknown pool type") 481 } 482 return nil 483 } 484 485 func (store *TagStore) poolRemove(kind, key string) error { 486 return store.poolRemoveWithError(kind, key, nil) 487 }