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 }