github.com/apptainer/singularity@v3.1.1+incompatible/pkg/client/library/util.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  	"crypto/sha256"
    11  	"encoding/hex"
    12  	"encoding/json"
    13  	"fmt"
    14  	"io"
    15  	"net/http"
    16  	"os"
    17  	"regexp"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/globalsign/mgo/bson"
    22  	"github.com/sylabs/singularity/internal/pkg/sylog"
    23  )
    24  
    25  // IsLibraryPullRef returns true if the provided string is a valid library
    26  // reference for a pull operation.
    27  func IsLibraryPullRef(libraryRef string) bool {
    28  	match, _ := regexp.MatchString("^(library://)?([a-z0-9]+(?:[._-][a-z0-9]+)*/){0,2}([a-z0-9]+(?:[._-][a-z0-9]+)*)(:[a-z0-9]+(?:[._-][a-z0-9]+)*)?$", libraryRef)
    29  	return match
    30  }
    31  
    32  // IsLibraryPushRef returns true if the provided string is a valid library
    33  // reference for a push operation.
    34  func IsLibraryPushRef(libraryRef string) bool {
    35  	// For push we allow specifying multiple tags, delimited with ,
    36  	match, _ := regexp.MatchString("^(library://)?([a-z0-9]+(?:[._-][a-z0-9]+)*/){2}([a-z0-9]+(?:[._-][a-z0-9]+)*)(:[a-z0-9]+(?:[,._-][a-z0-9]+)*)?$", libraryRef)
    37  	return match
    38  }
    39  
    40  // IsRefPart returns true if the provided string is valid as a component of a
    41  // library URI (i.e. a valid entity, collection etc. name)
    42  func IsRefPart(refPart string) bool {
    43  	match, err := regexp.MatchString("^[a-z0-9]+(?:[._-][a-z0-9]+)*$", refPart)
    44  	if err != nil {
    45  		sylog.Debugf("Error in regex matching: %v", err)
    46  		return false
    47  	}
    48  	return match
    49  }
    50  
    51  // IsImageHash returns true if the provided string is valid as a unique hash
    52  // for an image
    53  func IsImageHash(refPart string) bool {
    54  	// Legacy images will be sent with hash sha256.[a-f0-9]{64}
    55  	// SIF images will be sent with hash sif.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
    56  	//  which is the unique SIF UUID
    57  	match, err := regexp.MatchString("^((sha256\\.[a-f0-9]{64})|(sif\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$", refPart)
    58  	if err != nil {
    59  		sylog.Debugf("Error in regex matching: %v", err)
    60  		return false
    61  	}
    62  	return match
    63  }
    64  
    65  func parseLibraryRef(libraryRef string) (entity string, collection string, container string, tags []string) {
    66  
    67  	libraryRef = strings.TrimPrefix(libraryRef, "library://")
    68  
    69  	refParts := strings.Split(libraryRef, "/")
    70  
    71  	switch len(refParts) {
    72  	case 3:
    73  		entity = refParts[0]
    74  		collection = refParts[1]
    75  		container = refParts[2]
    76  	case 2:
    77  		entity = ""
    78  		collection = refParts[0]
    79  		container = refParts[1]
    80  	case 1:
    81  		entity = ""
    82  		collection = ""
    83  		container = refParts[0]
    84  	}
    85  
    86  	// Default tag is latest
    87  	tags = []string{"latest"}
    88  
    89  	if strings.Contains(container, ":") {
    90  		imageParts := strings.Split(container, ":")
    91  		container = imageParts[0]
    92  		tags = []string{imageParts[1]}
    93  		if strings.Contains(tags[0], ",") {
    94  			tags = strings.Split(tags[0], ",")
    95  		}
    96  	}
    97  
    98  	return
    99  }
   100  
   101  // ParseErrorBody - Parse an API format error out of the body
   102  func ParseErrorBody(r io.Reader) (jRes JSONResponse, err error) {
   103  	err = json.NewDecoder(r).Decode(&jRes)
   104  	if err != nil {
   105  		return jRes, fmt.Errorf("The server returned a response that could not be decoded: %v", err)
   106  	}
   107  	return jRes, nil
   108  }
   109  
   110  // ParseErrorResponse - Create a JSONResponse out of a raw HTTP response
   111  func ParseErrorResponse(res *http.Response) (jRes JSONResponse) {
   112  	buf := new(bytes.Buffer)
   113  	buf.ReadFrom(res.Body)
   114  	s := buf.String()
   115  	jRes.Error.Code = res.StatusCode
   116  	jRes.Error.Status = http.StatusText(res.StatusCode)
   117  	jRes.Error.Message = s
   118  	return jRes
   119  }
   120  
   121  // IDInSlice returns true if ID is present in the slice
   122  func IDInSlice(a bson.ObjectId, list []bson.ObjectId) bool {
   123  	for _, b := range list {
   124  		if b == a {
   125  			return true
   126  		}
   127  	}
   128  	return false
   129  }
   130  
   131  // SliceWithoutID returns slice with specified ID removed
   132  func SliceWithoutID(list []bson.ObjectId, a bson.ObjectId) []bson.ObjectId {
   133  
   134  	var newList []bson.ObjectId
   135  
   136  	for _, b := range list {
   137  		if b != a {
   138  			newList = append(newList, b)
   139  		}
   140  	}
   141  	return newList
   142  }
   143  
   144  // StringInSlice returns true if string is present in the slice
   145  func StringInSlice(a string, list []string) bool {
   146  	for _, b := range list {
   147  		if b == a {
   148  			return true
   149  		}
   150  	}
   151  	return false
   152  }
   153  
   154  // PrettyPrint - Debug helper, print nice json for any interface
   155  func PrettyPrint(v interface{}) {
   156  	b, _ := json.MarshalIndent(v, "", "  ")
   157  	println(string(b))
   158  }
   159  
   160  // BsonUTCNow returns a time.Time in UTC, with the precision supported by BSON
   161  func BsonUTCNow() time.Time {
   162  	return bson.Now().UTC()
   163  }
   164  
   165  // ImageHash returns the appropriate hash for a provided image file
   166  //   e.g. sif.<uuid> or sha256.<sha256>
   167  func ImageHash(filePath string) (result string, err error) {
   168  	// Currently using sha256 always
   169  	// TODO - use sif uuid for sif files!
   170  	return sha256sum(filePath)
   171  }
   172  
   173  // SHA256Sum computes the sha256sum of a file
   174  func sha256sum(filePath string) (result string, err error) {
   175  	file, err := os.Open(filePath)
   176  	if err != nil {
   177  		return "", err
   178  	}
   179  	defer file.Close()
   180  
   181  	hash := sha256.New()
   182  	_, err = io.Copy(hash, file)
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  
   187  	return "sha256." + hex.EncodeToString(hash.Sum(nil)), nil
   188  }