Skip to content

Commit f872caf

Browse files
authored
Merge pull request #130 from elezar/better-is-qualified-name
Move parsing and name validation to cdi/parse package
2 parents 0a82fdb + e53d516 commit f872caf

File tree

8 files changed

+413
-135
lines changed

8 files changed

+413
-135
lines changed

pkg/cdi/annotations.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"errors"
2121
"fmt"
2222
"strings"
23+
24+
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
2325
)
2426

2527
const (
@@ -101,22 +103,22 @@ func AnnotationKey(pluginName, deviceID string) (string, error) {
101103
return "", fmt.Errorf("invalid plugin+deviceID %q, too long", name)
102104
}
103105

104-
if c := rune(name[0]); !isAlphaNumeric(c) {
106+
if c := rune(name[0]); !parser.IsAlphaNumeric(c) {
105107
return "", fmt.Errorf("invalid name %q, first '%c' should be alphanumeric",
106108
name, c)
107109
}
108110
if len(name) > 2 {
109111
for _, c := range name[1 : len(name)-1] {
110112
switch {
111-
case isAlphaNumeric(c):
113+
case parser.IsAlphaNumeric(c):
112114
case c == '_' || c == '-' || c == '.':
113115
default:
114116
return "", fmt.Errorf("invalid name %q, invalid charcter '%c'",
115117
name, c)
116118
}
117119
}
118120
}
119-
if c := rune(name[len(name)-1]); !isAlphaNumeric(c) {
121+
if c := rune(name[len(name)-1]); !parser.IsAlphaNumeric(c) {
120122
return "", fmt.Errorf("invalid name %q, last '%c' should be alphanumeric",
121123
name, c)
122124
}

pkg/cdi/device.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121

2222
"github.com/container-orchestrated-devices/container-device-interface/internal/validation"
23+
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
2324
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
2425
oci "github.com/opencontainers/runtime-spec/specs-go"
2526
)
@@ -51,7 +52,7 @@ func (d *Device) GetSpec() *Spec {
5152

5253
// GetQualifiedName returns the qualified name for this device.
5354
func (d *Device) GetQualifiedName() string {
54-
return QualifiedName(d.spec.GetVendor(), d.spec.GetClass(), d.Name)
55+
return parser.QualifiedName(d.spec.GetVendor(), d.spec.GetClass(), d.Name)
5556
}
5657

5758
// ApplyEdits applies the device-speific container edits to an OCI Spec.

pkg/cdi/qualified-device.go

Lines changed: 39 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -17,189 +17,101 @@
1717
package cdi
1818

1919
import (
20-
"fmt"
21-
"strings"
20+
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
2221
)
2322

2423
// QualifiedName returns the qualified name for a device.
2524
// The syntax for a qualified device names is
26-
// "<vendor>/<class>=<name>".
25+
//
26+
// "<vendor>/<class>=<name>".
27+
//
2728
// A valid vendor name may contain the following runes:
28-
// 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
29+
//
30+
// 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
31+
//
2932
// A valid class name may contain the following runes:
30-
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_'.
33+
//
34+
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_'.
35+
//
3136
// A valid device name may containe the following runes:
32-
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'
37+
//
38+
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'
39+
//
40+
// Deprecated: use parser.QualifiedName instead
3341
func QualifiedName(vendor, class, name string) string {
34-
return vendor + "/" + class + "=" + name
42+
return parser.QualifiedName(vendor, class, name)
3543
}
3644

3745
// IsQualifiedName tests if a device name is qualified.
46+
//
47+
// Deprecated: use parser.IsQualifiedName instead
3848
func IsQualifiedName(device string) bool {
39-
_, _, _, err := ParseQualifiedName(device)
40-
return err == nil
49+
return parser.IsQualifiedName(device)
4150
}
4251

4352
// ParseQualifiedName splits a qualified name into device vendor, class,
4453
// and name. If the device fails to parse as a qualified name, or if any
4554
// of the split components fail to pass syntax validation, vendor and
4655
// class are returned as empty, together with the verbatim input as the
4756
// name and an error describing the reason for failure.
57+
//
58+
// Deprecated: use parser.ParseQualifiedName instead
4859
func ParseQualifiedName(device string) (string, string, string, error) {
49-
vendor, class, name := ParseDevice(device)
50-
51-
if vendor == "" {
52-
return "", "", device, fmt.Errorf("unqualified device %q, missing vendor", device)
53-
}
54-
if class == "" {
55-
return "", "", device, fmt.Errorf("unqualified device %q, missing class", device)
56-
}
57-
if name == "" {
58-
return "", "", device, fmt.Errorf("unqualified device %q, missing device name", device)
59-
}
60-
61-
if err := ValidateVendorName(vendor); err != nil {
62-
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
63-
}
64-
if err := ValidateClassName(class); err != nil {
65-
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
66-
}
67-
if err := ValidateDeviceName(name); err != nil {
68-
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
69-
}
70-
71-
return vendor, class, name, nil
60+
return parser.ParseQualifiedName(device)
7261
}
7362

7463
// ParseDevice tries to split a device name into vendor, class, and name.
7564
// If this fails, for instance in the case of unqualified device names,
7665
// ParseDevice returns an empty vendor and class together with name set
7766
// to the verbatim input.
67+
//
68+
// Deprecated: use parser.ParseDevice instead
7869
func ParseDevice(device string) (string, string, string) {
79-
if device == "" || device[0] == '/' {
80-
return "", "", device
81-
}
82-
83-
parts := strings.SplitN(device, "=", 2)
84-
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
85-
return "", "", device
86-
}
87-
88-
name := parts[1]
89-
vendor, class := ParseQualifier(parts[0])
90-
if vendor == "" {
91-
return "", "", device
92-
}
93-
94-
return vendor, class, name
70+
return parser.ParseDevice(device)
9571
}
9672

9773
// ParseQualifier splits a device qualifier into vendor and class.
9874
// The syntax for a device qualifier is
99-
// "<vendor>/<class>"
75+
//
76+
// "<vendor>/<class>"
77+
//
10078
// If parsing fails, an empty vendor and the class set to the
10179
// verbatim input is returned.
80+
//
81+
// Deprecated: use parser.ParseQualifier instead
10282
func ParseQualifier(kind string) (string, string) {
103-
parts := strings.SplitN(kind, "/", 2)
104-
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
105-
return "", kind
106-
}
107-
return parts[0], parts[1]
83+
return parser.ParseQualifier(kind)
10884
}
10985

11086
// ValidateVendorName checks the validity of a vendor name.
11187
// A vendor name may contain the following ASCII characters:
11288
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
11389
// - digits ('0'-'9')
11490
// - underscore, dash, and dot ('_', '-', and '.')
91+
//
92+
// Deprecated: use parser.ValidateVendorName instead
11593
func ValidateVendorName(vendor string) error {
116-
if vendor == "" {
117-
return fmt.Errorf("invalid (empty) vendor name")
118-
}
119-
if !isLetter(rune(vendor[0])) {
120-
return fmt.Errorf("invalid vendor %q, should start with letter", vendor)
121-
}
122-
for _, c := range string(vendor[1 : len(vendor)-1]) {
123-
switch {
124-
case isAlphaNumeric(c):
125-
case c == '_' || c == '-' || c == '.':
126-
default:
127-
return fmt.Errorf("invalid character '%c' in vendor name %q",
128-
c, vendor)
129-
}
130-
}
131-
if !isAlphaNumeric(rune(vendor[len(vendor)-1])) {
132-
return fmt.Errorf("invalid vendor %q, should end with a letter or digit", vendor)
133-
}
134-
135-
return nil
94+
return parser.ValidateVendorName(vendor)
13695
}
13796

13897
// ValidateClassName checks the validity of class name.
13998
// A class name may contain the following ASCII characters:
14099
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
141100
// - digits ('0'-'9')
142101
// - underscore and dash ('_', '-')
102+
//
103+
// Deprecated: use parser.ValidateClassName instead
143104
func ValidateClassName(class string) error {
144-
if class == "" {
145-
return fmt.Errorf("invalid (empty) device class")
146-
}
147-
if !isLetter(rune(class[0])) {
148-
return fmt.Errorf("invalid class %q, should start with letter", class)
149-
}
150-
for _, c := range string(class[1 : len(class)-1]) {
151-
switch {
152-
case isAlphaNumeric(c):
153-
case c == '_' || c == '-':
154-
default:
155-
return fmt.Errorf("invalid character '%c' in device class %q",
156-
c, class)
157-
}
158-
}
159-
if !isAlphaNumeric(rune(class[len(class)-1])) {
160-
return fmt.Errorf("invalid class %q, should end with a letter or digit", class)
161-
}
162-
return nil
105+
return parser.ValidateClassName(class)
163106
}
164107

165108
// ValidateDeviceName checks the validity of a device name.
166109
// A device name may contain the following ASCII characters:
167110
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
168111
// - digits ('0'-'9')
169112
// - underscore, dash, dot, colon ('_', '-', '.', ':')
113+
//
114+
// Deprecated: use parser.ValidateDeviceName instead
170115
func ValidateDeviceName(name string) error {
171-
if name == "" {
172-
return fmt.Errorf("invalid (empty) device name")
173-
}
174-
if !isAlphaNumeric(rune(name[0])) {
175-
return fmt.Errorf("invalid class %q, should start with a letter or digit", name)
176-
}
177-
if len(name) == 1 {
178-
return nil
179-
}
180-
for _, c := range string(name[1 : len(name)-1]) {
181-
switch {
182-
case isAlphaNumeric(c):
183-
case c == '_' || c == '-' || c == '.' || c == ':':
184-
default:
185-
return fmt.Errorf("invalid character '%c' in device name %q",
186-
c, name)
187-
}
188-
}
189-
if !isAlphaNumeric(rune(name[len(name)-1])) {
190-
return fmt.Errorf("invalid name %q, should end with a letter or digit", name)
191-
}
192-
return nil
193-
}
194-
195-
func isLetter(c rune) bool {
196-
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
197-
}
198-
199-
func isDigit(c rune) bool {
200-
return '0' <= c && c <= '9'
201-
}
202-
203-
func isAlphaNumeric(c rune) bool {
204-
return isLetter(c) || isDigit(c)
116+
return parser.ValidateDeviceName(name)
205117
}

pkg/cdi/registry.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ import (
2323
oci "github.com/opencontainers/runtime-spec/specs-go"
2424
)
2525

26-
//
2726
// Registry keeps a cache of all CDI Specs installed or generated on
2827
// the host. Registry is the primary interface clients should use to
2928
// interact with CDI.
3029
//
3130
// The most commonly used Registry functions are for refreshing the
3231
// registry and injecting CDI devices into an OCI Spec.
33-
//
3432
type Registry interface {
3533
RegistryResolver
3634
RegistryRefresher

pkg/cdi/spec_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"sigs.k8s.io/yaml"
2828

2929
"github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/validate"
30+
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
3031
"github.com/container-orchestrated-devices/container-device-interface/schema"
3132
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
3233
"github.com/stretchr/testify/require"
@@ -487,7 +488,7 @@ devices:
487488
for name, d := range spec.devices {
488489
require.Equal(t, spec, d.GetSpec())
489490
require.Equal(t, d, spec.GetDevice(name))
490-
require.Equal(t, QualifiedName(vendor, class, name), d.GetQualifiedName())
491+
require.Equal(t, parser.QualifiedName(vendor, class, name), d.GetQualifiedName())
491492
}
492493
})
493494
}

pkg/cdi/version.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"golang.org/x/mod/semver"
2323

24+
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
2425
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
2526
)
2627

@@ -140,7 +141,7 @@ func requiresV050(spec *cdi.Spec) bool {
140141

141142
for _, d := range spec.Devices {
142143
// The v0.5.0 spec allowed device names to start with a digit instead of requiring a letter
143-
if len(d.Name) > 0 && !isLetter(rune(d.Name[0])) {
144+
if len(d.Name) > 0 && !parser.IsLetter(rune(d.Name[0])) {
144145
return true
145146
}
146147
edits = append(edits, &d.ContainerEdits)

0 commit comments

Comments
 (0)