github.com/apptainer/singularity@v3.1.1+incompatible/pkg/client/library/pull.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  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"os"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/sylabs/singularity/internal/pkg/sylog"
    17  	useragent "github.com/sylabs/singularity/pkg/util/user-agent"
    18  	pb "gopkg.in/cheggaaa/pb.v1"
    19  )
    20  
    21  // Timeout for an image pull in seconds - could be a large download...
    22  const pullTimeout = 1800
    23  
    24  // DownloadImage will retrieve an image from the Container Library,
    25  // saving it into the specified file
    26  func DownloadImage(filePath string, libraryRef string, libraryURL string, Force bool, authToken string) error {
    27  
    28  	if !IsLibraryPullRef(libraryRef) {
    29  		return fmt.Errorf("Not a valid library reference: %s", libraryRef)
    30  	}
    31  
    32  	if filePath == "" {
    33  		_, _, container, tags := parseLibraryRef(libraryRef)
    34  		filePath = fmt.Sprintf("%s_%s.sif", container, tags[0])
    35  		sylog.Infof("Download filename not provided. Downloading to: %s\n", filePath)
    36  	}
    37  
    38  	libraryRef = strings.TrimPrefix(libraryRef, "library://")
    39  
    40  	if strings.Index(libraryRef, ":") == -1 {
    41  		libraryRef += ":latest"
    42  	}
    43  
    44  	url := libraryURL + "/v1/imagefile/" + libraryRef
    45  
    46  	sylog.Debugf("Pulling from URL: %s\n", url)
    47  
    48  	if !Force {
    49  		if _, err := os.Stat(filePath); err == nil {
    50  			return fmt.Errorf("image file already exists - will not overwrite")
    51  		}
    52  	}
    53  
    54  	client := &http.Client{
    55  		Timeout: pullTimeout * time.Second,
    56  	}
    57  
    58  	req, err := http.NewRequest(http.MethodGet, url, nil)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	if authToken != "" {
    64  		req.Header.Set("Authorization", "Bearer "+authToken)
    65  	}
    66  	req.Header.Set("User-Agent", useragent.Value())
    67  
    68  	res, err := client.Do(req)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	defer res.Body.Close()
    73  
    74  	if res.StatusCode == http.StatusNotFound {
    75  		return fmt.Errorf("The requested image was not found in the library")
    76  	}
    77  
    78  	if res.StatusCode != http.StatusOK {
    79  		jRes, err := ParseErrorBody(res.Body)
    80  		if err != nil {
    81  			jRes = ParseErrorResponse(res)
    82  		}
    83  		return fmt.Errorf("Download did not succeed: %d %s\n\t%v",
    84  			jRes.Error.Code, jRes.Error.Status, jRes.Error.Message)
    85  	}
    86  
    87  	sylog.Debugf("OK response received, beginning body download\n")
    88  
    89  	// Perms are 777 *prior* to umask
    90  	out, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	defer out.Close()
    95  
    96  	sylog.Debugf("Created output file: %s\n", filePath)
    97  
    98  	bodySize := res.ContentLength
    99  	bar := pb.New(int(bodySize)).SetUnits(pb.U_BYTES)
   100  	if sylog.GetLevel() < 0 {
   101  		bar.NotPrint = true
   102  	}
   103  	bar.ShowTimeLeft = true
   104  	bar.ShowSpeed = true
   105  
   106  	// create proxy reader
   107  	bodyProgress := bar.NewProxyReader(res.Body)
   108  
   109  	bar.Start()
   110  
   111  	// Write the body to file
   112  	_, err = io.Copy(out, bodyProgress)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	bar.Finish()
   118  
   119  	sylog.Debugf("Download complete\n")
   120  
   121  	return nil
   122  
   123  }