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