github.com/Cloud-Foundations/Dominator@v0.3.4/imageserver/scanner/imdb.go (about) 1 package scanner 2 3 import ( 4 "bufio" 5 "encoding/gob" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "path/filepath" 11 "syscall" 12 "time" 13 14 "github.com/Cloud-Foundations/Dominator/lib/format" 15 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 16 "github.com/Cloud-Foundations/Dominator/lib/image" 17 "github.com/Cloud-Foundations/Dominator/lib/log" 18 "github.com/Cloud-Foundations/Dominator/lib/srpc" 19 "github.com/Cloud-Foundations/Dominator/lib/tags/tagmatcher" 20 proto "github.com/Cloud-Foundations/Dominator/proto/imageserver" 21 ) 22 23 const ( 24 dirPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP | 25 syscall.S_IROTH | syscall.S_IXOTH 26 filePerms = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IRGRP | 27 syscall.S_IROTH 28 ) 29 30 var ( 31 errNoAccess = errors.New("no access to image") 32 errNoAuthInfo = errors.New("no authentication information") 33 ) 34 35 func (imdb *ImageDataBase) addImage(img *image.Image, name string, 36 authInfo *srpc.AuthInformation) error { 37 if err := img.Verify(); err != nil { 38 return err 39 } 40 if imageIsExpired(img) { 41 imdb.Logger.Printf("Ignoring already expired image: %s\n", name) 42 return nil 43 } 44 imdb.Lock() 45 doUnlock := true 46 defer func() { 47 if doUnlock { 48 imdb.Unlock() 49 } 50 }() 51 if _, ok := imdb.imageMap[name]; ok { 52 return errors.New("image: " + name + " already exists") 53 } else { 54 if err := imdb.checkPermissions(name, nil, authInfo); err != nil { 55 return err 56 } 57 filename := filepath.Join(imdb.BaseDirectory, name) 58 flags := os.O_CREATE | os.O_RDWR 59 if imdb.ReplicationMaster == "" { 60 flags |= os.O_EXCL // I am the master. 61 } else { 62 flags |= os.O_TRUNC 63 } 64 file, err := os.OpenFile(filename, flags, filePerms) 65 if err != nil { 66 if os.IsExist(err) { 67 return errors.New("cannot add previously deleted image: " + 68 name) 69 } 70 return err 71 } 72 defer file.Close() 73 w := bufio.NewWriter(file) 74 defer w.Flush() 75 writer := fsutil.NewChecksumWriter(w) 76 defer writer.WriteChecksum() 77 encoder := gob.NewEncoder(writer) 78 if err := encoder.Encode(img); err != nil { 79 os.Remove(filename) 80 return err 81 } 82 if err := w.Flush(); err != nil { 83 os.Remove(filename) 84 return err 85 } 86 imdb.scheduleExpiration(img, name) 87 imdb.imageMap[name] = img 88 imdb.addNotifiers.sendPlain(name, "add", imdb.Logger) 89 doUnlock = false 90 imdb.Unlock() 91 return imdb.Params.ObjectServer.AdjustRefcounts(true, img) 92 } 93 } 94 95 func (imdb *ImageDataBase) changeImageExpiration(name string, 96 expiresAt time.Time, authInfo *srpc.AuthInformation) (bool, error) { 97 if err := imdb.checkExpiration(expiresAt, authInfo); err != nil { 98 return false, err 99 } 100 imdb.Lock() 101 defer imdb.Unlock() 102 if img, ok := imdb.imageMap[name]; !ok { 103 return false, errors.New("image not found") 104 } else if err := imdb.checkPermissions(name, img, authInfo); err != nil { 105 return false, err 106 } else if img.ExpiresAt.IsZero() { 107 return false, errors.New("image does not expire") 108 } else if expiresAt.IsZero() { 109 if err := imdb.writeNewExpiration(name, img, expiresAt); err != nil { 110 return false, err 111 } 112 img.ExpiresAt = expiresAt 113 imdb.addNotifiers.sendPlain(name, "add", imdb.Logger) 114 return true, nil 115 } else if expiresAt.Before(img.ExpiresAt) { 116 return false, errors.New("cannot shorten expiration time") 117 } else if expiresAt.After(img.ExpiresAt) { 118 if err := imdb.writeNewExpiration(name, img, expiresAt); err != nil { 119 return false, err 120 } 121 img.ExpiresAt = expiresAt 122 imdb.addNotifiers.sendPlain(name, "add", imdb.Logger) 123 return true, nil 124 } else { 125 return false, nil 126 } 127 } 128 129 // This must be called with the lock held. 130 func (imdb *ImageDataBase) checkChown(dirname, ownerGroup string, 131 authInfo *srpc.AuthInformation) error { 132 if authInfo == nil { 133 return errNoAuthInfo 134 } 135 if authInfo.HaveMethodAccess { 136 return nil 137 } 138 // If owner of parent, any group can be set. 139 parentDirname := filepath.Dir(dirname) 140 if directoryMetadata, ok := imdb.directoryMap[parentDirname]; ok { 141 if directoryMetadata.OwnerGroup != "" { 142 if _, ok := authInfo.GroupList[directoryMetadata.OwnerGroup]; ok { 143 return nil 144 } 145 } 146 } 147 if _, ok := authInfo.GroupList[ownerGroup]; !ok { 148 return fmt.Errorf("no membership of %s group", ownerGroup) 149 } 150 if directoryMetadata, ok := imdb.directoryMap[dirname]; !ok { 151 return fmt.Errorf("no metadata for: \"%s\"", dirname) 152 } else if directoryMetadata.OwnerGroup != "" { 153 if _, ok := authInfo.GroupList[directoryMetadata.OwnerGroup]; ok { 154 return nil 155 } 156 } 157 return errNoAccess 158 } 159 160 func (imdb *ImageDataBase) checkDirectory(name string) bool { 161 imdb.RLock() 162 defer imdb.RUnlock() 163 _, ok := imdb.directoryMap[name] 164 return ok 165 } 166 167 func (imdb *ImageDataBase) checkExpiration(expiresAt time.Time, 168 authInfo *srpc.AuthInformation) error { 169 if expiresAt.IsZero() { 170 return nil 171 } 172 expiresIn := time.Until(expiresAt) 173 if authInfo != nil && authInfo.HaveMethodAccess { 174 if authInfo.Username == "" { 175 return nil // Internal call. 176 } 177 if expiresIn > imdb.MaximumExpirationDurationPrivileged { 178 return fmt.Errorf("maximum expiration time is %s for you", 179 format.Duration(imdb.MaximumExpirationDurationPrivileged)) 180 } 181 return nil 182 } 183 if expiresIn > imdb.MaximumExpirationDuration { 184 return fmt.Errorf("maximum expiration time is %s", 185 format.Duration(imdb.MaximumExpirationDuration)) 186 } 187 return nil 188 } 189 190 func (imdb *ImageDataBase) checkImage(name string) bool { 191 imdb.RLock() 192 defer imdb.RUnlock() 193 _, ok := imdb.imageMap[name] 194 return ok 195 } 196 197 // This must be called with the lock held. 198 func (imdb *ImageDataBase) checkPermissions(imageName string, img *image.Image, 199 authInfo *srpc.AuthInformation) error { 200 if authInfo == nil { 201 return errNoAuthInfo 202 } 203 if authInfo.HaveMethodAccess { 204 return nil 205 } 206 if authInfo.Username != "" && img != nil { 207 if img.CreatedBy == authInfo.Username || 208 img.CreatedFor == authInfo.Username { 209 return nil 210 } 211 } 212 dirname := filepath.Dir(imageName) 213 if directoryMetadata, ok := imdb.directoryMap[dirname]; !ok { 214 return fmt.Errorf("no metadata for: \"%s\"", dirname) 215 } else if directoryMetadata.OwnerGroup != "" { 216 if _, ok := authInfo.GroupList[directoryMetadata.OwnerGroup]; ok { 217 return nil 218 } 219 } 220 return errNoAccess 221 } 222 223 func (imdb *ImageDataBase) chownDirectory(dirname, ownerGroup string, 224 authInfo *srpc.AuthInformation) error { 225 dirname = filepath.Clean(dirname) 226 imdb.Lock() 227 defer imdb.Unlock() 228 directoryMetadata, ok := imdb.directoryMap[dirname] 229 if !ok { 230 return fmt.Errorf("no metadata for: \"%s\"", dirname) 231 } 232 if err := imdb.checkChown(dirname, ownerGroup, authInfo); err != nil { 233 return err 234 } 235 directoryMetadata.OwnerGroup = ownerGroup 236 return imdb.updateDirectoryMetadata( 237 image.Directory{Name: dirname, Metadata: directoryMetadata}) 238 } 239 240 // This must be called with the lock held. 241 func (imdb *ImageDataBase) updateDirectoryMetadata( 242 directory image.Directory) error { 243 oldDirectoryMetadata, ok := imdb.directoryMap[directory.Name] 244 if ok && directory.Metadata == oldDirectoryMetadata { 245 return nil 246 } 247 if err := imdb.updateDirectoryMetadataFile(directory); err != nil { 248 return err 249 } 250 imdb.directoryMap[directory.Name] = directory.Metadata 251 imdb.mkdirNotifiers.sendMakeDirectory(directory, imdb.Logger) 252 return nil 253 } 254 255 func (imdb *ImageDataBase) updateDirectoryMetadataFile( 256 directory image.Directory) error { 257 filename := filepath.Join(imdb.BaseDirectory, directory.Name, metadataFile) 258 _, ok := imdb.directoryMap[directory.Name] 259 if directory.Metadata == (image.DirectoryMetadata{}) { 260 if !ok { 261 return nil 262 } 263 return os.Remove(filename) 264 } 265 file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, filePerms) 266 if err != nil { 267 return err 268 } 269 if err := writeDirectoryMetadata(file, directory.Metadata); err != nil { 270 file.Close() 271 return err 272 } 273 return file.Close() 274 } 275 276 func writeDirectoryMetadata(file io.Writer, 277 directoryMetadata image.DirectoryMetadata) error { 278 w := bufio.NewWriter(file) 279 writer := fsutil.NewChecksumWriter(w) 280 if err := gob.NewEncoder(writer).Encode(directoryMetadata); err != nil { 281 return err 282 } 283 if err := writer.WriteChecksum(); err != nil { 284 return err 285 } 286 return w.Flush() 287 } 288 289 func (imdb *ImageDataBase) countDirectories() uint { 290 imdb.RLock() 291 defer imdb.RUnlock() 292 return uint(len(imdb.directoryMap)) 293 } 294 295 func (imdb *ImageDataBase) countImages() uint { 296 imdb.RLock() 297 defer imdb.RUnlock() 298 return uint(len(imdb.imageMap)) 299 } 300 301 func (imdb *ImageDataBase) deleteImage(name string, 302 authInfo *srpc.AuthInformation) error { 303 imdb.Lock() 304 defer imdb.Unlock() 305 if img, ok := imdb.imageMap[name]; ok { 306 if err := imdb.checkPermissions(name, img, authInfo); err != nil { 307 return err 308 } 309 filename := filepath.Join(imdb.BaseDirectory, name) 310 if err := os.Truncate(filename, 0); err != nil { 311 return err 312 } 313 imdb.deleteImageAndUpdateUnreferencedObjectsList(name) 314 imdb.deleteNotifiers.sendPlain(name, "delete", imdb.Logger) 315 return nil 316 } else { 317 return errors.New("image: " + name + " does not exist") 318 } 319 } 320 321 // This must be called with the main lock held. 322 func (imdb *ImageDataBase) deleteImageAndUpdateUnreferencedObjectsList( 323 name string) { 324 img := imdb.imageMap[name] 325 if img == nil { // May be nil if expiring an already deleted image. 326 return 327 } 328 delete(imdb.imageMap, name) 329 imdb.Params.ObjectServer.AdjustRefcounts(false, img) 330 } 331 332 func (imdb *ImageDataBase) doWithPendingImage(img *image.Image, 333 doFunc func() error) error { 334 imdb.pendingImageLock.Lock() 335 defer imdb.pendingImageLock.Unlock() 336 return doFunc() 337 } 338 339 func (imdb *ImageDataBase) findLatestImage( 340 request proto.FindLatestImageRequest) (string, error) { 341 imdb.RLock() 342 defer imdb.RUnlock() 343 if _, ok := imdb.directoryMap[request.DirectoryName]; !ok { 344 return "", errors.New("unknown directory: " + request.DirectoryName) 345 } 346 var previousCreateTime time.Time 347 var imageName string 348 tagMatcher := tagmatcher.New(request.TagsToMatch, false) 349 for name, img := range imdb.imageMap { 350 if request.IgnoreExpiringImages && !img.ExpiresAt.IsZero() { 351 continue 352 } 353 // First filter out images we don't want. 354 if filepath.Dir(name) != request.DirectoryName { 355 continue 356 } 357 if request.BuildCommitId != "" && 358 request.BuildCommitId != img.BuildCommitId { 359 continue 360 } 361 if !tagMatcher.MatchEach(img.Tags) { 362 continue 363 } 364 // Select newer image after filtering. 365 if img.CreatedOn.After(previousCreateTime) { 366 imageName = name 367 previousCreateTime = img.CreatedOn 368 } 369 } 370 return imageName, nil 371 } 372 373 func (imdb *ImageDataBase) getImage(name string) *image.Image { 374 imdb.RLock() 375 defer imdb.RUnlock() 376 return imdb.imageMap[name] 377 } 378 379 func (imdb *ImageDataBase) listDirectories() []image.Directory { 380 imdb.RLock() 381 defer imdb.RUnlock() 382 directories := make([]image.Directory, 0, len(imdb.directoryMap)) 383 for name, metadata := range imdb.directoryMap { 384 directories = append(directories, 385 image.Directory{Name: name, Metadata: metadata}) 386 } 387 return directories 388 } 389 390 func (imdb *ImageDataBase) listImages( 391 request proto.ListSelectedImagesRequest) []string { 392 tagMatcher := tagmatcher.New(request.TagsToMatch, false) 393 imdb.RLock() 394 defer imdb.RUnlock() 395 names := make([]string, 0) 396 for name, img := range imdb.imageMap { 397 if request.IgnoreExpiringImages && !img.ExpiresAt.IsZero() { 398 continue 399 } 400 if !tagMatcher.MatchEach(img.Tags) { 401 continue 402 } 403 names = append(names, name) 404 } 405 return names 406 } 407 408 func (imdb *ImageDataBase) makeDirectory(directory image.Directory, 409 authInfo *srpc.AuthInformation, userRpc bool) error { 410 directory.Name = filepath.Clean(directory.Name) 411 pathname := filepath.Join(imdb.BaseDirectory, directory.Name) 412 imdb.Lock() 413 defer imdb.Unlock() 414 oldDirectoryMetadata, ok := imdb.directoryMap[directory.Name] 415 if userRpc { 416 if authInfo == nil { 417 return errNoAuthInfo 418 } 419 if ok { 420 return fmt.Errorf("directory: %s already exists", directory.Name) 421 } 422 directory.Metadata = oldDirectoryMetadata 423 parentMetadata, ok := imdb.directoryMap[filepath.Dir(directory.Name)] 424 if !ok { 425 return fmt.Errorf("no metadata for: %s", 426 filepath.Dir(directory.Name)) 427 } 428 if !authInfo.HaveMethodAccess { 429 if parentMetadata.OwnerGroup == "" { 430 return errNoAccess 431 } 432 if _, ok := authInfo.GroupList[parentMetadata.OwnerGroup]; !ok { 433 return fmt.Errorf("no membership of %s group", 434 parentMetadata.OwnerGroup) 435 } 436 } 437 directory.Metadata.OwnerGroup = parentMetadata.OwnerGroup 438 } 439 if err := os.Mkdir(pathname, dirPerms); err != nil && !os.IsExist(err) { 440 return err 441 } 442 return imdb.updateDirectoryMetadata(directory) 443 } 444 445 func (imdb *ImageDataBase) registerAddNotifier() <-chan string { 446 channel := make(chan string, 1) 447 imdb.Lock() 448 defer imdb.Unlock() 449 imdb.addNotifiers[channel] = channel 450 return channel 451 } 452 453 func (imdb *ImageDataBase) registerDeleteNotifier() <-chan string { 454 channel := make(chan string, 1) 455 imdb.Lock() 456 defer imdb.Unlock() 457 imdb.deleteNotifiers[channel] = channel 458 return channel 459 } 460 461 func (imdb *ImageDataBase) registerMakeDirectoryNotifier() <-chan image.Directory { 462 channel := make(chan image.Directory, 1) 463 imdb.Lock() 464 defer imdb.Unlock() 465 imdb.mkdirNotifiers[channel] = channel 466 return channel 467 } 468 469 func (imdb *ImageDataBase) unregisterAddNotifier(channel <-chan string) { 470 imdb.Lock() 471 defer imdb.Unlock() 472 delete(imdb.addNotifiers, channel) 473 } 474 475 func (imdb *ImageDataBase) unregisterDeleteNotifier(channel <-chan string) { 476 imdb.Lock() 477 defer imdb.Unlock() 478 delete(imdb.deleteNotifiers, channel) 479 } 480 481 func (imdb *ImageDataBase) unregisterMakeDirectoryNotifier( 482 channel <-chan image.Directory) { 483 imdb.Lock() 484 defer imdb.Unlock() 485 delete(imdb.mkdirNotifiers, channel) 486 } 487 488 // This must be called with the lock held. 489 func (imdb *ImageDataBase) writeNewExpiration(name string, 490 oldImage *image.Image, expiresAt time.Time) error { 491 img := *oldImage 492 img.ExpiresAt = expiresAt 493 filename := filepath.Join(imdb.BaseDirectory, name) 494 tmpFilename := filename + "~" 495 file, err := os.OpenFile(tmpFilename, os.O_CREATE|os.O_RDWR|os.O_EXCL, 496 filePerms) 497 if err != nil { 498 return err 499 } 500 defer file.Close() 501 defer os.Remove(tmpFilename) 502 w := bufio.NewWriter(file) 503 defer w.Flush() 504 writer := fsutil.NewChecksumWriter(w) 505 encoder := gob.NewEncoder(writer) 506 if err := encoder.Encode(img); err != nil { 507 return err 508 } 509 if err := writer.WriteChecksum(); err != nil { 510 return err 511 } 512 if err := w.Flush(); err != nil { 513 return err 514 } 515 fsutil.FsyncFile(file) 516 return os.Rename(tmpFilename, filename) 517 } 518 519 func (n notifiers) sendPlain(name string, operation string, 520 logger log.Logger) { 521 if len(n) < 1 { 522 return 523 } else { 524 plural := "s" 525 if len(n) < 2 { 526 plural = "" 527 } 528 logger.Printf("Sending %s notification to: %d listener%s\n", 529 operation, len(n), plural) 530 } 531 for _, sendChannel := range n { 532 go func(channel chan<- string) { 533 channel <- name 534 }(sendChannel) 535 } 536 } 537 538 func (n makeDirectoryNotifiers) sendMakeDirectory(dir image.Directory, 539 logger log.Logger) { 540 if len(n) < 1 { 541 return 542 } else { 543 plural := "s" 544 if len(n) < 2 { 545 plural = "" 546 } 547 logger.Printf("Sending mkdir notification to: %d listener%s\n", 548 len(n), plural) 549 } 550 for _, sendChannel := range n { 551 go func(channel chan<- image.Directory) { 552 channel <- dir 553 }(sendChannel) 554 } 555 }