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  }