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  }