github.com/cvmfs/docker-graphdriver@v0.0.0-20181206110523-155ec6df0521/repository-manager/lib/conversion.go (about) 1 package lib 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "encoding/base64" 8 "encoding/json" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 16 da "github.com/cvmfs/docker-graphdriver/repository-manager/docker-api" 17 18 "github.com/docker/docker/api/types" 19 "github.com/docker/docker/client" 20 log "github.com/sirupsen/logrus" 21 ) 22 23 type ConversionResult int 24 25 const ( 26 ConversionNotFound = iota 27 ConversionMatch = iota 28 ConversionNotMatch = iota 29 ) 30 31 var subDirInsideRepo = ".layers" 32 33 func ConvertWish(wish WishFriendly, convertAgain, forceDownload, convertSingularity bool) (err error) { 34 35 err = CreateCatalogIntoDir(wish.CvmfsRepo, subDirInsideRepo) 36 if err != nil { 37 LogE(err).WithFields(log.Fields{ 38 "directory": subDirInsideRepo}).Error( 39 "Impossible to create subcatalog in super-directory.") 40 } 41 err = CreateCatalogIntoDir(wish.CvmfsRepo, ".flat") 42 if err != nil { 43 LogE(err).WithFields(log.Fields{ 44 "directory": ".flat"}).Error( 45 "Impossible to create subcatalog in super-directory.") 46 } 47 48 outputImage, err := ParseImage(wish.OutputName) 49 outputImage.User = wish.UserOutput 50 if err != nil { 51 return 52 } 53 password, err := getPassword() 54 if err != nil { 55 return 56 } 57 inputImage, err := ParseImage(wish.InputName) 58 inputImage.User = wish.UserInput 59 if err != nil { 60 return 61 } 62 manifest, err := inputImage.GetManifest() 63 if err != nil { 64 return 65 } 66 67 alreadyConverted := AlreadyConverted(wish.CvmfsRepo, inputImage, manifest.Config.Digest) 68 Log().WithFields(log.Fields{"alreadyConverted": alreadyConverted}).Info( 69 "Already converted the image, skipping.") 70 71 switch alreadyConverted { 72 73 case ConversionMatch: 74 { 75 Log().Info("Already converted the image.") 76 if convertAgain == false { 77 return nil 78 } 79 80 } 81 } 82 83 layersChanell := make(chan downloadedLayer, 3) 84 manifestChanell := make(chan string, 1) 85 stopGettingLayers := make(chan bool, 1) 86 noErrorInConversion := make(chan bool, 1) 87 88 type LayerRepoLocation struct { 89 Digest string 90 Location string //location does NOT need the prefix `/cvmfs` 91 } 92 layerRepoLocationChan := make(chan LayerRepoLocation, 3) 93 layerDigestChan := make(chan string, 3) 94 go func() { 95 noErrors := true 96 var wg sync.WaitGroup 97 defer func() { 98 wg.Wait() 99 close(layerRepoLocationChan) 100 close(layerDigestChan) 101 }() 102 defer func() { 103 noErrorInConversion <- noErrors 104 stopGettingLayers <- true 105 close(stopGettingLayers) 106 }() 107 cleanup := func(location string) { 108 Log().Info("Running clean up function deleting the last layer.") 109 110 err := ExecCommand("cvmfs_server", "abort", "-f", wish.CvmfsRepo).Start() 111 if err != nil { 112 LogE(err).Warning("Error in the abort command inside the cleanup function, this warning is usually normal") 113 } 114 115 err = ExecCommand("cvmfs_server", "ingest", "--delete", location, wish.CvmfsRepo).Start() 116 if err != nil { 117 LogE(err).Error("Error in the cleanup command") 118 } 119 } 120 for layer := range layersChanell { 121 122 Log().WithFields(log.Fields{"layer": layer.Name}).Info("Start Ingesting the file into CVMFS") 123 layerDigest := strings.Split(layer.Name, ":")[1] 124 layerPath := LayerRootfsPath(wish.CvmfsRepo, layerDigest) 125 126 var pathExists bool 127 if _, err := os.Stat(layerPath); os.IsNotExist(err) { 128 pathExists = false 129 } else { 130 pathExists = true 131 } 132 133 // need to run this into a goroutine to avoid a deadlock 134 wg.Add(1) 135 go func(layerName, layerLocation, layerDigest string) { 136 layerRepoLocationChan <- LayerRepoLocation{ 137 Digest: layerName, 138 Location: layerLocation} 139 layerDigestChan <- layerDigest 140 wg.Done() 141 }(layer.Name, layerPath, layerDigest) 142 143 if pathExists == false || forceDownload { 144 145 // need to create the "super-directory", those 146 // directory starting with 2 char prefix of the 147 // digest itself, and put a .cvmfscatalog files 148 // in it, if the directory still doesn't 149 // exists. Similarly we need a .cvmfscatalog in 150 // the layerfs directory, the one that host the 151 // whole layer 152 153 for _, dir := range []string{ 154 filepath.Dir(filepath.Dir(TrimCVMFSRepoPrefix(layerPath))), 155 //TrimCVMFSRepoPrefix(layerPath)} { 156 } { 157 158 Log().WithFields(log.Fields{"catalogdirectory": dir}).Info("Working on CATALOGDIRECTORY") 159 err = CreateCatalogIntoDir(wish.CvmfsRepo, dir) 160 if err != nil { 161 LogE(err).WithFields(log.Fields{ 162 "directory": dir}).Error( 163 "Impossible to create subcatalog in super-directory.") 164 } else { 165 Log().WithFields(log.Fields{ 166 "directory": dir}).Info( 167 "Created subcatalog in directory") 168 } 169 } 170 err = ExecCommand("cvmfs_server", "ingest", "--catalog", "-t", layer.Path, "-b", TrimCVMFSRepoPrefix(layerPath), wish.CvmfsRepo).Start() 171 172 if err != nil { 173 LogE(err).WithFields(log.Fields{"layer": layer.Name}).Error("Some error in ingest the layer") 174 noErrors = false 175 cleanup(TrimCVMFSRepoPrefix(layerPath)) 176 return 177 } 178 Log().WithFields(log.Fields{"layer": layer.Name}).Info("Finish Ingesting the file") 179 } else { 180 Log().WithFields(log.Fields{"layer": layer.Name}).Info("Skipping ingestion of layer, already exists") 181 } 182 //os.Remove(layer.Path) 183 } 184 Log().Info("Finished pushing the layers into CVMFS") 185 }() 186 // we create a temp directory for all the files needed, when this function finish we can remove the temp directory cleaning up 187 tmpDir, err := ioutil.TempDir("", "conversion") 188 if err != nil { 189 LogE(err).Error("Error in creating a temporary direcotry for all the files") 190 return 191 } 192 defer os.RemoveAll(tmpDir) 193 194 // this wil start to feed the above goroutine by writing into layersChanell 195 err = inputImage.GetLayers(layersChanell, manifestChanell, stopGettingLayers, tmpDir) 196 197 var singularity Singularity 198 if convertSingularity { 199 singularity, err = inputImage.DownloadSingularityDirectory(tmpDir) 200 if err != nil { 201 LogE(err).Error("Error in dowloading the singularity image") 202 return 203 } 204 defer os.RemoveAll(singularity.TempDirectory) 205 } 206 changes, _ := inputImage.GetChanges() 207 208 var wg sync.WaitGroup 209 210 layerLocations := make(map[string]string) 211 wg.Add(1) 212 go func() { 213 for layerLocation := range layerRepoLocationChan { 214 layerLocations[layerLocation.Digest] = layerLocation.Location 215 } 216 wg.Done() 217 }() 218 219 var layerDigests []string 220 wg.Add(1) 221 go func() { 222 for layerDigest := range layerDigestChan { 223 layerDigests = append(layerDigests, layerDigest) 224 } 225 wg.Done() 226 }() 227 wg.Wait() 228 229 thin, err := da.MakeThinImage(manifest, layerLocations, inputImage.WholeName()) 230 if err != nil { 231 return 232 } 233 234 thinJson, err := json.MarshalIndent(thin, "", " ") 235 if err != nil { 236 return 237 } 238 fmt.Println(string(thinJson)) 239 var imageTar bytes.Buffer 240 tarFile := tar.NewWriter(&imageTar) 241 header := &tar.Header{Name: "thin.json", Mode: 0644, Size: int64(len(thinJson))} 242 err = tarFile.WriteHeader(header) 243 if err != nil { 244 return 245 } 246 _, err = tarFile.Write(thinJson) 247 if err != nil { 248 return 249 } 250 err = tarFile.Close() 251 if err != nil { 252 return 253 } 254 255 dockerClient, err := client.NewClientWithOpts(client.WithVersion("1.19")) 256 if err != nil { 257 return 258 } 259 260 image := types.ImageImportSource{ 261 Source: bytes.NewBuffer(imageTar.Bytes()), 262 SourceName: "-", 263 } 264 importOptions := types.ImageImportOptions{ 265 Tag: outputImage.Tag, 266 Message: "", 267 Changes: changes, 268 } 269 importResult, err := dockerClient.ImageImport( 270 context.Background(), 271 image, 272 outputImage.GetSimpleName(), 273 importOptions) 274 if err != nil { 275 LogE(err).Error("Error in image import") 276 return 277 } 278 defer importResult.Close() 279 Log().Info("Created the image in the local docker daemon") 280 281 // is necessary this mechanism to pass the authentication to the 282 // dockers even if the documentation says otherwise 283 authStruct := struct { 284 Username string 285 Password string 286 }{ 287 Username: outputImage.User, 288 Password: password, 289 } 290 authBytes, _ := json.Marshal(authStruct) 291 authCredential := base64.StdEncoding.EncodeToString(authBytes) 292 pushOptions := types.ImagePushOptions{ 293 RegistryAuth: authCredential, 294 } 295 296 res, err := dockerClient.ImagePush( 297 context.Background(), 298 outputImage.GetSimpleName(), 299 pushOptions) 300 if err != nil { 301 return 302 } 303 b, _ := ioutil.ReadAll(res) 304 fmt.Println(string(b)) 305 defer res.Close() 306 // here is possible to use the result of the above ReadAll to have 307 // informantion about the status of the upload. 308 _, err = ioutil.ReadAll(res) 309 if err != nil { 310 return 311 } 312 Log().Info("Finish pushing the image to the registry") 313 // we wait for the goroutines to finish 314 // and if there was no error we add everything to the converted table 315 noErrorInConversionValue := <-noErrorInConversion 316 317 // here we can launch the ingestion for the singularity image 318 if convertSingularity { 319 err = singularity.IngestIntoCVMFS(wish.CvmfsRepo) 320 if err != nil { 321 LogE(err).Error("Error in ingesting the singularity image into the CVMFS repository") 322 noErrorInConversionValue = false 323 } 324 } 325 326 err = SaveLayersBacklink(wish.CvmfsRepo, inputImage, layerDigests) 327 if err != nil { 328 LogE(err).Error("Error in saving the backlinks") 329 noErrorInConversionValue = false 330 } 331 332 if noErrorInConversionValue { 333 manifestPath := filepath.Join(".metadata", inputImage.GetSimpleName(), "manifest.json") 334 errIng := IngestIntoCVMFS(wish.CvmfsRepo, manifestPath, <-manifestChanell) 335 if err != nil { 336 LogE(errIng).Error("Error in storing the manifest in the repository") 337 } 338 var errRemoveSchedule error 339 if alreadyConverted == ConversionNotMatch { 340 Log().Info("Image already converted, but it does not match the manifest, adding it to the remove scheduler") 341 errRemoveSchedule = AddManifestToRemoveScheduler(wish.CvmfsRepo, manifest) 342 if errRemoveSchedule != nil { 343 Log().Warning("Error in adding the image to the remove schedule") 344 } 345 } 346 if errIng == nil && errRemoveSchedule == nil { 347 Log().Info("Conversion completed") 348 } 349 return 350 } else { 351 Log().Warn("Some error during the conversion, we are not storing it into the database") 352 return 353 } 354 } 355 356 func AlreadyConverted(CVMFSRepo string, img Image, reference string) ConversionResult { 357 path := filepath.Join("/", "cvmfs", CVMFSRepo, ".metadata", img.GetSimpleName(), "manifest.json") 358 359 fmt.Println(path) 360 manifestStat, err := os.Stat(path) 361 if os.IsNotExist(err) { 362 Log().Info("Manifest not existing") 363 return ConversionNotFound 364 } 365 if !manifestStat.Mode().IsRegular() { 366 Log().Info("Manifest not a regular file") 367 return ConversionNotFound 368 } 369 370 manifestFile, err := os.Open(path) 371 if err != nil { 372 Log().Info("Error in opening the manifest") 373 return ConversionNotFound 374 } 375 defer manifestFile.Close() 376 377 bytes, _ := ioutil.ReadAll(manifestFile) 378 379 var manifest da.Manifest 380 err = json.Unmarshal(bytes, &manifest) 381 if err != nil { 382 LogE(err).Warning("Error in unmarshaling the manifest") 383 return ConversionNotFound 384 } 385 fmt.Printf("%s == %s\n", manifest.Config.Digest, reference) 386 if manifest.Config.Digest == reference { 387 return ConversionMatch 388 } 389 return ConversionNotMatch 390 } 391 392 func getPassword() (string, error) { 393 envVar := "DOCKER2CVMFS_DOCKER_REGISTRY_PASS" 394 pass := os.Getenv(envVar) 395 if pass == "" { 396 err := fmt.Errorf( 397 "Env variable (%s) storing the password to access the docker registry is not set", envVar) 398 return "", err 399 } 400 return pass, nil 401 }