github.com/apptainer/singularity@v3.1.1+incompatible/pkg/client/library/push.go (about) 1 // Copyright (c) 2018, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 package client 7 8 import ( 9 "bufio" 10 "fmt" 11 "net/http" 12 "os" 13 "time" 14 15 "github.com/sylabs/singularity/internal/pkg/sylog" 16 "github.com/sylabs/singularity/pkg/util/user-agent" 17 "gopkg.in/cheggaaa/pb.v1" 18 ) 19 20 // Timeout in seconds for the main upload (not api calls) 21 const pushTimeout = 1800 22 23 // UploadImage will push a specified image up to the Container Library, 24 func UploadImage(filePath string, libraryRef string, libraryURL string, authToken string, description string) error { 25 26 if !IsLibraryPushRef(libraryRef) { 27 return fmt.Errorf("Not a valid library reference: %s", libraryRef) 28 } 29 30 imageHash, err := ImageHash(filePath) 31 if err != nil { 32 return err 33 } 34 sylog.Debugf("Image hash computed as %s\n", imageHash) 35 36 entityName, collectionName, containerName, tags := parseLibraryRef(libraryRef) 37 38 // Find or create entity 39 entity, found, err := getEntity(libraryURL, authToken, entityName) 40 if err != nil { 41 return err 42 } 43 if !found { 44 sylog.Verbosef("Entity %s does not exist in library - creating it.\n", entityName) 45 entity, err = createEntity(libraryURL, authToken, entityName) 46 if err != nil { 47 return err 48 } 49 } 50 51 // Find or create collection 52 collection, found, err := getCollection(libraryURL, authToken, entityName+"/"+collectionName) 53 if err != nil { 54 return err 55 } 56 if !found { 57 sylog.Verbosef("Collection %s does not exist in library - creating it.\n", collectionName) 58 collection, err = createCollection(libraryURL, authToken, collectionName, entity.GetID().Hex()) 59 if err != nil { 60 return err 61 } 62 } 63 64 // Find or create container 65 container, found, err := getContainer(libraryURL, authToken, entityName+"/"+collectionName+"/"+containerName) 66 if err != nil { 67 return err 68 } 69 if !found { 70 sylog.Verbosef("Container %s does not exist in library - creating it.\n", containerName) 71 container, err = createContainer(libraryURL, authToken, containerName, collection.GetID().Hex()) 72 if err != nil { 73 return err 74 } 75 } 76 77 // Find or create image 78 image, found, err := getImage(libraryURL, authToken, entityName+"/"+collectionName+"/"+containerName+":"+imageHash) 79 if err != nil { 80 return err 81 } 82 if !found { 83 sylog.Verbosef("Image %s does not exist in library - creating it.\n", imageHash) 84 image, err = createImage(libraryURL, authToken, imageHash, container.GetID().Hex(), description) 85 if err != nil { 86 return err 87 } 88 } 89 90 if !image.Uploaded { 91 sylog.Infof("Now uploading %s to the library\n", filePath) 92 err = postFile(libraryURL, authToken, filePath, image.GetID().Hex()) 93 if err != nil { 94 return err 95 } 96 sylog.Debugf("Upload completed OK\n") 97 } else { 98 sylog.Infof("Image is already present in the library - not uploading.\n") 99 } 100 101 sylog.Debugf("Setting tags against uploaded image\n") 102 err = setTags(libraryURL, authToken, container.GetID().Hex(), image.GetID().Hex(), tags) 103 if err != nil { 104 return err 105 } 106 107 return nil 108 } 109 110 func postFile(baseURL string, authToken string, filePath string, imageID string) error { 111 112 f, err := os.Open(filePath) 113 if err != nil { 114 return fmt.Errorf("Could not open the image file to upload: %v", err) 115 } 116 defer f.Close() 117 fi, err := f.Stat() 118 if err != nil { 119 return fmt.Errorf("Could not find size of the image file to upload: %v", err) 120 } 121 fileSize := fi.Size() 122 123 postURL := baseURL + "/v1/imagefile/" + imageID 124 sylog.Debugf("postFile calling %s\n", postURL) 125 126 b := bufio.NewReader(f) 127 128 // create and start bar 129 bar := pb.New(int(fileSize)).SetUnits(pb.U_BYTES) 130 if sylog.GetLevel() < 0 { 131 bar.NotPrint = true 132 } 133 bar.ShowTimeLeft = true 134 bar.ShowSpeed = true 135 bar.Start() 136 // create proxy reader 137 bodyProgress := bar.NewProxyReader(b) 138 // Make an upload request 139 req, _ := http.NewRequest("POST", postURL, bodyProgress) 140 req.Header.Set("Content-Type", "application/octet-stream") 141 if authToken != "" { 142 req.Header.Set("Authorization", "Bearer "+authToken) 143 } 144 req.Header.Set("User-Agent", useragent.Value()) 145 // Content length is required by the API 146 req.ContentLength = fileSize 147 client := &http.Client{ 148 Timeout: pushTimeout * time.Second, 149 } 150 res, err := client.Do(req) 151 152 bar.Finish() 153 154 if err != nil { 155 return fmt.Errorf("Error uploading file to server: %s", err.Error()) 156 } 157 if res.StatusCode != http.StatusOK { 158 jRes, err := ParseErrorBody(res.Body) 159 if err != nil { 160 jRes = ParseErrorResponse(res) 161 } 162 return fmt.Errorf("Sending file did not succeed: %d %s\n\t%v", 163 jRes.Error.Code, jRes.Error.Status, jRes.Error.Message) 164 } 165 166 return nil 167 168 }