github.com/apptainer/singularity@v3.1.1+incompatible/pkg/client/shub/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  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"net/url"
    15  	"time"
    16  
    17  	"github.com/sylabs/singularity/internal/pkg/sylog"
    18  	"github.com/sylabs/singularity/pkg/util/user-agent"
    19  )
    20  
    21  const (
    22  	defaultRegistry string = `https://singularity-hub.org`
    23  	shubAPIRoute    string = "/api/container/"
    24  	// URINotSupported if we are using a non default registry error out for now
    25  	URINotSupported string = "Only the default Singularity Hub registry is suported for now"
    26  )
    27  
    28  // ShubURI stores the various components of a singularityhub URI
    29  type ShubURI struct {
    30  	registry  string
    31  	user      string
    32  	container string
    33  	tag       string
    34  	digest    string
    35  }
    36  
    37  func (s *ShubURI) String() string {
    38  	return s.registry + s.user + "/" + s.container + s.tag + s.digest
    39  }
    40  
    41  // ShubAPIResponse holds the information returned from the Shub API
    42  type ShubAPIResponse struct {
    43  	Image   string `json:"image"`
    44  	Name    string `json:"name"`
    45  	Tag     string `json:"tag"`
    46  	Version string `json:"version"`
    47  }
    48  
    49  // getManifest will return the image manifest for a container uri
    50  // from Singularity Hub.
    51  func getManifest(uri ShubURI, noHTTPS bool) (manifest ShubAPIResponse, err error) {
    52  
    53  	// Create a new http Hub client
    54  	httpc := http.Client{
    55  		Timeout: 30 * time.Second,
    56  	}
    57  
    58  	if uri.registry != defaultRegistry+shubAPIRoute {
    59  		uri.registry = "https://" + uri.registry
    60  	}
    61  
    62  	// Create the request, add headers context
    63  	url, err := url.Parse(uri.registry + uri.user + "/" + uri.container + uri.tag + uri.digest)
    64  	if err != nil {
    65  		return ShubAPIResponse{}, err
    66  	}
    67  
    68  	if noHTTPS {
    69  		url.Scheme = "http"
    70  	}
    71  
    72  	req, err := http.NewRequest(http.MethodGet, url.String(), nil)
    73  	if err != nil {
    74  		return ShubAPIResponse{}, err
    75  	}
    76  	req.Header.Set("User-Agent", useragent.Value())
    77  
    78  	sylog.Debugf("shub request: %s", req.URL.String())
    79  
    80  	// Do the request, if status isn't success, return error
    81  	res, err := httpc.Do(req)
    82  	if res == nil {
    83  		return ShubAPIResponse{}, fmt.Errorf("No response received from singularity hub")
    84  	}
    85  	if res.StatusCode == http.StatusNotFound {
    86  		return ShubAPIResponse{}, fmt.Errorf("The requested manifest was not found in singularity hub")
    87  	}
    88  	sylog.Debugf("%s response received, beginning manifest download\n", res.Status)
    89  
    90  	if err != nil {
    91  		return ShubAPIResponse{}, err
    92  	}
    93  	defer res.Body.Close()
    94  
    95  	if res.StatusCode != http.StatusOK {
    96  		err = errors.New(res.Status)
    97  		return ShubAPIResponse{}, err
    98  	}
    99  
   100  	body, err := ioutil.ReadAll(res.Body)
   101  	if err != nil {
   102  		return ShubAPIResponse{}, err
   103  	}
   104  
   105  	err = json.Unmarshal(body, &manifest)
   106  	sylog.Debugf("manifest image name: %v\n", manifest.Name)
   107  	if err != nil {
   108  		return ShubAPIResponse{}, err
   109  	}
   110  
   111  	return
   112  }