mirror of
https://github.com/NVIDIA/nvidia-container-toolkit
synced 2025-03-29 03:43:53 +00:00
Update container-device-interface to v0.6.0
Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
parent
c98f6ea395
commit
96aeb9bf64
2
go.mod
2
go.mod
@ -5,7 +5,7 @@ go 1.20
|
|||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1
|
github.com/BurntSushi/toml v1.2.1
|
||||||
github.com/NVIDIA/go-nvml v0.12.0-1
|
github.com/NVIDIA/go-nvml v0.12.0-1
|
||||||
github.com/container-orchestrated-devices/container-device-interface v0.5.4-0.20230111111500-5b3b5d81179a
|
github.com/container-orchestrated-devices/container-device-interface v0.6.0
|
||||||
github.com/fsnotify/fsnotify v1.5.4
|
github.com/fsnotify/fsnotify v1.5.4
|
||||||
github.com/opencontainers/runtime-spec v1.1.0-rc.2
|
github.com/opencontainers/runtime-spec v1.1.0-rc.2
|
||||||
github.com/pelletier/go-toml v1.9.4
|
github.com/pelletier/go-toml v1.9.4
|
||||||
|
2
go.sum
2
go.sum
@ -7,6 +7,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM
|
|||||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||||
github.com/container-orchestrated-devices/container-device-interface v0.5.4-0.20230111111500-5b3b5d81179a h1:sP3PcgyIkRlHqfF3Jfpe/7G8kf/qpzG4C8r94y9hLbE=
|
github.com/container-orchestrated-devices/container-device-interface v0.5.4-0.20230111111500-5b3b5d81179a h1:sP3PcgyIkRlHqfF3Jfpe/7G8kf/qpzG4C8r94y9hLbE=
|
||||||
github.com/container-orchestrated-devices/container-device-interface v0.5.4-0.20230111111500-5b3b5d81179a/go.mod h1:xMRa4fJgXzSDFUCURSimOUgoSc+odohvO3uXT9xjqH0=
|
github.com/container-orchestrated-devices/container-device-interface v0.5.4-0.20230111111500-5b3b5d81179a/go.mod h1:xMRa4fJgXzSDFUCURSimOUgoSc+odohvO3uXT9xjqH0=
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface v0.6.0 h1:aWwcz/Ep0Fd7ZuBjQGjU/jdPloM7ydhMW13h85jZNvk=
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface v0.6.0/go.mod h1:OQlgtJtDrOxSQ1BWODC8OZK1tzi9W69wek+Jy17ndzo=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
57
vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/objectmeta.go
generated
vendored
Normal file
57
vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/objectmeta.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Adapted from k8s.io/apimachinery/pkg/api/validation:
|
||||||
|
// https://github.com/kubernetes/apimachinery/blob/7687996c715ee7d5c8cf1e3215e607eb065a4221/pkg/api/validation/objectmeta.go
|
||||||
|
|
||||||
|
package k8s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/container-orchestrated-devices/container-device-interface/internal/multierror"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TotalAnnotationSizeLimitB defines the maximum size of all annotations in characters.
|
||||||
|
const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
|
||||||
|
|
||||||
|
// ValidateAnnotations validates that a set of annotations are correctly defined.
|
||||||
|
func ValidateAnnotations(annotations map[string]string, path string) error {
|
||||||
|
errors := multierror.New()
|
||||||
|
for k := range annotations {
|
||||||
|
// The rule is QualifiedName except that case doesn't matter, so convert to lowercase before checking.
|
||||||
|
for _, msg := range IsQualifiedName(strings.ToLower(k)) {
|
||||||
|
errors = multierror.Append(errors, fmt.Errorf("%v.%v is invalid: %v", path, k, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ValidateAnnotationsSize(annotations); err != nil {
|
||||||
|
errors = multierror.Append(errors, fmt.Errorf("%v is too long: %v", path, err))
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAnnotationsSize validates that a set of annotations is not too large.
|
||||||
|
func ValidateAnnotationsSize(annotations map[string]string) error {
|
||||||
|
var totalSize int64
|
||||||
|
for k, v := range annotations {
|
||||||
|
totalSize += (int64)(len(k)) + (int64)(len(v))
|
||||||
|
}
|
||||||
|
if totalSize > (int64)(TotalAnnotationSizeLimitB) {
|
||||||
|
return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
217
vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/validation.go
generated
vendored
Normal file
217
vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/validation.go
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Adapted from k8s.io/apimachinery/pkg/util/validation:
|
||||||
|
// https://github.com/kubernetes/apimachinery/blob/7687996c715ee7d5c8cf1e3215e607eb065a4221/pkg/util/validation/validation.go
|
||||||
|
|
||||||
|
package k8s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const qnameCharFmt string = "[A-Za-z0-9]"
|
||||||
|
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
|
||||||
|
const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
|
||||||
|
const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
|
||||||
|
const qualifiedNameMaxLength int = 63
|
||||||
|
|
||||||
|
var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
|
||||||
|
|
||||||
|
// IsQualifiedName tests whether the value passed is what Kubernetes calls a
|
||||||
|
// "qualified name". This is a format used in various places throughout the
|
||||||
|
// system. If the value is not valid, a list of error strings is returned.
|
||||||
|
// Otherwise an empty list (or nil) is returned.
|
||||||
|
func IsQualifiedName(value string) []string {
|
||||||
|
var errs []string
|
||||||
|
parts := strings.Split(value, "/")
|
||||||
|
var name string
|
||||||
|
switch len(parts) {
|
||||||
|
case 1:
|
||||||
|
name = parts[0]
|
||||||
|
case 2:
|
||||||
|
var prefix string
|
||||||
|
prefix, name = parts[0], parts[1]
|
||||||
|
if len(prefix) == 0 {
|
||||||
|
errs = append(errs, "prefix part "+EmptyError())
|
||||||
|
} else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
|
||||||
|
errs = append(errs, prefixEach(msgs, "prefix part ")...)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
|
||||||
|
" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(name) == 0 {
|
||||||
|
errs = append(errs, "name part "+EmptyError())
|
||||||
|
} else if len(name) > qualifiedNameMaxLength {
|
||||||
|
errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength))
|
||||||
|
}
|
||||||
|
if !qualifiedNameRegexp.MatchString(name) {
|
||||||
|
errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"))
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelValueFmt string = "(" + qualifiedNameFmt + ")?"
|
||||||
|
const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
|
||||||
|
|
||||||
|
// LabelValueMaxLength is a label's max length
|
||||||
|
const LabelValueMaxLength int = 63
|
||||||
|
|
||||||
|
var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
|
||||||
|
|
||||||
|
// IsValidLabelValue tests whether the value passed is a valid label value. If
|
||||||
|
// the value is not valid, a list of error strings is returned. Otherwise an
|
||||||
|
// empty list (or nil) is returned.
|
||||||
|
func IsValidLabelValue(value string) []string {
|
||||||
|
var errs []string
|
||||||
|
if len(value) > LabelValueMaxLength {
|
||||||
|
errs = append(errs, MaxLenError(LabelValueMaxLength))
|
||||||
|
}
|
||||||
|
if !labelValueRegexp.MatchString(value) {
|
||||||
|
errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
|
||||||
|
const dns1123LabelErrMsg string = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
|
||||||
|
|
||||||
|
// DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123)
|
||||||
|
const DNS1123LabelMaxLength int = 63
|
||||||
|
|
||||||
|
var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
|
||||||
|
|
||||||
|
// IsDNS1123Label tests for a string that conforms to the definition of a label in
|
||||||
|
// DNS (RFC 1123).
|
||||||
|
func IsDNS1123Label(value string) []string {
|
||||||
|
var errs []string
|
||||||
|
if len(value) > DNS1123LabelMaxLength {
|
||||||
|
errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
|
||||||
|
}
|
||||||
|
if !dns1123LabelRegexp.MatchString(value) {
|
||||||
|
errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
|
||||||
|
const dns1123SubdomainErrorMsg string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
|
||||||
|
|
||||||
|
// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
|
||||||
|
const DNS1123SubdomainMaxLength int = 253
|
||||||
|
|
||||||
|
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
|
||||||
|
|
||||||
|
// IsDNS1123Subdomain tests for a string that conforms to the definition of a
|
||||||
|
// subdomain in DNS (RFC 1123).
|
||||||
|
func IsDNS1123Subdomain(value string) []string {
|
||||||
|
var errs []string
|
||||||
|
if len(value) > DNS1123SubdomainMaxLength {
|
||||||
|
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
|
||||||
|
}
|
||||||
|
if !dns1123SubdomainRegexp.MatchString(value) {
|
||||||
|
errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
|
||||||
|
const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"
|
||||||
|
|
||||||
|
// DNS1035LabelMaxLength is a label's max length in DNS (RFC 1035)
|
||||||
|
const DNS1035LabelMaxLength int = 63
|
||||||
|
|
||||||
|
var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
|
||||||
|
|
||||||
|
// IsDNS1035Label tests for a string that conforms to the definition of a label in
|
||||||
|
// DNS (RFC 1035).
|
||||||
|
func IsDNS1035Label(value string) []string {
|
||||||
|
var errs []string
|
||||||
|
if len(value) > DNS1035LabelMaxLength {
|
||||||
|
errs = append(errs, MaxLenError(DNS1035LabelMaxLength))
|
||||||
|
}
|
||||||
|
if !dns1035LabelRegexp.MatchString(value) {
|
||||||
|
errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// wildcard definition - RFC 1034 section 4.3.3.
|
||||||
|
// examples:
|
||||||
|
// - valid: *.bar.com, *.foo.bar.com
|
||||||
|
// - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, *
|
||||||
|
const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt
|
||||||
|
const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character"
|
||||||
|
|
||||||
|
// IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a
|
||||||
|
// wildcard subdomain in DNS (RFC 1034 section 4.3.3).
|
||||||
|
func IsWildcardDNS1123Subdomain(value string) []string {
|
||||||
|
wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$")
|
||||||
|
|
||||||
|
var errs []string
|
||||||
|
if len(value) > DNS1123SubdomainMaxLength {
|
||||||
|
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
|
||||||
|
}
|
||||||
|
if !wildcardDNS1123SubdomainRegexp.MatchString(value) {
|
||||||
|
errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com"))
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxLenError returns a string explanation of a "string too long" validation
|
||||||
|
// failure.
|
||||||
|
func MaxLenError(length int) string {
|
||||||
|
return fmt.Sprintf("must be no more than %d characters", length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegexError returns a string explanation of a regex validation failure.
|
||||||
|
func RegexError(msg string, fmt string, examples ...string) string {
|
||||||
|
if len(examples) == 0 {
|
||||||
|
return msg + " (regex used for validation is '" + fmt + "')"
|
||||||
|
}
|
||||||
|
msg += " (e.g. "
|
||||||
|
for i := range examples {
|
||||||
|
if i > 0 {
|
||||||
|
msg += " or "
|
||||||
|
}
|
||||||
|
msg += "'" + examples[i] + "', "
|
||||||
|
}
|
||||||
|
msg += "regex used for validation is '" + fmt + "')"
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyError returns a string explanation of a "must not be empty" validation
|
||||||
|
// failure.
|
||||||
|
func EmptyError() string {
|
||||||
|
return "must be non-empty"
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixEach(msgs []string, prefix string) []string {
|
||||||
|
for i := range msgs {
|
||||||
|
msgs[i] = prefix + msgs[i]
|
||||||
|
}
|
||||||
|
return msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// InclusiveRangeError returns a string explanation of a numeric "must be
|
||||||
|
// between" validation failure.
|
||||||
|
func InclusiveRangeError(lo, hi int) string {
|
||||||
|
return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi)
|
||||||
|
}
|
56
vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/validate.go
generated
vendored
Normal file
56
vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/validate.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Copyright © The CDI Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateSpecAnnotations checks whether spec annotations are valid.
|
||||||
|
func ValidateSpecAnnotations(name string, any interface{}) error {
|
||||||
|
if any == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := any.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
annotations := make(map[string]string)
|
||||||
|
for k, v := range v {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
annotations[k] = s
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid annotation %v.%v; %v is not a string", name, k, any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validateSpecAnnotations(name, annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSpecAnnotations checks whether spec annotations are valid.
|
||||||
|
func validateSpecAnnotations(name string, annotations map[string]string) error {
|
||||||
|
path := "annotations"
|
||||||
|
if name != "" {
|
||||||
|
path = strings.Join([]string{name, path}, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
return k8s.ValidateAnnotations(annotations, path)
|
||||||
|
}
|
@ -20,6 +20,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -101,22 +103,22 @@ func AnnotationKey(pluginName, deviceID string) (string, error) {
|
|||||||
return "", fmt.Errorf("invalid plugin+deviceID %q, too long", name)
|
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",
|
return "", fmt.Errorf("invalid name %q, first '%c' should be alphanumeric",
|
||||||
name, c)
|
name, c)
|
||||||
}
|
}
|
||||||
if len(name) > 2 {
|
if len(name) > 2 {
|
||||||
for _, c := range name[1 : len(name)-1] {
|
for _, c := range name[1 : len(name)-1] {
|
||||||
switch {
|
switch {
|
||||||
case isAlphaNumeric(c):
|
case parser.IsAlphaNumeric(c):
|
||||||
case c == '_' || c == '-' || c == '.':
|
case c == '_' || c == '-' || c == '.':
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("invalid name %q, invalid charcter '%c'",
|
return "", fmt.Errorf("invalid name %q, invalid character '%c'",
|
||||||
name, 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",
|
return "", fmt.Errorf("invalid name %q, last '%c' should be alphanumeric",
|
||||||
name, c)
|
name, c)
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@ func (d *DeviceNode) Validate() error {
|
|||||||
}
|
}
|
||||||
for _, bit := range d.Permissions {
|
for _, bit := range d.Permissions {
|
||||||
if bit != 'r' && bit != 'w' && bit != 'm' {
|
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)
|
d.Path, d.Permissions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ package cdi
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"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"
|
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
|
||||||
oci "github.com/opencontainers/runtime-spec/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.
|
// GetQualifiedName returns the qualified name for this device.
|
||||||
func (d *Device) GetQualifiedName() string {
|
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.
|
// 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 {
|
if err := ValidateDeviceName(d.Name); err != nil {
|
||||||
return err
|
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()
|
edits := d.edits()
|
||||||
if edits.isEmpty() {
|
if edits.isEmpty() {
|
||||||
return fmt.Errorf("invalid device, empty device edits")
|
return fmt.Errorf("invalid device, empty device edits")
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
// were loaded from. The later a directory occurs in the list of CDI
|
// were loaded from. The later a directory occurs in the list of CDI
|
||||||
// directories to scan, the higher priority Spec files loaded from that
|
// directories to scan, the higher priority Spec files loaded from that
|
||||||
// directory are assigned to. When two or more Spec files define the
|
// 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.
|
// Spec file with the highest priority.
|
||||||
//
|
//
|
||||||
// The default CDI directory configuration is chosen to encourage
|
// The default CDI directory configuration is chosen to encourage
|
||||||
@ -197,7 +197,7 @@
|
|||||||
// return registry.SpecDB().WriteSpec(spec, specName)
|
// 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
|
// done with code fragments similar to the following. These transient Spec
|
||||||
// files are temporary Spec files with container-specific parametrization.
|
// files are temporary Spec files with container-specific parametrization.
|
||||||
// They are typically created before the associated container is created
|
// They are typically created before the associated container is created
|
||||||
|
@ -17,27 +17,32 @@
|
|||||||
package cdi
|
package cdi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// QualifiedName returns the qualified name for a device.
|
// QualifiedName returns the qualified name for a device.
|
||||||
// The syntax for a qualified device names is
|
// The syntax for a qualified device names is
|
||||||
// "<vendor>/<class>=<name>".
|
//
|
||||||
// A valid vendor name may contain the following runes:
|
// "<vendor>/<class>=<name>".
|
||||||
// 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
|
//
|
||||||
// A valid class name may contain the following runes:
|
// A valid vendor and 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', '.', '-', '_'.
|
||||||
// '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 {
|
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.
|
// IsQualifiedName tests if a device name is qualified.
|
||||||
|
//
|
||||||
|
// Deprecated: use parser.IsQualifiedName instead
|
||||||
func IsQualifiedName(device string) bool {
|
func IsQualifiedName(device string) bool {
|
||||||
_, _, _, err := ParseQualifiedName(device)
|
return parser.IsQualifiedName(device)
|
||||||
return err == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseQualifiedName splits a qualified name into device vendor, class,
|
// 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
|
// of the split components fail to pass syntax validation, vendor and
|
||||||
// class are returned as empty, together with the verbatim input as the
|
// class are returned as empty, together with the verbatim input as the
|
||||||
// name and an error describing the reason for failure.
|
// name and an error describing the reason for failure.
|
||||||
|
//
|
||||||
|
// Deprecated: use parser.ParseQualifiedName instead
|
||||||
func ParseQualifiedName(device string) (string, string, string, error) {
|
func ParseQualifiedName(device string) (string, string, string, error) {
|
||||||
vendor, class, name := ParseDevice(device)
|
return parser.ParseQualifiedName(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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDevice tries to split a device name into vendor, class, and name.
|
// ParseDevice tries to split a device name into vendor, class, and name.
|
||||||
// If this fails, for instance in the case of unqualified device names,
|
// If this fails, for instance in the case of unqualified device names,
|
||||||
// ParseDevice returns an empty vendor and class together with name set
|
// ParseDevice returns an empty vendor and class together with name set
|
||||||
// to the verbatim input.
|
// to the verbatim input.
|
||||||
|
//
|
||||||
|
// Deprecated: use parser.ParseDevice instead
|
||||||
func ParseDevice(device string) (string, string, string) {
|
func ParseDevice(device string) (string, string, string) {
|
||||||
if device == "" || device[0] == '/' {
|
return parser.ParseDevice(device)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseQualifier splits a device qualifier into vendor and class.
|
// ParseQualifier splits a device qualifier into vendor and class.
|
||||||
// The syntax for a device qualifier is
|
// The syntax for a device qualifier is
|
||||||
// "<vendor>/<class>"
|
//
|
||||||
|
// "<vendor>/<class>"
|
||||||
|
//
|
||||||
// If parsing fails, an empty vendor and the class set to the
|
// If parsing fails, an empty vendor and the class set to the
|
||||||
// verbatim input is returned.
|
// verbatim input is returned.
|
||||||
|
//
|
||||||
|
// Deprecated: use parser.ParseQualifier instead
|
||||||
func ParseQualifier(kind string) (string, string) {
|
func ParseQualifier(kind string) (string, string) {
|
||||||
parts := strings.SplitN(kind, "/", 2)
|
return parser.ParseQualifier(kind)
|
||||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
|
||||||
return "", kind
|
|
||||||
}
|
|
||||||
return parts[0], parts[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateVendorName checks the validity of a vendor name.
|
// 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')
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
// - digits ('0'-'9')
|
// - digits ('0'-'9')
|
||||||
// - underscore, dash, and dot ('_', '-', and '.')
|
// - underscore, dash, and dot ('_', '-', and '.')
|
||||||
|
//
|
||||||
|
// Deprecated: use parser.ValidateVendorName instead
|
||||||
func ValidateVendorName(vendor string) error {
|
func ValidateVendorName(vendor string) error {
|
||||||
if vendor == "" {
|
return parser.ValidateVendorName(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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateClassName checks the validity of class name.
|
// ValidateClassName checks the validity of class name.
|
||||||
// A class name may contain the following ASCII characters:
|
// A class name may contain the following ASCII characters:
|
||||||
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
// - digits ('0'-'9')
|
// - digits ('0'-'9')
|
||||||
// - underscore and dash ('_', '-')
|
// - underscore, dash, and dot ('_', '-', and '.')
|
||||||
|
//
|
||||||
|
// Deprecated: use parser.ValidateClassName instead
|
||||||
func ValidateClassName(class string) error {
|
func ValidateClassName(class string) error {
|
||||||
if class == "" {
|
return parser.ValidateClassName(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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateDeviceName checks the validity of a device name.
|
// 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')
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
// - digits ('0'-'9')
|
// - digits ('0'-'9')
|
||||||
// - underscore, dash, dot, colon ('_', '-', '.', ':')
|
// - underscore, dash, dot, colon ('_', '-', '.', ':')
|
||||||
|
//
|
||||||
|
// Deprecated: use parser.ValidateDeviceName instead
|
||||||
func ValidateDeviceName(name string) error {
|
func ValidateDeviceName(name string) error {
|
||||||
if name == "" {
|
return parser.ValidateDeviceName(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)
|
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,12 @@ import (
|
|||||||
oci "github.com/opencontainers/runtime-spec/specs-go"
|
oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
//
|
|
||||||
// Registry keeps a cache of all CDI Specs installed or generated on
|
// Registry keeps a cache of all CDI Specs installed or generated on
|
||||||
// the host. Registry is the primary interface clients should use to
|
// the host. Registry is the primary interface clients should use to
|
||||||
// interact with CDI.
|
// interact with CDI.
|
||||||
//
|
//
|
||||||
// The most commonly used Registry functions are for refreshing the
|
// The most commonly used Registry functions are for refreshing the
|
||||||
// registry and injecting CDI devices into an OCI Spec.
|
// registry and injecting CDI devices into an OCI Spec.
|
||||||
//
|
|
||||||
type Registry interface {
|
type Registry interface {
|
||||||
RegistryResolver
|
RegistryResolver
|
||||||
RegistryRefresher
|
RegistryRefresher
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
oci "github.com/opencontainers/runtime-spec/specs-go"
|
oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"sigs.k8s.io/yaml"
|
"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"
|
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" {
|
if filepath.Ext(s.path) == ".yaml" {
|
||||||
data, err = yaml.Marshal(s.Spec)
|
data, err = yaml.Marshal(s.Spec)
|
||||||
|
data = append([]byte("---\n"), data...)
|
||||||
} else {
|
} else {
|
||||||
data, err = json.Marshal(s.Spec)
|
data, err = json.Marshal(s.Spec)
|
||||||
}
|
}
|
||||||
@ -207,7 +209,7 @@ func (s *Spec) validate() (map[string]*Device, error) {
|
|||||||
|
|
||||||
minVersion, err := MinimumRequiredVersion(s.Spec)
|
minVersion, err := MinimumRequiredVersion(s.Spec)
|
||||||
if err != nil {
|
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)) {
|
if newVersion(minVersion).IsGreaterThan(newVersion(s.Version)) {
|
||||||
return nil, fmt.Errorf("the spec version must be at least v%v", minVersion)
|
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 {
|
if err := ValidateClassName(s.class); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := validation.ValidateSpecAnnotations(s.Kind, s.Annotations); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := s.edits().Validate(); err != nil {
|
if err := s.edits().Validate(); err != nil {
|
||||||
return nil, err
|
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
|
// match the vendor and class of the CDI Spec. transientID should be
|
||||||
// unique among all CDI users on the same host that might generate
|
// unique among all CDI users on the same host that might generate
|
||||||
// transient Spec files using the same vendor/class combination. If
|
// 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
|
// is tied to has a unique ID of its own, then this is usually a
|
||||||
// good choice for transientID.
|
// good choice for transientID.
|
||||||
//
|
//
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/mod/semver"
|
"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"
|
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ const (
|
|||||||
v030 version = "v0.3.0"
|
v030 version = "v0.3.0"
|
||||||
v040 version = "v0.4.0"
|
v040 version = "v0.4.0"
|
||||||
v050 version = "v0.5.0"
|
v050 version = "v0.5.0"
|
||||||
|
v060 version = "v0.6.0"
|
||||||
|
|
||||||
// vEarliest is the earliest supported version of the CDI specification
|
// vEarliest is the earliest supported version of the CDI specification
|
||||||
vEarliest version = v030
|
vEarliest version = v030
|
||||||
@ -51,9 +53,10 @@ var validSpecVersions = requiredVersionMap{
|
|||||||
v030: nil,
|
v030: nil,
|
||||||
v040: requiresV040,
|
v040: requiresV040,
|
||||||
v050: requiresV050,
|
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) {
|
func MinimumRequiredVersion(spec *cdi.Spec) (string, error) {
|
||||||
minVersion := validSpecVersions.requiredVersion(spec)
|
minVersion := validSpecVersions.requiredVersion(spec)
|
||||||
return minVersion.String(), nil
|
return minVersion.String(), nil
|
||||||
@ -115,13 +118,38 @@ func (r requiredVersionMap) requiredVersion(spec *cdi.Spec) version {
|
|||||||
return minVersion
|
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
|
// requiresV050 returns true if the spec uses v0.5.0 features
|
||||||
func requiresV050(spec *cdi.Spec) bool {
|
func requiresV050(spec *cdi.Spec) bool {
|
||||||
var edits []*cdi.ContainerEdits
|
var edits []*cdi.ContainerEdits
|
||||||
|
|
||||||
for _, d := range spec.Devices {
|
for _, d := range spec.Devices {
|
||||||
// The v0.5.0 spec allowed device names to start with a digit instead of requiring a letter
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
edits = append(edits, &d.ContainerEdits)
|
edits = append(edits, &d.ContainerEdits)
|
||||||
|
212
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/parser/parser.go
generated
vendored
Normal file
212
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/parser/parser.go
generated
vendored
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
Copyright © The CDI Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QualifiedName returns the qualified name for a device.
|
||||||
|
// The syntax for a qualified device names is
|
||||||
|
//
|
||||||
|
// "<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', '-', '_', '.', ':'
|
||||||
|
func QualifiedName(vendor, class, name string) string {
|
||||||
|
return vendor + "/" + class + "=" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsQualifiedName tests if a device name is qualified.
|
||||||
|
func IsQualifiedName(device string) bool {
|
||||||
|
_, _, _, err := ParseQualifiedName(device)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseQualifiedName splits a qualified name into device vendor, class,
|
||||||
|
// and name. If the device fails to parse as a qualified name, or if any
|
||||||
|
// 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.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseQualifier splits a device qualifier into vendor and class.
|
||||||
|
// The syntax for a device qualifier is
|
||||||
|
//
|
||||||
|
// "<vendor>/<class>"
|
||||||
|
//
|
||||||
|
// If parsing fails, an empty vendor and the class set to the
|
||||||
|
// verbatim input is returned.
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateVendorName checks the validity of a vendor name.
|
||||||
|
// A vendor name may contain the following ASCII characters:
|
||||||
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
|
// - digits ('0'-'9')
|
||||||
|
// - underscore, dash, and dot ('_', '-', and '.')
|
||||||
|
func ValidateVendorName(vendor string) error {
|
||||||
|
err := validateVendorOrClassName(vendor)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("invalid vendor. %w", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, dash, and dot ('_', '-', and '.')
|
||||||
|
func ValidateClassName(class string) error {
|
||||||
|
err := validateVendorOrClassName(class)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("invalid class. %w", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateVendorOrClassName checks the validity of vendor or class name.
|
||||||
|
// A name may contain the following ASCII characters:
|
||||||
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
|
// - digits ('0'-'9')
|
||||||
|
// - underscore, dash, and dot ('_', '-', and '.')
|
||||||
|
func validateVendorOrClassName(name string) error {
|
||||||
|
if name == "" {
|
||||||
|
return fmt.Errorf("empty name")
|
||||||
|
}
|
||||||
|
if !IsLetter(rune(name[0])) {
|
||||||
|
return fmt.Errorf("%q, should start with letter", name)
|
||||||
|
}
|
||||||
|
for _, c := range string(name[1 : len(name)-1]) {
|
||||||
|
switch {
|
||||||
|
case IsAlphaNumeric(c):
|
||||||
|
case c == '_' || c == '-' || c == '.':
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid character '%c' in name %q",
|
||||||
|
c, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !IsAlphaNumeric(rune(name[len(name)-1])) {
|
||||||
|
return fmt.Errorf("%q, should end with a letter or digit", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateDeviceName checks the validity of a device name.
|
||||||
|
// A device name may contain the following ASCII characters:
|
||||||
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
|
// - digits ('0'-'9')
|
||||||
|
// - underscore, dash, dot, colon ('_', '-', '.', ':')
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLetter reports whether the rune is a letter.
|
||||||
|
func IsLetter(c rune) bool {
|
||||||
|
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDigit reports whether the rune is a digit.
|
||||||
|
func IsDigit(c rune) bool {
|
||||||
|
return '0' <= c && c <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAlphaNumeric reports whether the rune is a letter or digit.
|
||||||
|
func IsAlphaNumeric(c rune) bool {
|
||||||
|
return IsLetter(c) || IsDigit(c)
|
||||||
|
}
|
@ -3,21 +3,24 @@ package specs
|
|||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
// CurrentVersion is the current version of the Spec.
|
// CurrentVersion is the current version of the Spec.
|
||||||
const CurrentVersion = "0.5.0"
|
const CurrentVersion = "0.6.0"
|
||||||
|
|
||||||
// Spec is the base configuration for CDI
|
// Spec is the base configuration for CDI
|
||||||
type Spec struct {
|
type Spec struct {
|
||||||
Version string `json:"cdiVersion"`
|
Version string `json:"cdiVersion"`
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
|
// Annotations add meta information per CDI spec. Note these are CDI-specific and do not affect container metadata.
|
||||||
Devices []Device `json:"devices"`
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
ContainerEdits ContainerEdits `json:"containerEdits,omitempty"`
|
Devices []Device `json:"devices"`
|
||||||
|
ContainerEdits ContainerEdits `json:"containerEdits,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device is a "Device" a container runtime can add to a container
|
// Device is a "Device" a container runtime can add to a container
|
||||||
type Device struct {
|
type Device struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ContainerEdits ContainerEdits `json:"containerEdits"`
|
// Annotations add meta information per device. Note these are CDI-specific and do not affect container metadata.
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
ContainerEdits ContainerEdits `json:"containerEdits"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerEdits are edits a container runtime must make to the OCI spec to expose the device.
|
// ContainerEdits are edits a container runtime must make to the OCI spec to expose the device.
|
||||||
|
@ -22,7 +22,7 @@ func ApplyOCIEditsForDevice(config *spec.Spec, cdi *Spec, dev string) error {
|
|||||||
return fmt.Errorf("CDI: device %q not found for spec %q", dev, cdi.Kind)
|
return fmt.Errorf("CDI: device %q not found for spec %q", dev, cdi.Kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyOCIEdits applies the OCI edits the CDI spec declares globablly
|
// ApplyOCIEdits applies the OCI edits the CDI spec declares globally
|
||||||
func ApplyOCIEdits(config *spec.Spec, cdi *Spec) error {
|
func ApplyOCIEdits(config *spec.Spec, cdi *Spec) error {
|
||||||
return ApplyEditsToOCISpec(config, &cdi.ContainerEdits)
|
return ApplyEditsToOCISpec(config, &cdi.ContainerEdits)
|
||||||
}
|
}
|
||||||
|
5
vendor/modules.txt
vendored
5
vendor/modules.txt
vendored
@ -6,10 +6,13 @@ github.com/BurntSushi/toml/internal
|
|||||||
## explicit; go 1.15
|
## explicit; go 1.15
|
||||||
github.com/NVIDIA/go-nvml/pkg/dl
|
github.com/NVIDIA/go-nvml/pkg/dl
|
||||||
github.com/NVIDIA/go-nvml/pkg/nvml
|
github.com/NVIDIA/go-nvml/pkg/nvml
|
||||||
# github.com/container-orchestrated-devices/container-device-interface v0.5.4-0.20230111111500-5b3b5d81179a
|
# github.com/container-orchestrated-devices/container-device-interface v0.6.0
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/container-orchestrated-devices/container-device-interface/internal/multierror
|
github.com/container-orchestrated-devices/container-device-interface/internal/multierror
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface/internal/validation
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s
|
||||||
github.com/container-orchestrated-devices/container-device-interface/pkg/cdi
|
github.com/container-orchestrated-devices/container-device-interface/pkg/cdi
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface/pkg/parser
|
||||||
github.com/container-orchestrated-devices/container-device-interface/specs-go
|
github.com/container-orchestrated-devices/container-device-interface/specs-go
|
||||||
# github.com/cpuguy83/go-md2man/v2 v2.0.2
|
# github.com/cpuguy83/go-md2man/v2 v2.0.2
|
||||||
## explicit; go 1.11
|
## explicit; go 1.11
|
||||||
|
Loading…
Reference in New Issue
Block a user