go.mod: github.com/.../container-device-interface v0.6.0

https://github.com/container-orchestrated-devices/container-device-interface/compare/v0.5.4...v0.6.0

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
Akihiro Suda
2023-07-23 02:34:41 +09:00
parent 74b8cb850a
commit 0498acefb9
20 changed files with 660 additions and 160 deletions

View File

@@ -20,6 +20,8 @@ import (
"errors"
"fmt"
"strings"
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
)
const (
@@ -101,22 +103,22 @@ func AnnotationKey(pluginName, deviceID string) (string, error) {
return "", fmt.Errorf("invalid plugin+deviceID %q, too long", name)
}
if c := rune(name[0]); !isAlphaNumeric(c) {
if c := rune(name[0]); !parser.IsAlphaNumeric(c) {
return "", fmt.Errorf("invalid name %q, first '%c' should be alphanumeric",
name, c)
}
if len(name) > 2 {
for _, c := range name[1 : len(name)-1] {
switch {
case isAlphaNumeric(c):
case parser.IsAlphaNumeric(c):
case c == '_' || c == '-' || c == '.':
default:
return "", fmt.Errorf("invalid name %q, invalid charcter '%c'",
return "", fmt.Errorf("invalid name %q, invalid character '%c'",
name, c)
}
}
}
if c := rune(name[len(name)-1]); !isAlphaNumeric(c) {
if c := rune(name[len(name)-1]); !parser.IsAlphaNumeric(c) {
return "", fmt.Errorf("invalid name %q, last '%c' should be alphanumeric",
name, c)
}

View File

@@ -238,7 +238,7 @@ func (d *DeviceNode) Validate() error {
}
for _, bit := range d.Permissions {
if bit != 'r' && bit != 'w' && bit != 'm' {
return fmt.Errorf("device %q: invalid persmissions %q",
return fmt.Errorf("device %q: invalid permissions %q",
d.Path, d.Permissions)
}
}

View File

@@ -19,6 +19,8 @@ package cdi
import (
"fmt"
"github.com/container-orchestrated-devices/container-device-interface/internal/validation"
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
oci "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -50,7 +52,7 @@ func (d *Device) GetSpec() *Spec {
// GetQualifiedName returns the qualified name for this device.
func (d *Device) GetQualifiedName() string {
return QualifiedName(d.spec.GetVendor(), d.spec.GetClass(), d.Name)
return parser.QualifiedName(d.spec.GetVendor(), d.spec.GetClass(), d.Name)
}
// ApplyEdits applies the device-speific container edits to an OCI Spec.
@@ -68,6 +70,13 @@ func (d *Device) validate() error {
if err := ValidateDeviceName(d.Name); err != nil {
return err
}
name := d.Name
if d.spec != nil {
name = d.GetQualifiedName()
}
if err := validation.ValidateSpecAnnotations(name, d.Annotations); err != nil {
return err
}
edits := d.edits()
if edits.isEmpty() {
return fmt.Errorf("invalid device, empty device edits")

View File

@@ -137,7 +137,7 @@
// were loaded from. The later a directory occurs in the list of CDI
// directories to scan, the higher priority Spec files loaded from that
// directory are assigned to. When two or more Spec files define the
// same device, conflict is resolved by chosing the definition from the
// same device, conflict is resolved by choosing the definition from the
// Spec file with the highest priority.
//
// The default CDI directory configuration is chosen to encourage
@@ -197,7 +197,7 @@
// return registry.SpecDB().WriteSpec(spec, specName)
// }
//
// Similary, generating and later cleaning up transient Spec files can be
// Similarly, generating and later cleaning up transient Spec files can be
// done with code fragments similar to the following. These transient Spec
// files are temporary Spec files with container-specific parametrization.
// They are typically created before the associated container is created

View File

@@ -17,27 +17,32 @@
package cdi
import (
"fmt"
"strings"
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
)
// QualifiedName returns the qualified name for a device.
// The syntax for a qualified device names is
// "<vendor>/<class>=<name>".
// A valid vendor name may contain the following runes:
// 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
// A valid class name may contain the following runes:
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_'.
// A valid device name may containe the following runes:
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'
//
// "<vendor>/<class>=<name>".
//
// A valid vendor and class name may contain the following runes:
//
// 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
//
// A valid device name may contain the following runes:
//
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'
//
// Deprecated: use parser.QualifiedName instead
func QualifiedName(vendor, class, name string) string {
return vendor + "/" + class + "=" + name
return parser.QualifiedName(vendor, class, name)
}
// IsQualifiedName tests if a device name is qualified.
//
// Deprecated: use parser.IsQualifiedName instead
func IsQualifiedName(device string) bool {
_, _, _, err := ParseQualifiedName(device)
return err == nil
return parser.IsQualifiedName(device)
}
// ParseQualifiedName splits a qualified name into device vendor, class,
@@ -45,66 +50,33 @@ func IsQualifiedName(device string) bool {
// of the split components fail to pass syntax validation, vendor and
// class are returned as empty, together with the verbatim input as the
// name and an error describing the reason for failure.
//
// Deprecated: use parser.ParseQualifiedName instead
func ParseQualifiedName(device string) (string, string, string, error) {
vendor, class, name := ParseDevice(device)
if vendor == "" {
return "", "", device, fmt.Errorf("unqualified device %q, missing vendor", device)
}
if class == "" {
return "", "", device, fmt.Errorf("unqualified device %q, missing class", device)
}
if name == "" {
return "", "", device, fmt.Errorf("unqualified device %q, missing device name", device)
}
if err := ValidateVendorName(vendor); err != nil {
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
}
if err := ValidateClassName(class); err != nil {
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
}
if err := ValidateDeviceName(name); err != nil {
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
}
return vendor, class, name, nil
return parser.ParseQualifiedName(device)
}
// ParseDevice tries to split a device name into vendor, class, and name.
// If this fails, for instance in the case of unqualified device names,
// ParseDevice returns an empty vendor and class together with name set
// to the verbatim input.
//
// Deprecated: use parser.ParseDevice instead
func ParseDevice(device string) (string, string, string) {
if device == "" || device[0] == '/' {
return "", "", device
}
parts := strings.SplitN(device, "=", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", device
}
name := parts[1]
vendor, class := ParseQualifier(parts[0])
if vendor == "" {
return "", "", device
}
return vendor, class, name
return parser.ParseDevice(device)
}
// ParseQualifier splits a device qualifier into vendor and class.
// The syntax for a device qualifier is
// "<vendor>/<class>"
//
// "<vendor>/<class>"
//
// If parsing fails, an empty vendor and the class set to the
// verbatim input is returned.
//
// Deprecated: use parser.ParseQualifier instead
func ParseQualifier(kind string) (string, string) {
parts := strings.SplitN(kind, "/", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", kind
}
return parts[0], parts[1]
return parser.ParseQualifier(kind)
}
// ValidateVendorName checks the validity of a vendor name.
@@ -112,54 +84,21 @@ func ParseQualifier(kind string) (string, string) {
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
// - digits ('0'-'9')
// - underscore, dash, and dot ('_', '-', and '.')
//
// Deprecated: use parser.ValidateVendorName instead
func ValidateVendorName(vendor string) error {
if vendor == "" {
return fmt.Errorf("invalid (empty) vendor name")
}
if !isLetter(rune(vendor[0])) {
return fmt.Errorf("invalid vendor %q, should start with letter", vendor)
}
for _, c := range string(vendor[1 : len(vendor)-1]) {
switch {
case isAlphaNumeric(c):
case c == '_' || c == '-' || c == '.':
default:
return fmt.Errorf("invalid character '%c' in vendor name %q",
c, vendor)
}
}
if !isAlphaNumeric(rune(vendor[len(vendor)-1])) {
return fmt.Errorf("invalid vendor %q, should end with a letter or digit", vendor)
}
return nil
return parser.ValidateVendorName(vendor)
}
// ValidateClassName checks the validity of class name.
// A class name may contain the following ASCII characters:
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
// - digits ('0'-'9')
// - underscore and dash ('_', '-')
// - underscore, dash, and dot ('_', '-', and '.')
//
// Deprecated: use parser.ValidateClassName instead
func ValidateClassName(class string) error {
if class == "" {
return fmt.Errorf("invalid (empty) device class")
}
if !isLetter(rune(class[0])) {
return fmt.Errorf("invalid class %q, should start with letter", class)
}
for _, c := range string(class[1 : len(class)-1]) {
switch {
case isAlphaNumeric(c):
case c == '_' || c == '-':
default:
return fmt.Errorf("invalid character '%c' in device class %q",
c, class)
}
}
if !isAlphaNumeric(rune(class[len(class)-1])) {
return fmt.Errorf("invalid class %q, should end with a letter or digit", class)
}
return nil
return parser.ValidateClassName(class)
}
// ValidateDeviceName checks the validity of a device name.
@@ -167,39 +106,8 @@ func ValidateClassName(class string) error {
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
// - digits ('0'-'9')
// - underscore, dash, dot, colon ('_', '-', '.', ':')
//
// Deprecated: use parser.ValidateDeviceName instead
func ValidateDeviceName(name string) error {
if name == "" {
return fmt.Errorf("invalid (empty) device name")
}
if !isAlphaNumeric(rune(name[0])) {
return fmt.Errorf("invalid class %q, should start with a letter or digit", name)
}
if len(name) == 1 {
return nil
}
for _, c := range string(name[1 : len(name)-1]) {
switch {
case isAlphaNumeric(c):
case c == '_' || c == '-' || c == '.' || c == ':':
default:
return fmt.Errorf("invalid character '%c' in device name %q",
c, name)
}
}
if !isAlphaNumeric(rune(name[len(name)-1])) {
return fmt.Errorf("invalid name %q, should end with a letter or digit", name)
}
return nil
}
func isLetter(c rune) bool {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
}
func isDigit(c rune) bool {
return '0' <= c && c <= '9'
}
func isAlphaNumeric(c rune) bool {
return isLetter(c) || isDigit(c)
return parser.ValidateDeviceName(name)
}

View File

@@ -23,14 +23,12 @@ import (
oci "github.com/opencontainers/runtime-spec/specs-go"
)
//
// Registry keeps a cache of all CDI Specs installed or generated on
// the host. Registry is the primary interface clients should use to
// interact with CDI.
//
// The most commonly used Registry functions are for refreshing the
// registry and injecting CDI devices into an OCI Spec.
//
type Registry interface {
RegistryResolver
RegistryRefresher

View File

@@ -28,6 +28,7 @@ import (
oci "github.com/opencontainers/runtime-spec/specs-go"
"sigs.k8s.io/yaml"
"github.com/container-orchestrated-devices/container-device-interface/internal/validation"
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
)
@@ -131,6 +132,7 @@ func (s *Spec) write(overwrite bool) error {
if filepath.Ext(s.path) == ".yaml" {
data, err = yaml.Marshal(s.Spec)
data = append([]byte("---\n"), data...)
} else {
data, err = json.Marshal(s.Spec)
}
@@ -207,7 +209,7 @@ func (s *Spec) validate() (map[string]*Device, error) {
minVersion, err := MinimumRequiredVersion(s.Spec)
if err != nil {
return nil, fmt.Errorf("could not determine minumum required version: %v", err)
return nil, fmt.Errorf("could not determine minimum required version: %v", err)
}
if newVersion(minVersion).IsGreaterThan(newVersion(s.Version)) {
return nil, fmt.Errorf("the spec version must be at least v%v", minVersion)
@@ -219,6 +221,9 @@ func (s *Spec) validate() (map[string]*Device, error) {
if err := ValidateClassName(s.class); err != nil {
return nil, err
}
if err := validation.ValidateSpecAnnotations(s.Kind, s.Annotations); err != nil {
return nil, err
}
if err := s.edits().Validate(); err != nil {
return nil, err
}
@@ -306,7 +311,7 @@ func GenerateSpecName(vendor, class string) string {
// match the vendor and class of the CDI Spec. transientID should be
// unique among all CDI users on the same host that might generate
// transient Spec files using the same vendor/class combination. If
// the external entity to which the lifecycle of the tranient Spec
// the external entity to which the lifecycle of the transient Spec
// is tied to has a unique ID of its own, then this is usually a
// good choice for transientID.
//

View File

@@ -21,6 +21,7 @@ import (
"golang.org/x/mod/semver"
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
)
@@ -37,6 +38,7 @@ const (
v030 version = "v0.3.0"
v040 version = "v0.4.0"
v050 version = "v0.5.0"
v060 version = "v0.6.0"
// vEarliest is the earliest supported version of the CDI specification
vEarliest version = v030
@@ -51,9 +53,10 @@ var validSpecVersions = requiredVersionMap{
v030: nil,
v040: requiresV040,
v050: requiresV050,
v060: requiresV060,
}
// MinimumRequiredVersion determines the minumum spec version for the input spec.
// MinimumRequiredVersion determines the minimum spec version for the input spec.
func MinimumRequiredVersion(spec *cdi.Spec) (string, error) {
minVersion := validSpecVersions.requiredVersion(spec)
return minVersion.String(), nil
@@ -115,13 +118,38 @@ func (r requiredVersionMap) requiredVersion(spec *cdi.Spec) version {
return minVersion
}
// requiresV060 returns true if the spec uses v0.6.0 features
func requiresV060(spec *cdi.Spec) bool {
// The v0.6.0 spec allows annotations to be specified at a spec level
for range spec.Annotations {
return true
}
// The v0.6.0 spec allows annotations to be specified at a device level
for _, d := range spec.Devices {
for range d.Annotations {
return true
}
}
// The v0.6.0 spec allows dots "." in Kind name label (class)
vendor, class := parser.ParseQualifier(spec.Kind)
if vendor != "" {
if strings.ContainsRune(class, '.') {
return true
}
}
return false
}
// requiresV050 returns true if the spec uses v0.5.0 features
func requiresV050(spec *cdi.Spec) bool {
var edits []*cdi.ContainerEdits
for _, d := range spec.Devices {
// The v0.5.0 spec allowed device names to start with a digit instead of requiring a letter
if len(d.Name) > 0 && !isLetter(rune(d.Name[0])) {
if len(d.Name) > 0 && !parser.IsLetter(rune(d.Name[0])) {
return true
}
edits = append(edits, &d.ContainerEdits)