github.com/cvmfs/docker-graphdriver@v0.0.0-20181206110523-155ec6df0521/repository-manager/lib/cvmfs.go (about)

     1  package lib
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	copy "github.com/otiai10/copy"
    13  	log "github.com/sirupsen/logrus"
    14  
    15  	da "github.com/cvmfs/docker-graphdriver/repository-manager/docker-api"
    16  )
    17  
    18  var dirPermision = os.FileMode(0744)
    19  var filePermision = os.FileMode(0644)
    20  
    21  // ingest into the repository, inside the subpath, the target (directory or file) object
    22  // CVMFSRepo: just the name of the repository (ex: unpacked.cern.ch)
    23  // path: the path inside the repository, without the prefix (ex: .foo/bar/baz), where to put the ingested target
    24  // target: the path of the target in the normal FS, the thing to ingest
    25  // if no error is returned, we remove the target from the FS
    26  func IngestIntoCVMFS(CVMFSRepo string, path string, target string) (err error) {
    27  	defer func() {
    28  		if err == nil {
    29  			Log().WithFields(log.Fields{"target": target, "action": "ingesting"}).Info("Deleting temporary directory")
    30  			os.RemoveAll(target)
    31  		}
    32  	}()
    33  	Log().WithFields(log.Fields{"target": target, "action": "ingesting"}).Info("Start ingesting")
    34  
    35  	path = filepath.Join("/", "cvmfs", CVMFSRepo, path)
    36  
    37  	Log().WithFields(log.Fields{"target": target, "action": "ingesting"}).Info("Start transaction")
    38  	err = ExecCommand("cvmfs_server", "transaction", CVMFSRepo).Start()
    39  	if err != nil {
    40  		LogE(err).WithFields(log.Fields{"repo": CVMFSRepo}).Error("Error in opening the transaction")
    41  		ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
    42  		return err
    43  	}
    44  
    45  	Log().WithFields(log.Fields{"target": target, "path": path, "action": "ingesting"}).Info("Copying target into path")
    46  
    47  	targetStat, err := os.Stat(target)
    48  	if err != nil {
    49  		LogE(err).WithFields(log.Fields{"target": target}).Error("Impossible to obtain information about the target")
    50  		return err
    51  	}
    52  
    53  	if targetStat.Mode().IsDir() {
    54  		os.RemoveAll(path)
    55  		err = os.MkdirAll(path, dirPermision)
    56  		if err != nil {
    57  			LogE(err).WithFields(log.Fields{"repo": CVMFSRepo}).Warning("Error in creating the directory where to copy the singularity")
    58  		}
    59  		err = copy.Copy(target, path)
    60  
    61  	} else if targetStat.Mode().IsRegular() {
    62  		err = func() error {
    63  			os.MkdirAll(filepath.Dir(path), dirPermision)
    64  			os.Remove(path)
    65  			from, err := os.Open(target)
    66  			defer from.Close()
    67  			if err != nil {
    68  				return err
    69  			}
    70  			to, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, filePermision)
    71  			defer to.Close()
    72  			if err != nil {
    73  				return err
    74  			}
    75  			_, err = io.Copy(to, from)
    76  			return err
    77  		}()
    78  	} else {
    79  		err = fmt.Errorf("Trying to ingest neither a file nor a directory")
    80  	}
    81  
    82  	if err != nil {
    83  		LogE(err).WithFields(log.Fields{"repo": CVMFSRepo, "target": target}).Error("Error in moving the target inside the CVMFS repo")
    84  		ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
    85  		return err
    86  	}
    87  
    88  	Log().WithFields(log.Fields{"target": target, "action": "ingesting"}).Info("Publishing")
    89  	err = ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
    90  	if err != nil {
    91  		LogE(err).WithFields(log.Fields{"repo": CVMFSRepo}).Error("Error in publishing the repository")
    92  		ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
    93  		return err
    94  	}
    95  	err = nil
    96  	return err
    97  }
    98  
    99  // create a symbolic link inside the repository called `newLinkName`, the symlink will point to `toLinkPath`
   100  // newLinkName: comes without the /cvmfs/$REPO/ prefix
   101  // toLinkPath: comes without the /cvmfs/$REPO/ prefix
   102  func CreateSymlinkIntoCVMFS(CVMFSRepo, newLinkName, toLinkPath string) (err error) {
   103  	// add the necessary prefix
   104  	newLinkName = filepath.Join("/", "cvmfs", CVMFSRepo, newLinkName)
   105  	toLinkPath = filepath.Join("/", "cvmfs", CVMFSRepo, toLinkPath)
   106  
   107  	llog := func(l *log.Entry) *log.Entry {
   108  		return l.WithFields(log.Fields{"action": "save backlink",
   109  			"repo":           CVMFSRepo,
   110  			"link name":      newLinkName,
   111  			"target to link": toLinkPath})
   112  	}
   113  
   114  	// check if the file we want to link actually exists
   115  	if _, err := os.Stat(toLinkPath); os.IsNotExist(err) {
   116  		return err
   117  	}
   118  
   119  	err = ExecCommand("cvmfs_server", "transaction", CVMFSRepo).Start()
   120  	if err != nil {
   121  		llog(LogE(err)).Error("Error in opening the transaction")
   122  		ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
   123  		return err
   124  	}
   125  
   126  	linkDir := filepath.Dir(newLinkName)
   127  	err = os.MkdirAll(linkDir, dirPermision)
   128  	if err != nil {
   129  		llog(LogE(err)).WithFields(log.Fields{
   130  			"directory": linkDir}).Error(
   131  			"Error in creating the directory where to store the symlink")
   132  	}
   133  
   134  	// the symlink exists already, we delete it and replace it
   135  	if lstat, err := os.Lstat(newLinkName); !os.IsNotExist(err) {
   136  		if lstat.Mode()&os.ModeSymlink != 0 {
   137  			// the file exists and it is a symlink, we overwrite it
   138  			err = os.Remove(newLinkName)
   139  			if err != nil {
   140  				err = fmt.Errorf("Error in removing existsing symlink: %s", err)
   141  				llog(LogE(err)).Error("Error in removing previous symlink")
   142  				return err
   143  			}
   144  		} else {
   145  			// the file exists but is not a symlink
   146  			err = fmt.Errorf(
   147  				"Error, trying to overwrite with a symlink something that is not a symlink")
   148  			llog(LogE(err)).Error("Error in creating a symlink")
   149  			return err
   150  		}
   151  	}
   152  
   153  	err = os.Symlink(toLinkPath, newLinkName)
   154  	if err != nil {
   155  		llog(LogE(err)).Error(
   156  			"Error in creating the symlink")
   157  		ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
   158  		return err
   159  	}
   160  
   161  	err = ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
   162  	if err != nil {
   163  		llog(LogE(err)).WithFields(log.Fields{"repo": CVMFSRepo}).Error(
   164  			"Error in publishing the repository")
   165  		ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
   166  		return err
   167  	}
   168  	return nil
   169  }
   170  
   171  type Backlink struct {
   172  	Origin []string `json:"origin"`
   173  }
   174  
   175  func getBacklinkPath(CVMFSRepo, layerDigest string) string {
   176  	return filepath.Join(LayerMetadataPath(CVMFSRepo, layerDigest), "origin.json")
   177  }
   178  
   179  func getBacklinkFromLayer(CVMFSRepo, layerDigest string) (backlink Backlink, err error) {
   180  	backlinkPath := getBacklinkPath(CVMFSRepo, layerDigest)
   181  	llog := func(l *log.Entry) *log.Entry {
   182  		return l.WithFields(log.Fields{"action": "get backlink from layer",
   183  			"repo":         CVMFSRepo,
   184  			"layer":        layerDigest,
   185  			"backlinkPath": backlinkPath})
   186  	}
   187  
   188  	if _, err := os.Stat(backlinkPath); os.IsNotExist(err) {
   189  		return Backlink{Origin: []string{}}, nil
   190  	}
   191  
   192  	backlinkFile, err := os.Open(backlinkPath)
   193  	if err != nil {
   194  		llog(LogE(err)).Error(
   195  			"Error in opening the file for writing the backlinks, skipping...")
   196  		return
   197  	}
   198  
   199  	byteBackLink, err := ioutil.ReadAll(backlinkFile)
   200  	if err != nil {
   201  		llog(LogE(err)).Error(
   202  			"Error in reading the bytes from the origin file, skipping...")
   203  		return
   204  	}
   205  
   206  	err = backlinkFile.Close()
   207  	if err != nil {
   208  		llog(LogE(err)).Error(
   209  			"Error in closing the file after reading, moving on...")
   210  		return
   211  	}
   212  
   213  	err = json.Unmarshal(byteBackLink, &backlink)
   214  	if err != nil {
   215  		llog(LogE(err)).Error(
   216  			"Error in unmarshaling the files, skipping...")
   217  		return
   218  	}
   219  	return
   220  }
   221  
   222  func SaveLayersBacklink(CVMFSRepo string, img Image, layerDigest []string) error {
   223  	llog := func(l *log.Entry) *log.Entry {
   224  		return l.WithFields(log.Fields{"action": "save backlink",
   225  			"repo":  CVMFSRepo,
   226  			"image": img.GetSimpleName()})
   227  	}
   228  
   229  	llog(Log()).Info("Start saving backlinks")
   230  
   231  	backlinks := make(map[string][]byte)
   232  
   233  	for _, layerDigest := range layerDigest {
   234  		imgManifest, err := img.GetManifest()
   235  		if err != nil {
   236  			llog(LogE(err)).Error(
   237  				"Error in getting the manifest from the image, skipping...")
   238  			continue
   239  		}
   240  		imgDigest := imgManifest.Config.Digest
   241  
   242  		backlink, err := getBacklinkFromLayer(CVMFSRepo, layerDigest)
   243  		if err != nil {
   244  			llog(LogE(err)).WithFields(log.Fields{"layer": layerDigest}).Error(
   245  				"Error in obtaining the backlink from a layer digest, skipping...")
   246  			continue
   247  		}
   248  		backlink.Origin = append(backlink.Origin, imgDigest)
   249  
   250  		backlinkBytesMarshal, err := json.Marshal(backlink)
   251  		if err != nil {
   252  			llog(LogE(err)).WithFields(log.Fields{"layer": layerDigest}).Error(
   253  				"Error in Marshaling back the files, skipping...")
   254  			continue
   255  		}
   256  
   257  		backlinkPath := getBacklinkPath(CVMFSRepo, layerDigest)
   258  		backlinks[backlinkPath] = backlinkBytesMarshal
   259  	}
   260  
   261  	llog(Log()).Info("Start transaction")
   262  	err := ExecCommand("cvmfs_server", "transaction", CVMFSRepo).Start()
   263  	if err != nil {
   264  		llog(LogE(err)).Error("Error in opening the transaction")
   265  		return err
   266  	}
   267  
   268  	for path, fileContent := range backlinks {
   269  		// the path may not be there, check, and if it doesn't exists create it
   270  		dir := filepath.Dir(path)
   271  		if _, err := os.Stat(dir); os.IsNotExist(err) {
   272  			err = os.MkdirAll(dir, dirPermision)
   273  			if err != nil {
   274  				llog(LogE(err)).WithFields(log.Fields{"file": path}).Error(
   275  					"Error in creating the directory for the backlinks file, skipping...")
   276  				continue
   277  			}
   278  		}
   279  		err = ioutil.WriteFile(path, fileContent, filePermision)
   280  		if err != nil {
   281  			llog(LogE(err)).WithFields(log.Fields{"file": path}).Error(
   282  				"Error in writing the backlink file, skipping...")
   283  			continue
   284  		}
   285  		llog(LogE(err)).WithFields(log.Fields{"file": path}).Info(
   286  			"Wrote backlink")
   287  	}
   288  
   289  	err = ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
   290  	if err != nil {
   291  		llog(LogE(err)).Error("Error in publishing after adding the backlinks")
   292  		return err
   293  	}
   294  
   295  	return nil
   296  }
   297  
   298  func RemoveScheduleLocation(CVMFSRepo string) string {
   299  	return filepath.Join("/", "cvmfs", CVMFSRepo, ".metadata", "remove-schedule.json")
   300  }
   301  
   302  func AddManifestToRemoveScheduler(CVMFSRepo string, manifest da.Manifest) error {
   303  	schedulePath := RemoveScheduleLocation(CVMFSRepo)
   304  	llog := func(l *log.Entry) *log.Entry {
   305  		return l.WithFields(log.Fields{
   306  			"action": "add manifest to remove schedule",
   307  			"file":   schedulePath})
   308  	}
   309  	var schedule []da.Manifest
   310  
   311  	// if the file exist, load from it
   312  	if _, err := os.Stat(schedulePath); !os.IsNotExist(err) {
   313  
   314  		scheduleFileRO, err := os.OpenFile(schedulePath, os.O_RDONLY, filePermision)
   315  		if err != nil {
   316  			llog(LogE(err)).Error("Impossible to open the schedule file")
   317  			return err
   318  		}
   319  
   320  		scheduleBytes, err := ioutil.ReadAll(scheduleFileRO)
   321  		if err != nil {
   322  			llog(LogE(err)).Error("Impossible to read the schedule file")
   323  			return err
   324  		}
   325  
   326  		err = scheduleFileRO.Close()
   327  		if err != nil {
   328  			llog(LogE(err)).Error("Impossible to close the schedule file")
   329  			return err
   330  		}
   331  
   332  		err = json.Unmarshal(scheduleBytes, &schedule)
   333  		if err != nil {
   334  			llog(LogE(err)).Error("Impossible to unmarshal the schedule file")
   335  			return err
   336  		}
   337  	}
   338  
   339  	schedule = func() []da.Manifest {
   340  		for _, m := range schedule {
   341  			if m.Config.Digest == manifest.Config.Digest {
   342  				return schedule
   343  			}
   344  		}
   345  		schedule = append(schedule, manifest)
   346  		return schedule
   347  	}()
   348  
   349  	err := ExecCommand("cvmfs_server", "transaction", CVMFSRepo).Start()
   350  	if err != nil {
   351  		llog(LogE(err)).Error("Error in opening the transaction")
   352  		return err
   353  	}
   354  
   355  	if _, err = os.Stat(schedulePath); os.IsNotExist(err) {
   356  		err = os.MkdirAll(filepath.Dir(schedulePath), dirPermision)
   357  		if err != nil {
   358  			llog(LogE(err)).Error("Error in creating the directory where save the schedule")
   359  		}
   360  	}
   361  
   362  	bytes, err := json.Marshal(schedule)
   363  	if err != nil {
   364  		llog(LogE(err)).Error("Error in marshaling the new schedule")
   365  	} else {
   366  
   367  		err = ioutil.WriteFile(schedulePath, bytes, filePermision)
   368  		if err != nil {
   369  			llog(LogE(err)).Error("Error in writing the new schedule")
   370  		} else {
   371  			llog(Log()).Info("Wrote new remove schedule")
   372  		}
   373  	}
   374  
   375  	err = ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
   376  	if err != nil {
   377  		llog(LogE(err)).Error("Error in publishing after adding the backlinks")
   378  		return err
   379  	}
   380  
   381  	return nil
   382  }
   383  
   384  func RemoveSingularityImageFromManifest(CVMFSRepo string, manifest da.Manifest) error {
   385  	dir := filepath.Join("/", "cvmfs", CVMFSRepo, GetSingularityPathFromManifest(manifest))
   386  	llog := func(l *log.Entry) *log.Entry {
   387  		return l.WithFields(log.Fields{
   388  			"action": "removing singularity directory", "directory": dir})
   389  	}
   390  	err := RemoveDirectory(dir)
   391  	if err != nil {
   392  		llog(LogE(err)).Error("Error in removing singularity direcotry")
   393  		return err
   394  	}
   395  	return nil
   396  }
   397  
   398  func LayerPath(CVMFSRepo, layerDigest string) string {
   399  	return filepath.Join("/", "cvmfs", CVMFSRepo, subDirInsideRepo, layerDigest[0:2], layerDigest)
   400  }
   401  
   402  func LayerRootfsPath(CVMFSRepo, layerDigest string) string {
   403  	return filepath.Join(LayerPath(CVMFSRepo, layerDigest), "layerfs")
   404  }
   405  
   406  func LayerMetadataPath(CVMFSRepo, layerDigest string) string {
   407  	return filepath.Join(LayerPath(CVMFSRepo, layerDigest), ".metadata")
   408  }
   409  
   410  //from /cvmfs/$REPO/foo/bar -> foo/bar
   411  func TrimCVMFSRepoPrefix(path string) string {
   412  	return strings.Join(strings.Split(path, string(os.PathSeparator))[3:], string(os.PathSeparator))
   413  }
   414  
   415  func RemoveLayer(CVMFSRepo, layerDigest string) error {
   416  	dir := LayerPath(CVMFSRepo, layerDigest)
   417  	llog := func(l *log.Entry) *log.Entry {
   418  		return l.WithFields(log.Fields{
   419  			"action": "removing layer", "directory": dir, "layer": layerDigest})
   420  	}
   421  	err := RemoveDirectory(dir)
   422  	if err != nil {
   423  		llog(LogE(err)).Error("Error in deleting a layer")
   424  		return err
   425  	}
   426  	return nil
   427  }
   428  
   429  func RemoveDirectory(directory string) error {
   430  	llog := func(l *log.Entry) *log.Entry {
   431  		return l.WithFields(log.Fields{
   432  			"action": "removing directory", "directory": directory})
   433  	}
   434  	stat, err := os.Stat(directory)
   435  	if err != nil {
   436  		if os.IsNotExist(err) {
   437  			llog(LogE(err)).Warning("Directory not existing")
   438  			return nil
   439  		}
   440  		llog(LogE(err)).Error("Error in stating the directory")
   441  		return err
   442  	}
   443  	if !stat.Mode().IsDir() {
   444  		err = fmt.Errorf("Trying to remove something different from a directory")
   445  		llog(LogE(err)).Error("Error, input is not a directory")
   446  		return err
   447  	}
   448  
   449  	dirsSplitted := strings.Split(directory, string(os.PathSeparator))
   450  	if len(dirsSplitted) <= 3 || dirsSplitted[1] != "cvmfs" {
   451  		err := fmt.Errorf("Directory not in the CVMFS repo")
   452  		llog(LogE(err)).Error("Error in opening the transaction")
   453  		return err
   454  	}
   455  	CVMFSRepo := dirsSplitted[2]
   456  	err = ExecCommand("cvmfs_server", "transaction", CVMFSRepo).Start()
   457  	if err != nil {
   458  		llog(LogE(err)).Error("Error in opening the transaction")
   459  		return err
   460  	}
   461  
   462  	err = os.RemoveAll(directory)
   463  	if err != nil {
   464  		llog(LogE(err)).Error("Error in publishing after adding the backlinks")
   465  		ExecCommand("cvmfs_server", "abort", "-f", CVMFSRepo).Start()
   466  		return err
   467  	}
   468  
   469  	err = ExecCommand("cvmfs_server", "publish", CVMFSRepo).Start()
   470  	if err != nil {
   471  		llog(LogE(err)).Error("Error in publishing after adding the backlinks")
   472  		return err
   473  	}
   474  
   475  	return nil
   476  }
   477  
   478  func CreateCatalogIntoDir(CVMFSRepo, dir string) (err error) {
   479  	catalogPath := filepath.Join("/", "cvmfs", CVMFSRepo, dir, ".cvmfscatalog")
   480  	if _, err := os.Stat(catalogPath); os.IsNotExist(err) {
   481  		tmpFile, err := ioutil.TempFile("", "tempCatalog")
   482  		tmpFile.Close()
   483  		if err != nil {
   484  			return err
   485  		}
   486  		err = IngestIntoCVMFS(CVMFSRepo, TrimCVMFSRepoPrefix(catalogPath), tmpFile.Name())
   487  		if err != nil {
   488  			return err
   489  		}
   490  		return err
   491  	}
   492  	return nil
   493  }