github.com/apptainer/singularity@v3.1.1+incompatible/pkg/client/library/api.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 "bytes" 10 "encoding/json" 11 "fmt" 12 "io/ioutil" 13 "net/http" 14 "net/url" 15 "time" 16 17 "github.com/globalsign/mgo/bson" 18 "github.com/sylabs/singularity/internal/pkg/sylog" 19 "github.com/sylabs/singularity/pkg/util/user-agent" 20 ) 21 22 // HTTP timeout in seconds 23 const httpTimeout = 10 24 25 func getEntity(baseURL string, authToken string, entityRef string) (entity Entity, found bool, err error) { 26 url := (baseURL + "/v1/entities/" + entityRef) 27 entJSON, found, err := apiGet(url, authToken) 28 if err != nil { 29 return entity, false, err 30 } 31 if !found { 32 return entity, false, nil 33 } 34 var res EntityResponse 35 if err := json.Unmarshal(entJSON, &res); err != nil { 36 return entity, false, fmt.Errorf("error decoding entity: %v", err) 37 } 38 return res.Data, found, nil 39 } 40 41 func getCollection(baseURL string, authToken string, collectionRef string) (collection Collection, found bool, err error) { 42 url := baseURL + "/v1/collections/" + collectionRef 43 colJSON, found, err := apiGet(url, authToken) 44 if err != nil { 45 return collection, false, err 46 } 47 if !found { 48 return collection, false, nil 49 } 50 var res CollectionResponse 51 if err := json.Unmarshal(colJSON, &res); err != nil { 52 return collection, false, fmt.Errorf("error decoding collection: %v", err) 53 } 54 return res.Data, found, nil 55 } 56 57 func getContainer(baseURL string, authToken string, containerRef string) (container Container, found bool, err error) { 58 url := baseURL + "/v1/containers/" + containerRef 59 conJSON, found, err := apiGet(url, authToken) 60 if err != nil { 61 return container, false, err 62 } 63 if !found { 64 return container, false, nil 65 } 66 var res ContainerResponse 67 if err := json.Unmarshal(conJSON, &res); err != nil { 68 return container, false, fmt.Errorf("error decoding container: %v", err) 69 } 70 return res.Data, found, nil 71 } 72 73 func getImage(baseURL string, authToken string, imageRef string) (image Image, found bool, err error) { 74 url := baseURL + "/v1/images/" + imageRef 75 imgJSON, found, err := apiGet(url, authToken) 76 if err != nil { 77 return image, false, err 78 } 79 if !found { 80 return image, false, nil 81 } 82 var res ImageResponse 83 if err := json.Unmarshal(imgJSON, &res); err != nil { 84 return image, false, fmt.Errorf("error decoding image: %v", err) 85 } 86 return res.Data, found, nil 87 } 88 89 func createEntity(baseURL string, authToken string, name string) (entity Entity, err error) { 90 e := Entity{ 91 Name: name, 92 Description: "No description", 93 } 94 entJSON, err := apiCreate(e, baseURL+"/v1/entities", authToken) 95 if err != nil { 96 return entity, err 97 } 98 var res EntityResponse 99 if err := json.Unmarshal(entJSON, &res); err != nil { 100 return entity, fmt.Errorf("error decoding entity: %v", err) 101 } 102 return res.Data, nil 103 } 104 105 func createCollection(baseURL string, authToken string, name string, entityID string) (collection Collection, err error) { 106 c := Collection{ 107 Name: name, 108 Description: "No description", 109 Entity: bson.ObjectIdHex(entityID), 110 } 111 colJSON, err := apiCreate(c, baseURL+"/v1/collections", authToken) 112 if err != nil { 113 return collection, err 114 } 115 var res CollectionResponse 116 if err := json.Unmarshal(colJSON, &res); err != nil { 117 return collection, fmt.Errorf("error decoding collection: %v", err) 118 } 119 return res.Data, nil 120 } 121 122 func createContainer(baseURL string, authToken string, name string, collectionID string) (container Container, err error) { 123 c := Container{ 124 Name: name, 125 Description: "No description", 126 Collection: bson.ObjectIdHex(collectionID), 127 } 128 conJSON, err := apiCreate(c, baseURL+"/v1/containers", authToken) 129 if err != nil { 130 return container, err 131 } 132 var res ContainerResponse 133 if err := json.Unmarshal(conJSON, &res); err != nil { 134 return container, fmt.Errorf("error decoding container: %v", err) 135 } 136 return res.Data, nil 137 } 138 139 func createImage(baseURL string, authToken string, hash string, containerID string, description string) (image Image, err error) { 140 i := Image{ 141 Hash: hash, 142 Description: description, 143 Container: bson.ObjectIdHex(containerID), 144 } 145 imgJSON, err := apiCreate(i, baseURL+"/v1/images", authToken) 146 if err != nil { 147 return image, err 148 } 149 var res ImageResponse 150 if err := json.Unmarshal(imgJSON, &res); err != nil { 151 return image, fmt.Errorf("error decoding image: %v", err) 152 } 153 return res.Data, nil 154 } 155 156 func setTags(baseURL string, authToken string, containerID string, imageID string, tags []string) error { 157 // Get existing tags, so we know which will be replaced 158 existingTags, err := apiGetTags(baseURL+"/v1/tags/"+containerID, authToken) 159 if err != nil { 160 return err 161 } 162 163 for _, tag := range tags { 164 sylog.Infof("Setting tag %s\n", tag) 165 166 if _, ok := existingTags[tag]; ok { 167 sylog.Warningf("%s replaces an existing tag\n", tag) 168 } 169 170 imgTag := ImageTag{ 171 tag, 172 bson.ObjectIdHex(imageID), 173 } 174 err := apiSetTag(baseURL+"/v1/tags/"+containerID, authToken, imgTag) 175 if err != nil { 176 return err 177 } 178 } 179 return nil 180 } 181 182 func search(baseURL string, authToken string, value string) (results SearchResults, err error) { 183 u, err := url.Parse(baseURL + "/v1/search") 184 if err != nil { 185 return 186 } 187 q := u.Query() 188 q.Set("value", value) 189 u.RawQuery = q.Encode() 190 191 resJSON, _, err := apiGet(u.String(), authToken) 192 if err != nil { 193 return results, err 194 } 195 196 var res SearchResponse 197 if err := json.Unmarshal(resJSON, &res); err != nil { 198 return results, fmt.Errorf("error decoding reesults: %v", err) 199 } 200 201 return res.Data, nil 202 } 203 204 func apiCreate(o interface{}, url string, authToken string) (objJSON []byte, err error) { 205 sylog.Debugf("apiCreate calling %s\n", url) 206 s, err := json.Marshal(o) 207 if err != nil { 208 return []byte{}, fmt.Errorf("error encoding object to JSON:\n\t%v", err) 209 } 210 req, err := http.NewRequest("POST", url, bytes.NewBuffer(s)) 211 req.Header.Set("Content-Type", "application/json") 212 if authToken != "" { 213 req.Header.Set("Authorization", "Bearer "+authToken) 214 } 215 req.Header.Set("User-Agent", useragent.Value()) 216 217 client := &http.Client{ 218 Timeout: (httpTimeout * time.Second), 219 } 220 res, err := client.Do(req) 221 if err != nil { 222 return []byte{}, fmt.Errorf("error making request to server:\n\t%v", err) 223 } 224 if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated { 225 jRes, err := ParseErrorBody(res.Body) 226 if err != nil { 227 jRes = ParseErrorResponse(res) 228 } 229 return []byte{}, fmt.Errorf("creation did not succeed: %d %s\n\t%v", 230 jRes.Error.Code, jRes.Error.Status, jRes.Error.Message) 231 } 232 objJSON, err = ioutil.ReadAll(res.Body) 233 if err != nil { 234 return []byte{}, fmt.Errorf("error reading response from server:\n\t%v", err) 235 } 236 return objJSON, nil 237 } 238 239 func apiGet(url string, authToken string) (objJSON []byte, found bool, err error) { 240 sylog.Debugf("apiGet calling %s\n", url) 241 client := &http.Client{ 242 Timeout: (httpTimeout * time.Second), 243 } 244 req, err := http.NewRequest(http.MethodGet, url, nil) 245 if err != nil { 246 return []byte{}, false, fmt.Errorf("error creating request to server:\n\t%v", err) 247 } 248 if authToken != "" { 249 req.Header.Set("Authorization", "Bearer "+authToken) 250 } 251 req.Header.Set("User-Agent", useragent.Value()) 252 res, err := client.Do(req) 253 if err != nil { 254 return []byte{}, false, fmt.Errorf("error making request to server:\n\t%v", err) 255 } 256 defer res.Body.Close() 257 if res.StatusCode == http.StatusNotFound { 258 return []byte{}, false, nil 259 } 260 if res.StatusCode == http.StatusOK { 261 objJSON, err := ioutil.ReadAll(res.Body) 262 if err != nil { 263 return []byte{}, false, fmt.Errorf("error reading response from server:\n\t%v", err) 264 } 265 return objJSON, true, nil 266 } 267 // Not OK, not 404.... error 268 jRes, err := ParseErrorBody(res.Body) 269 if err != nil { 270 jRes = ParseErrorResponse(res) 271 } 272 return []byte{}, false, fmt.Errorf("get did not succeed: %d %s\n\t%v", 273 jRes.Error.Code, jRes.Error.Status, jRes.Error.Message) 274 } 275 276 func apiGetTags(url string, authToken string) (tags TagMap, err error) { 277 sylog.Debugf("apiGetTags calling %s\n", url) 278 client := &http.Client{ 279 Timeout: (httpTimeout * time.Second), 280 } 281 req, err := http.NewRequest(http.MethodGet, url, nil) 282 if err != nil { 283 return nil, fmt.Errorf("error creating request to server:\n\t%v", err) 284 } 285 if authToken != "" { 286 req.Header.Set("Authorization", "Bearer "+authToken) 287 } 288 req.Header.Set("User-Agent", useragent.Value()) 289 res, err := client.Do(req) 290 if err != nil { 291 return nil, fmt.Errorf("error making request to server:\n\t%v", err) 292 } 293 if res.StatusCode != http.StatusOK { 294 jRes, err := ParseErrorBody(res.Body) 295 if err != nil { 296 jRes = ParseErrorResponse(res) 297 } 298 return nil, fmt.Errorf("creation did not succeed: %d %s\n\t%v", 299 jRes.Error.Code, jRes.Error.Status, jRes.Error.Message) 300 } 301 var tagRes TagsResponse 302 err = json.NewDecoder(res.Body).Decode(&tagRes) 303 if err != nil { 304 return tags, fmt.Errorf("error decoding tags: %v", err) 305 } 306 return tagRes.Data, nil 307 308 } 309 310 func apiSetTag(url string, authToken string, t ImageTag) (err error) { 311 sylog.Debugf("apiSetTag calling %s\n", url) 312 s, err := json.Marshal(t) 313 if err != nil { 314 return fmt.Errorf("error encoding object to JSON:\n\t%v", err) 315 } 316 req, err := http.NewRequest("POST", url, bytes.NewBuffer(s)) 317 req.Header.Set("Content-Type", "application/json") 318 if authToken != "" { 319 req.Header.Set("Authorization", "Bearer "+authToken) 320 } 321 req.Header.Set("User-Agent", useragent.Value()) 322 client := &http.Client{ 323 Timeout: (httpTimeout * time.Second), 324 } 325 res, err := client.Do(req) 326 if err != nil { 327 return fmt.Errorf("error making request to server:\n\t%v", err) 328 } 329 if res.StatusCode != http.StatusOK { 330 jRes, err := ParseErrorBody(res.Body) 331 if err != nil { 332 jRes = ParseErrorResponse(res) 333 } 334 return fmt.Errorf("creation did not succeed: %d %s\n\t%v", 335 jRes.Error.Code, jRes.Error.Status, jRes.Error.Message) 336 } 337 return nil 338 } 339 340 // GetImage returns the Image object if exists, otherwise returns error 341 func GetImage(baseURL string, authToken string, imageRef string) (image Image, err error) { 342 entityName, collectionName, containerName, tags := parseLibraryRef(imageRef) 343 344 i, f, err := getImage(baseURL, authToken, entityName+"/"+collectionName+"/"+containerName+":"+tags[0]) 345 if err != nil { 346 return Image{}, err 347 } else if !f { 348 return Image{}, fmt.Errorf("the requested image was not found in the library") 349 } 350 351 return i, nil 352 }