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