File: ref.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 (208 lines) | stat: -rw-r--r-- 5,396 bytes parent folder | download | duplicates (2)
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// 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 (
	"net/url"
	"strings"
)

// Scheme is the required scheme for Library URIs.
const Scheme = "library"

// A Ref represents a parsed Library URI.
//
// The general form represented is:
//
//	scheme:[//host][/]path[:tags]
//
// The host contains both the hostname and port, if present. These values can be accessed using
// the Hostname and Port methods.
//
// Examples of valid URIs:
//
//	library:path:tags
//	library:/path:tags
//	library:///path:tags
//	library://host/path:tags
//	library://host:port/path:tags
//
// The tags component is a comma-separated list of one or more tags.
type Ref struct {
	Host string   // host or host:port
	Path string   // project or entity/project
	Tags []string // list of tags
}

// parseTags takes raw tags and returns a slice of tags.
func parseTags(rawTags string) (tags []string, err error) {
	if len(rawTags) == 0 {
		return nil, ErrRefTagsNotValid
	}

	return strings.Split(rawTags, ","), nil
}

// parsePath takes the URI path and parses the path and tags.
func parsePath(rawPath string) (path string, tags []string, err error) {
	if len(rawPath) == 0 {
		return "", nil, ErrRefPathNotValid
	}

	// The path is separated from the tags (if present) by a single colon.
	parts := strings.Split(rawPath, ":")
	if len(parts) > 2 {
		return "", nil, ErrRefPathNotValid
	}

	// TODO: not sure we should modify the path here...
	// Name can optionally start with a leading "/".
	path = strings.TrimPrefix(parts[0], "/")
	if len(path) == 0 {
		return "", nil, ErrRefPathNotValid
	}

	if len(parts) > 1 {
		tags, err = parseTags(parts[1])
		if err != nil {
			return "", nil, err
		}
	} else {
		tags = nil
	}
	return path, tags, nil
}

// parse parses a raw Library reference, optionally taking into account ambiguity that exists
// within Singularity Library references.
func parse(rawRef string, ambiguous bool) (*Ref, error) {
	var u *url.URL

	if ambiguous && strings.HasPrefix(rawRef, "library://") && !strings.HasPrefix(rawRef, "library:///") {
		// Parse as if there's no host component.
		uri, err := url.Parse(strings.Replace(rawRef, "library://", "library:///", 1))
		if err != nil {
			return nil, err
		}

		// If the path contains one or three parts, there was no host component. Otherwise, fall
		// through to the normal logic.
		if n := len(strings.Split(uri.Path[1:], "/")); n == 1 || n == 3 {
			u = uri
		}
	}

	if u == nil {
		var err error
		if u, err = url.Parse(rawRef); err != nil {
			return nil, err
		}
	}

	if u.Scheme != Scheme {
		return nil, ErrRefSchemeNotValid
	}
	if u.User != nil {
		return nil, ErrRefUserNotPermitted
	}
	if u.RawQuery != "" {
		return nil, ErrRefQueryNotPermitted
	}
	if u.Fragment != "" {
		return nil, ErrRefFragmentNotPermitted
	}

	rawPath := u.Path
	if u.Opaque != "" {
		rawPath = u.Opaque
	}

	path, tags, err := parsePath(rawPath)
	if err != nil {
		return nil, err
	}

	r := &Ref{
		Host: u.Host,
		Path: path,
		Tags: tags,
	}
	return r, nil
}

// Parse parses a raw Library reference.
func Parse(rawRef string) (*Ref, error) {
	return parse(rawRef, false)
}

// ParseAmbiguous behaves like Parse, but takes into account ambiguity that exists within
// Singularity Library references that begin with the prefix "library://".
//
// In particular, Singularity supports hostless Library references in the form of "library://path".
// This creates ambiguity in whether or not a host is present in the path or not. To account for
// this, ParseAmbiguous treats library references beginning with "library://" followed by one or
// three path components (ex. "library://a", "library://a/b/c") as hostless. All other references
// are treated the same as Parse.
func ParseAmbiguous(rawRef string) (*Ref, error) {
	return parse(rawRef, true)
}

// String reassembles the ref into a valid URI string. The general form of the result is one of:
//
//	scheme:path[:tags]
//	scheme://host/path[:tags]
//
// If u.Host is empty, String uses the first form; otherwise it uses the second form.
func (r *Ref) String() string {
	u := url.URL{
		Scheme: Scheme,
		Host:   r.Host,
	}

	rawPath := r.Path
	if len(r.Tags) > 0 {
		rawPath += ":" + strings.Join(r.Tags, ",")
	}

	if u.Host != "" {
		u.Path = rawPath
	} else {
		u.Opaque = rawPath
	}

	return u.String()
}

// Hostname returns r.Host, without any port number.
//
// If Host is an IPv6 literal with a port number, Hostname returns the IPv6 literal without the
// square brackets. IPv6 literals may include a zone identifier.
func (r *Ref) Hostname() string {
	colon := strings.IndexByte(r.Host, ':')
	if colon == -1 {
		return r.Host
	}
	if i := strings.IndexByte(r.Host, ']'); i != -1 {
		return strings.TrimPrefix(r.Host[:i], "[")
	}
	return r.Host[:colon]
}

// Port returns the port part of u.Host, without the leading colon. If u.Host doesn't contain a
// port, Port returns an empty string.
func (r *Ref) Port() string {
	colon := strings.IndexByte(r.Host, ':')
	if colon == -1 {
		return ""
	}
	if i := strings.Index(r.Host, "]:"); i != -1 {
		return r.Host[i+len("]:"):]
	}
	if strings.Contains(r.Host, "]") {
		return ""
	}
	return r.Host[colon+len(":"):]
}