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