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 }