File: util.go

package info (click to toggle)
singularity-container 4.0.3%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 21,672 kB
  • sloc: asm: 3,857; sh: 2,125; ansic: 1,677; awk: 414; makefile: 110; python: 99
file content (176 lines) | stat: -rw-r--r-- 4,869 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.

package client

import (
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"io"
	"os"
	"regexp"
	"strings"
)

// IsLibraryPullRef returns true if the provided string is a valid library
// reference for a pull operation.
func IsLibraryPullRef(libraryRef string) bool {
	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)
	return match
}

// IsLibraryPushRef returns true if the provided string is a valid library
// reference for a push operation.
func IsLibraryPushRef(libraryRef string) bool {
	// For push we allow specifying multiple tags, delimited with ,
	match, _ := regexp.MatchString("^(library://)?([a-z0-9]+(?:[._-][a-z0-9]+)*/){2}([a-z0-9]+(?:[._-][a-z0-9]+)*)(:[a-z0-9]+(?:[,._-][a-z0-9]+)*)?$", libraryRef)
	return match
}

// IsRefPart returns true if the provided string is valid as a component of a
// library URI (i.e. a valid entity, collection etc. name)
func IsRefPart(refPart string) bool {
	match, err := regexp.MatchString("^[a-z0-9]+(?:[._-][a-z0-9]+)*$", refPart)
	if err != nil {
		return false
	}
	return match
}

// IsImageHash returns true if the provided string is valid as a unique hash
// for an image
func IsImageHash(refPart string) bool {
	// Legacy images will be sent with hash sha256.[a-f0-9]{64}
	// 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}
	//  which is the unique SIF UUID
	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)
	if err != nil {
		return false
	}
	return match
}

func ParseLibraryPath(libraryRef string) (entity string, collection string, container string, tags []string) {
	libraryRef = strings.TrimPrefix(libraryRef, "library://")

	refParts := strings.Split(libraryRef, "/")

	switch len(refParts) {
	case 3:
		entity = refParts[0]
		collection = refParts[1]
		container = refParts[2]
	case 2:
		entity = ""
		collection = refParts[0]
		container = refParts[1]
	case 1:
		entity = ""
		collection = ""
		container = refParts[0]
	default:
		// malformed libraryRef; must conform to "library://entity/collection/container[:tag[,tag]...]"
		entity = ""
		collection = ""
		container = ""
		tags = []string{}
		return
	}

	if strings.Contains(container, ":") {
		imageParts := strings.Split(container, ":")
		container = imageParts[0]
		tags = []string{imageParts[1]}
		if strings.Contains(tags[0], ",") {
			tags = strings.Split(tags[0], ",")
		}
	}

	return entity, collection, container, tags
}

// IDInSlice returns true if ID is present in the slice
func IDInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

// SliceWithoutID returns slice with specified ID removed
func SliceWithoutID(list []string, a string) []string {
	var newList []string

	for _, b := range list {
		if b != a {
			newList = append(newList, b)
		}
	}
	return newList
}

// StringInSlice returns true if string is present in the slice
func StringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

// PrettyPrint - Debug helper, print nice json for any interface
func PrettyPrint(v interface{}) {
	if b, err := json.MarshalIndent(v, "", "  "); err == nil {
		println(string(b))
	}
}

// ImageHash returns the appropriate hash for a provided image file
//
//	e.g. sif.<uuid> or sha256.<sha256>
func ImageHash(filePath string) (result string, err error) {
	// Currently using sha256 always
	// TODO - use sif uuid for sif files!
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer file.Close()

	result, _, err = sha256sum(file)

	return "sha256." + result, err
}

// sha256sum computes the sha256sum of the specified reader; caller is
// responsible for resetting file pointer. 'nBytes' indicates number of
// bytes read from reader
func sha256sum(r io.Reader) (result string, nBytes int64, err error) {
	hash := sha256.New()
	nBytes, err = io.Copy(hash, r)
	if err != nil {
		return "", 0, err
	}

	return hex.EncodeToString(hash.Sum(nil)), nBytes, nil
}

// md5sum computes the MD5 checksum of the specified reader; caller is
// responsible for resetting file pointer. nBytes' indicates number of
// bytes read from reader
func md5sum(r io.Reader) (result string, nBytes int64, err error) {
	hash := md5.New()
	nBytes, err = io.Copy(hash, r)
	if err != nil {
		return "", 0, err
	}

	return hex.EncodeToString(hash.Sum(nil)), nBytes, nil
}