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