github.com/christopherobin/docker@v1.6.2/graph/tags.go (about) 1 package graph 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "regexp" 11 "sort" 12 "strings" 13 "sync" 14 15 "github.com/docker/docker/image" 16 "github.com/docker/docker/pkg/common" 17 "github.com/docker/docker/pkg/parsers" 18 "github.com/docker/docker/registry" 19 "github.com/docker/docker/utils" 20 "github.com/docker/libtrust" 21 ) 22 23 const DEFAULTTAG = "latest" 24 25 var ( 26 //FIXME these 2 regexes also exist in registry/v2/regexp.go 27 validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`) 28 validDigest = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`) 29 ) 30 31 type TagStore struct { 32 path string 33 graph *Graph 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 } 42 43 type Repository map[string]string 44 45 // update Repository mapping with content of u 46 func (r Repository) Update(u Repository) { 47 for k, v := range u { 48 r[k] = v 49 } 50 } 51 52 // return true if the contents of u Repository, are wholly contained in r Repository 53 func (r Repository) Contains(u Repository) bool { 54 for k, v := range u { 55 // if u's key is not present in r OR u's key is present, but not the same value 56 if rv, ok := r[k]; !ok || (ok && rv != v) { 57 return false 58 } 59 } 60 return true 61 } 62 63 func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey) (*TagStore, error) { 64 abspath, err := filepath.Abs(path) 65 if err != nil { 66 return nil, err 67 } 68 69 store := &TagStore{ 70 path: abspath, 71 graph: graph, 72 trustKey: key, 73 Repositories: make(map[string]Repository), 74 pullingPool: make(map[string]chan struct{}), 75 pushingPool: make(map[string]chan struct{}), 76 } 77 // Load the json file if it exists, otherwise create it. 78 if err := store.reload(); os.IsNotExist(err) { 79 if err := store.save(); err != nil { 80 return nil, err 81 } 82 } else if err != nil { 83 return nil, err 84 } 85 return store, nil 86 } 87 88 func (store *TagStore) save() error { 89 // Store the json ball 90 jsonData, err := json.Marshal(store) 91 if err != nil { 92 return err 93 } 94 if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil { 95 return err 96 } 97 return nil 98 } 99 100 func (store *TagStore) reload() error { 101 jsonData, err := ioutil.ReadFile(store.path) 102 if err != nil { 103 return err 104 } 105 if err := json.Unmarshal(jsonData, store); err != nil { 106 return err 107 } 108 return nil 109 } 110 111 func (store *TagStore) LookupImage(name string) (*image.Image, error) { 112 // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else 113 // (so we can pass all errors here) 114 repoName, ref := parsers.ParseRepositoryTag(name) 115 if ref == "" { 116 ref = DEFAULTTAG 117 } 118 var ( 119 err error 120 img *image.Image 121 ) 122 123 img, err = store.GetImage(repoName, ref) 124 if err != nil { 125 return nil, err 126 } 127 128 if img != nil { 129 return img, err 130 } 131 132 // name must be an image ID. 133 store.Lock() 134 defer store.Unlock() 135 if img, err = store.graph.Get(name); err != nil { 136 return nil, err 137 } 138 139 return img, nil 140 } 141 142 // Return a reverse-lookup table of all the names which refer to each image 143 // Eg. {"43b5f19b10584": {"base:latest", "base:v1"}} 144 func (store *TagStore) ByID() map[string][]string { 145 store.Lock() 146 defer store.Unlock() 147 byID := make(map[string][]string) 148 for repoName, repository := range store.Repositories { 149 for tag, id := range repository { 150 name := utils.ImageReference(repoName, tag) 151 if _, exists := byID[id]; !exists { 152 byID[id] = []string{name} 153 } else { 154 byID[id] = append(byID[id], name) 155 sort.Strings(byID[id]) 156 } 157 } 158 } 159 return byID 160 } 161 162 func (store *TagStore) ImageName(id string) string { 163 if names, exists := store.ByID()[id]; exists && len(names) > 0 { 164 return names[0] 165 } 166 return common.TruncateID(id) 167 } 168 169 func (store *TagStore) DeleteAll(id string) error { 170 names, exists := store.ByID()[id] 171 if !exists || len(names) == 0 { 172 return nil 173 } 174 for _, name := range names { 175 if strings.Contains(name, ":") { 176 nameParts := strings.Split(name, ":") 177 if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil { 178 return err 179 } 180 } else { 181 if _, err := store.Delete(name, ""); err != nil { 182 return err 183 } 184 } 185 } 186 return nil 187 } 188 189 func (store *TagStore) Delete(repoName, ref string) (bool, error) { 190 store.Lock() 191 defer store.Unlock() 192 deleted := false 193 if err := store.reload(); err != nil { 194 return false, err 195 } 196 197 repoName = registry.NormalizeLocalName(repoName) 198 199 if ref == "" { 200 // Delete the whole repository. 201 delete(store.Repositories, repoName) 202 return true, store.save() 203 } 204 205 repoRefs, exists := store.Repositories[repoName] 206 if !exists { 207 return false, fmt.Errorf("No such repository: %s", repoName) 208 } 209 210 if _, exists := repoRefs[ref]; exists { 211 delete(repoRefs, ref) 212 if len(repoRefs) == 0 { 213 delete(store.Repositories, repoName) 214 } 215 deleted = true 216 } 217 218 return deleted, store.save() 219 } 220 221 func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { 222 img, err := store.LookupImage(imageName) 223 store.Lock() 224 defer store.Unlock() 225 if err != nil { 226 return err 227 } 228 if tag == "" { 229 tag = DEFAULTTAG 230 } 231 if err := validateRepoName(repoName); err != nil { 232 return err 233 } 234 if err := ValidateTagName(tag); err != nil { 235 return err 236 } 237 if err := store.reload(); err != nil { 238 return err 239 } 240 var repo Repository 241 repoName = registry.NormalizeLocalName(repoName) 242 if r, exists := store.Repositories[repoName]; exists { 243 repo = r 244 if old, exists := store.Repositories[repoName][tag]; exists && !force { 245 return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old) 246 } 247 } else { 248 repo = make(map[string]string) 249 store.Repositories[repoName] = repo 250 } 251 repo[tag] = img.ID 252 return store.save() 253 } 254 255 // SetDigest creates a digest reference to an image ID. 256 func (store *TagStore) SetDigest(repoName, digest, imageName string) error { 257 img, err := store.LookupImage(imageName) 258 if err != nil { 259 return err 260 } 261 262 if err := validateRepoName(repoName); err != nil { 263 return err 264 } 265 266 if err := validateDigest(digest); err != nil { 267 return err 268 } 269 270 store.Lock() 271 defer store.Unlock() 272 if err := store.reload(); err != nil { 273 return err 274 } 275 276 repoName = registry.NormalizeLocalName(repoName) 277 repoRefs, exists := store.Repositories[repoName] 278 if !exists { 279 repoRefs = Repository{} 280 store.Repositories[repoName] = repoRefs 281 } else if oldID, exists := repoRefs[digest]; exists && oldID != img.ID { 282 return fmt.Errorf("Conflict: Digest %s is already set to image %s", digest, oldID) 283 } 284 285 repoRefs[digest] = img.ID 286 return store.save() 287 } 288 289 func (store *TagStore) Get(repoName string) (Repository, error) { 290 store.Lock() 291 defer store.Unlock() 292 if err := store.reload(); err != nil { 293 return nil, err 294 } 295 repoName = registry.NormalizeLocalName(repoName) 296 if r, exists := store.Repositories[repoName]; exists { 297 return r, nil 298 } 299 return nil, nil 300 } 301 302 func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) { 303 repo, err := store.Get(repoName) 304 305 if err != nil { 306 return nil, err 307 } 308 if repo == nil { 309 return nil, nil 310 } 311 312 store.Lock() 313 defer store.Unlock() 314 if imgID, exists := repo[refOrID]; exists { 315 return store.graph.Get(imgID) 316 } 317 318 // If no matching tag is found, search through images for a matching image id 319 for _, revision := range repo { 320 if strings.HasPrefix(revision, refOrID) { 321 return store.graph.Get(revision) 322 } 323 } 324 325 return nil, nil 326 } 327 328 func (store *TagStore) GetRepoRefs() map[string][]string { 329 store.Lock() 330 reporefs := make(map[string][]string) 331 332 for name, repository := range store.Repositories { 333 for tag, id := range repository { 334 shortID := common.TruncateID(id) 335 reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag)) 336 } 337 } 338 store.Unlock() 339 return reporefs 340 } 341 342 // Validate the name of a repository 343 func validateRepoName(name string) error { 344 if name == "" { 345 return fmt.Errorf("Repository name can't be empty") 346 } 347 if name == "scratch" { 348 return fmt.Errorf("'scratch' is a reserved name") 349 } 350 return nil 351 } 352 353 // ValidateTagName validates the name of a tag 354 func ValidateTagName(name string) error { 355 if name == "" { 356 return fmt.Errorf("tag name can't be empty") 357 } 358 if !validTagName.MatchString(name) { 359 return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 1, maximum 128 in length", name) 360 } 361 return nil 362 } 363 364 func validateDigest(dgst string) error { 365 if dgst == "" { 366 return errors.New("digest can't be empty") 367 } 368 if !validDigest.MatchString(dgst) { 369 return fmt.Errorf("illegal digest (%s): must be of the form [a-zA-Z0-9-_+.]+:[a-fA-F0-9]+", dgst) 370 } 371 return nil 372 } 373 374 func (store *TagStore) poolAdd(kind, key string) (chan struct{}, error) { 375 store.Lock() 376 defer store.Unlock() 377 378 if c, exists := store.pullingPool[key]; exists { 379 return c, fmt.Errorf("pull %s is already in progress", key) 380 } 381 if c, exists := store.pushingPool[key]; exists { 382 return c, fmt.Errorf("push %s is already in progress", key) 383 } 384 385 c := make(chan struct{}) 386 switch kind { 387 case "pull": 388 store.pullingPool[key] = c 389 case "push": 390 store.pushingPool[key] = c 391 default: 392 return nil, fmt.Errorf("Unknown pool type") 393 } 394 return c, nil 395 } 396 397 func (store *TagStore) poolRemove(kind, key string) error { 398 store.Lock() 399 defer store.Unlock() 400 switch kind { 401 case "pull": 402 if c, exists := store.pullingPool[key]; exists { 403 close(c) 404 delete(store.pullingPool, key) 405 } 406 case "push": 407 if c, exists := store.pushingPool[key]; exists { 408 close(c) 409 delete(store.pushingPool, key) 410 } 411 default: 412 return fmt.Errorf("Unknown pool type") 413 } 414 return nil 415 }