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  }