diff --git a/go.mod b/go.mod index 01da470763..819ffec061 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0 github.com/checkpoint-restore/go-criu/v6 v6.3.0 - github.com/container-orchestrated-devices/container-device-interface v0.5.3 + github.com/container-orchestrated-devices/container-device-interface v0.6.0 github.com/containerd/cgroups v1.0.5-0.20220816231112-7083cd60b721 github.com/containerd/containerd v1.7.0-beta.0 github.com/containerd/cri-containerd v1.19.0 @@ -51,7 +51,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b github.com/opencontainers/runc v1.1.4 - github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb + github.com/opencontainers/runtime-spec v1.1.0-rc.1 github.com/opencontainers/runtime-tools v0.9.1-0.20230110161035-a6a073817ab0 github.com/opencontainers/selinux v1.10.2 github.com/prometheus/client_golang v1.14.0 diff --git a/go.sum b/go.sum index 6b99e97638..a68c640397 100644 --- a/go.sum +++ b/go.sum @@ -478,8 +478,8 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= -github.com/container-orchestrated-devices/container-device-interface v0.5.3 h1:4v6FMaa1Pn8SS0IBwgsvCsno8HRXoQvI87Uj1Zu7Tw4= -github.com/container-orchestrated-devices/container-device-interface v0.5.3/go.mod h1:SQohok453ewi9dItvUcO0MrP7K1CEQTxPDNd7OV+nxI= +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/container-storage-interface/spec v1.7.0/go.mod h1:JYuzLqr9VVNoDJl44xp/8fmCOvWPDKzuGTwCoklhuqk= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= @@ -1584,8 +1584,9 @@ github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.m github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb h1:1xSVPOd7/UA+39/hXEGnBJ13p6JFB0E1EvQFlrRDOXI= github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w= +github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/runtime-tools v0.9.1-0.20230110161035-a6a073817ab0 h1:C1EbGtbEhyGJN6XkYHjmRm5lmmGo9UT+vGj0Rn8lF5E= github.com/opencontainers/runtime-tools v0.9.1-0.20230110161035-a6a073817ab0/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI= diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/objectmeta.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/objectmeta.go new file mode 100644 index 0000000000..b8a6487f0e --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/objectmeta.go @@ -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 +} diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/validation.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/validation.go new file mode 100644 index 0000000000..5ad6ce2776 --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s/validation.go @@ -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) +} diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/validate.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/validate.go new file mode 100644 index 0000000000..59c14c2022 --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/validation/validate.go @@ -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) +} diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go index 1055c7df8f..69b69663cb 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go @@ -17,9 +17,11 @@ package cdi import ( + "errors" + "fmt" "strings" - "github.com/pkg/errors" + "github.com/container-orchestrated-devices/container-device-interface/pkg/parser" ) const ( @@ -34,14 +36,14 @@ const ( func UpdateAnnotations(annotations map[string]string, plugin string, deviceID string, devices []string) (map[string]string, error) { key, err := AnnotationKey(plugin, deviceID) if err != nil { - return annotations, errors.Wrap(err, "CDI annotation failed") + return annotations, fmt.Errorf("CDI annotation failed: %w", err) } if _, ok := annotations[key]; ok { - return annotations, errors.Errorf("CDI annotation failed, key %q used", key) + return annotations, fmt.Errorf("CDI annotation failed, key %q used", key) } value, err := AnnotationValue(devices) if err != nil { - return annotations, errors.Wrap(err, "CDI annotation failed") + return annotations, fmt.Errorf("CDI annotation failed: %w", err) } if annotations == nil { @@ -70,7 +72,7 @@ func ParseAnnotations(annotations map[string]string) ([]string, []string, error) } for _, d := range strings.Split(value, ",") { if !IsQualifiedName(d) { - return nil, nil, errors.Errorf("invalid CDI device name %q", d) + return nil, nil, fmt.Errorf("invalid CDI device name %q", d) } devices = append(devices, d) } @@ -98,26 +100,26 @@ func AnnotationKey(pluginName, deviceID string) (string, error) { name := pluginName + "_" + strings.ReplaceAll(deviceID, "/", "_") if len(name) > maxNameLen { - return "", errors.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) { - return "", errors.Errorf("invalid name %q, first '%c' should be alphanumeric", + 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 "", errors.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) { - return "", errors.Errorf("invalid name %q, last '%c' should be alphanumeric", + if c := rune(name[len(name)-1]); !parser.IsAlphaNumeric(c) { + return "", fmt.Errorf("invalid name %q, last '%c' should be alphanumeric", name, c) } diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go index dee854862d..cb495ebb36 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go @@ -17,6 +17,8 @@ package cdi import ( + "errors" + "fmt" "io/fs" "os" "path/filepath" @@ -24,13 +26,10 @@ import ( "strings" "sync" - stderr "errors" - "github.com/container-orchestrated-devices/container-device-interface/internal/multierror" cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" "github.com/fsnotify/fsnotify" oci "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" ) // Option is an option to change some aspect of default CDI behavior. @@ -97,7 +96,7 @@ func (c *Cache) configure(options ...Option) error { for _, o := range options { if err = o(c); err != nil { - return errors.Wrapf(err, "failed to apply cache options") + return fmt.Errorf("failed to apply cache options: %w", err) } } @@ -159,7 +158,7 @@ func (c *Cache) refresh() error { return false case devPrio == oldPrio: devPath, oldPath := devSpec.GetPath(), oldSpec.GetPath() - collectError(errors.Errorf("conflicting device %q (specs %q, %q)", + collectError(fmt.Errorf("conflicting device %q (specs %q, %q)", name, devPath, oldPath), devPath, oldPath) conflicts[name] = struct{}{} } @@ -169,7 +168,7 @@ func (c *Cache) refresh() error { _ = scanSpecDirs(c.specDirs, func(path string, priority int, spec *Spec, err error) error { path = filepath.Clean(path) if err != nil { - collectError(errors.Wrapf(err, "failed to load CDI Spec"), path) + collectError(fmt.Errorf("failed to load CDI Spec %w", err), path) return nil } @@ -219,7 +218,7 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e var unresolved []string if ociSpec == nil { - return devices, errors.Errorf("can't inject devices, nil OCI Spec") + return devices, fmt.Errorf("can't inject devices, nil OCI Spec") } c.Lock() @@ -244,12 +243,12 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e } if unresolved != nil { - return unresolved, errors.Errorf("unresolvable CDI devices %s", + return unresolved, fmt.Errorf("unresolvable CDI devices %s", strings.Join(devices, ", ")) } if err := edits.Apply(ociSpec); err != nil { - return nil, errors.Wrap(err, "failed to inject devices") + return nil, fmt.Errorf("failed to inject devices: %w", err) } return nil, nil @@ -320,7 +319,7 @@ func (c *Cache) RemoveSpec(name string) error { } err = os.Remove(path) - if err != nil && stderr.Is(err, fs.ErrNotExist) { + if err != nil && errors.Is(err, fs.ErrNotExist) { err = nil } @@ -409,7 +408,17 @@ func (c *Cache) GetVendorSpecs(vendor string) []*Spec { // GetSpecErrors returns all errors encountered for the spec during the // last cache refresh. func (c *Cache) GetSpecErrors(spec *Spec) []error { - return c.errors[spec.GetPath()] + var errors []error + + c.Lock() + defer c.Unlock() + + if errs, ok := c.errors[spec.GetPath()]; ok { + errors = make([]error, len(errs)) + copy(errors, errs) + } + + return errors } // GetErrors returns all errors encountered during the last @@ -475,7 +484,7 @@ func (w *watch) setup(dirs []string, dirErrors map[string]error) { w.watcher, err = fsnotify.NewWatcher() if err != nil { for _, dir := range dirs { - dirErrors[dir] = errors.Wrap(err, "failed to create watcher") + dirErrors[dir] = fmt.Errorf("failed to create watcher: %w", err) } return } @@ -558,7 +567,7 @@ func (w *watch) update(dirErrors map[string]error, removed ...string) bool { update = true } else { w.tracked[dir] = false - dirErrors[dir] = errors.Wrap(err, "failed to monitor for changes") + dirErrors[dir] = fmt.Errorf("failed to monitor for changes: %w", err) } } diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go index 9fcecf8497..55c748fc42 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go @@ -17,13 +17,13 @@ package cdi import ( + "errors" + "fmt" "os" "path/filepath" "sort" "strings" - "github.com/pkg/errors" - "github.com/container-orchestrated-devices/container-device-interface/specs-go" oci "github.com/opencontainers/runtime-spec/specs-go" ocigen "github.com/opencontainers/runtime-tools/generate" @@ -140,7 +140,7 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { ensureOCIHooks(spec) spec.Hooks.StartContainer = append(spec.Hooks.StartContainer, h.ToOCI()) default: - return errors.Errorf("unknown hook name %q", h.HookName) + return fmt.Errorf("unknown hook name %q", h.HookName) } } @@ -154,7 +154,7 @@ func (e *ContainerEdits) Validate() error { } if err := ValidateEnv(e.Env); err != nil { - return errors.Wrap(err, "invalid container edits") + return fmt.Errorf("invalid container edits: %w", err) } for _, d := range e.DeviceNodes { if err := (&DeviceNode{d}).Validate(); err != nil { @@ -209,7 +209,7 @@ func (e *ContainerEdits) isEmpty() bool { func ValidateEnv(env []string) error { for _, v := range env { if strings.IndexByte(v, byte('=')) <= 0 { - return errors.Errorf("invalid environment variable %q", v) + return fmt.Errorf("invalid environment variable %q", v) } } return nil @@ -234,11 +234,11 @@ func (d *DeviceNode) Validate() error { return errors.New("invalid (empty) device path") } if _, ok := validTypes[d.Type]; !ok { - return errors.Errorf("device %q: invalid type %q", d.Path, d.Type) + return fmt.Errorf("device %q: invalid type %q", d.Path, d.Type) } for _, bit := range d.Permissions { if bit != 'r' && bit != 'w' && bit != 'm' { - return errors.Errorf("device %q: invalid persmissions %q", + return fmt.Errorf("device %q: invalid permissions %q", d.Path, d.Permissions) } } @@ -253,13 +253,13 @@ type Hook struct { // Validate a hook. func (h *Hook) Validate() error { if _, ok := validHookNames[h.HookName]; !ok { - return errors.Errorf("invalid hook name %q", h.HookName) + return fmt.Errorf("invalid hook name %q", h.HookName) } if h.Path == "" { - return errors.Errorf("invalid hook %q with empty path", h.HookName) + return fmt.Errorf("invalid hook %q with empty path", h.HookName) } if err := ValidateEnv(h.Env); err != nil { - return errors.Wrapf(err, "invalid hook %q", h.HookName) + return fmt.Errorf("invalid hook %q: %w", h.HookName, err) } return nil } @@ -298,7 +298,8 @@ func sortMounts(specgen *ocigen.Generator) { // orderedMounts defines how to sort an OCI Spec Mount slice. // This is the almost the same implementation sa used by CRI-O and Docker, // with a minor tweak for stable sorting order (easier to test): -// https://github.com/moby/moby/blob/17.05.x/daemon/volumes.go#L26 +// +// https://github.com/moby/moby/blob/17.05.x/daemon/volumes.go#L26 type orderedMounts []oci.Mount // Len returns the number of mounts. Used in sorting. diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_unix.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_unix.go index 5d7ebcb557..11a4cfe8c1 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_unix.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_unix.go @@ -20,8 +20,9 @@ package cdi import ( + "fmt" + runc "github.com/opencontainers/runc/libcontainer/devices" - "github.com/pkg/errors" ) // fillMissingInfo fills in missing mandatory attributes from the host device. @@ -36,14 +37,14 @@ func (d *DeviceNode) fillMissingInfo() error { hostDev, err := runc.DeviceFromPath(d.HostPath, "rwm") if err != nil { - return errors.Wrapf(err, "failed to stat CDI host device %q", d.HostPath) + return fmt.Errorf("failed to stat CDI host device %q: %w", d.HostPath, err) } if d.Type == "" { d.Type = string(hostDev.Type) } else { if d.Type != string(hostDev.Type) { - return errors.Errorf("CDI device (%q, %q), host type mismatch (%s, %s)", + return fmt.Errorf("CDI device (%q, %q), host type mismatch (%s, %s)", d.Path, d.HostPath, d.Type, string(hostDev.Type)) } } diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go index 0bb1f531bd..d93ddd0256 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go @@ -17,9 +17,12 @@ 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" - "github.com/pkg/errors" ) // Device represents a CDI device of a Spec. @@ -49,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. @@ -67,12 +70,19 @@ 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 errors.Errorf("invalid device, empty device edits") + return fmt.Errorf("invalid device, empty device edits") } if err := edits.Validate(); err != nil { - return errors.Wrapf(err, "invalid device %q", d.Name) + return fmt.Errorf("invalid device %q: %w", d.Name, err) } return nil } diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go index 9646122906..c5cce0c87c 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go @@ -46,7 +46,6 @@ // "fmt" // "strings" // -// "github.com/pkg/errors" // log "github.com/sirupsen/logrus" // // "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" @@ -58,7 +57,7 @@ // // unresolved, err := cdi.GetRegistry().InjectDevices(spec, devices) // if err != nil { -// return errors.Wrap(err, "CDI device injection failed") +// return fmt.Errorf("CDI device injection failed: %w", err) // } // // log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec)) @@ -90,7 +89,6 @@ // "fmt" // "strings" // -// "github.com/pkg/errors" // log "github.com/sirupsen/logrus" // // "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" @@ -115,7 +113,7 @@ // // unresolved, err := registry.InjectDevices(spec, devices) // if err != nil { -// return errors.Wrap(err, "CDI device injection failed") +// return fmt.Errorf("CDI device injection failed: %w", err) // } // // log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec)) @@ -139,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 @@ -196,10 +194,10 @@ // return fmt.Errorf("failed to generate Spec name: %w", err) // } // -// return registry.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 // files are temporary Spec files with container-specific parametrization. // They are typically created before the associated container is created @@ -241,7 +239,7 @@ // return fmt.Errorf("failed to generate Spec name: %w", err) // } // -// return registry.WriteSpec(spec, specName) +// return registry.SpecDB().WriteSpec(spec, specName) // } // // func removeTransientSpec(ctr Container) error { @@ -249,7 +247,7 @@ // transientID := getSomeSufficientlyUniqueIDForContainer(ctr) // specName := cdi.GenerateNameForTransientSpec(vendor, class, transientID) // -// return registry.RemoveSpec(specName) +// return registry.SpecDB().RemoveSpec(specName) // } // // CDI Spec Validation diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go index ccfab7094c..16e889a7ae 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go @@ -17,28 +17,32 @@ package cdi import ( - "strings" - - "github.com/pkg/errors" + "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 -// "/=". -// 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', '-', '_', '.', ':' +// +// "/=". +// +// 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, @@ -46,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, errors.Errorf("unqualified device %q, missing vendor", device) - } - if class == "" { - return "", "", device, errors.Errorf("unqualified device %q, missing class", device) - } - if name == "" { - return "", "", device, errors.Errorf("unqualified device %q, missing device name", device) - } - - if err := ValidateVendorName(vendor); err != nil { - return "", "", device, errors.Wrapf(err, "invalid device %q", device) - } - if err := ValidateClassName(class); err != nil { - return "", "", device, errors.Wrapf(err, "invalid device %q", device) - } - if err := ValidateDeviceName(name); err != nil { - return "", "", device, errors.Wrapf(err, "invalid device %q", device) - } - - 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 -// "/" +// +// "/" +// // 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. @@ -113,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 errors.Errorf("invalid (empty) vendor name") - } - if !isLetter(rune(vendor[0])) { - return errors.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 errors.Errorf("invalid character '%c' in vendor name %q", - c, vendor) - } - } - if !isAlphaNumeric(rune(vendor[len(vendor)-1])) { - return errors.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 errors.Errorf("invalid (empty) device class") - } - if !isLetter(rune(class[0])) { - return errors.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 errors.Errorf("invalid character '%c' in device class %q", - c, class) - } - } - if !isAlphaNumeric(rune(class[len(class)-1])) { - return errors.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. @@ -168,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 errors.Errorf("invalid (empty) device name") - } - if !isAlphaNumeric(rune(name[0])) { - return errors.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 errors.Errorf("invalid character '%c' in device name %q", - c, name) - } - } - if !isAlphaNumeric(rune(name[len(name)-1])) { - return errors.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) } diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go index 10fab8997e..e13ce60b55 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go @@ -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 diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go index c558b8efed..62693c1bda 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go @@ -18,6 +18,7 @@ package cdi import ( "encoding/json" + "fmt" "io/ioutil" "os" "path/filepath" @@ -25,30 +26,18 @@ import ( "sync" oci "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" "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" ) const ( - // CurrentVersion is the current vesion of the CDI Spec. - CurrentVersion = cdi.CurrentVersion - // defaultSpecExt is the file extension for the default encoding. defaultSpecExt = ".yaml" ) var ( - // Valid CDI Spec versions. - validSpecVersions = map[string]struct{}{ - "0.1.0": {}, - "0.2.0": {}, - "0.3.0": {}, - "0.4.0": {}, - "0.5.0": {}, - } - // Externally set CDI Spec validation function. specValidator func(*cdi.Spec) error validatorLock sync.RWMutex @@ -78,15 +67,15 @@ func ReadSpec(path string, priority int) (*Spec, error) { case os.IsNotExist(err): return nil, err case err != nil: - return nil, errors.Wrapf(err, "failed to read CDI Spec %q", path) + return nil, fmt.Errorf("failed to read CDI Spec %q: %w", path, err) } raw, err := ParseSpec(data) if err != nil { - return nil, errors.Wrapf(err, "failed to parse CDI Spec %q", path) + return nil, fmt.Errorf("failed to parse CDI Spec %q: %w", path, err) } if raw == nil { - return nil, errors.Errorf("failed to parse CDI Spec %q, no Spec data", path) + return nil, fmt.Errorf("failed to parse CDI Spec %q, no Spec data", path) } spec, err := newSpec(raw, path, priority) @@ -120,7 +109,7 @@ func newSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) { spec.vendor, spec.class = ParseQualifier(spec.Kind) if spec.devices, err = spec.validate(); err != nil { - return nil, errors.Wrap(err, "invalid CDI Spec") + return nil, fmt.Errorf("invalid CDI Spec: %w", err) } return spec, nil @@ -143,34 +132,35 @@ 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) } if err != nil { - return errors.Wrap(err, "failed to marshal Spec file") + return fmt.Errorf("failed to marshal Spec file: %w", err) } dir = filepath.Dir(s.path) err = os.MkdirAll(dir, 0o755) if err != nil { - return errors.Wrap(err, "failed to create Spec dir") + return fmt.Errorf("failed to create Spec dir: %w", err) } tmp, err = os.CreateTemp(dir, "spec.*.tmp") if err != nil { - return errors.Wrap(err, "failed to create Spec file") + return fmt.Errorf("failed to create Spec file: %w", err) } _, err = tmp.Write(data) tmp.Close() if err != nil { - return errors.Wrap(err, "failed to write Spec file") + return fmt.Errorf("failed to write Spec file: %w", err) } err = renameIn(dir, filepath.Base(tmp.Name()), filepath.Base(s.path), overwrite) if err != nil { os.Remove(tmp.Name()) - err = errors.Wrap(err, "failed to write Spec file") + err = fmt.Errorf("failed to write Spec file: %w", err) } return err @@ -216,12 +206,24 @@ func (s *Spec) validate() (map[string]*Device, error) { if err := validateVersion(s.Version); err != nil { return nil, err } + + minVersion, err := MinimumRequiredVersion(s.Spec) + if err != nil { + 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) + } + if err := ValidateVendorName(s.vendor); err != nil { return nil, err } 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 } @@ -230,10 +232,10 @@ func (s *Spec) validate() (map[string]*Device, error) { for _, d := range s.Devices { dev, err := newDevice(s, d) if err != nil { - return nil, errors.Wrapf(err, "failed add device %q", d.Name) + return nil, fmt.Errorf("failed add device %q: %w", d.Name, err) } if _, conflict := devices[d.Name]; conflict { - return nil, errors.Errorf("invalid spec, multiple device %q", d.Name) + return nil, fmt.Errorf("invalid spec, multiple device %q", d.Name) } devices[d.Name] = dev } @@ -243,8 +245,8 @@ func (s *Spec) validate() (map[string]*Device, error) { // validateVersion checks whether the specified spec version is supported. func validateVersion(version string) error { - if _, ok := validSpecVersions[version]; !ok { - return errors.Errorf("invalid version %q", version) + if !validSpecVersions.isValidVersion(version) { + return fmt.Errorf("invalid version %q", version) } return nil @@ -255,7 +257,7 @@ func ParseSpec(data []byte) (*cdi.Spec, error) { var raw *cdi.Spec err := yaml.UnmarshalStrict(data, &raw) if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal CDI Spec") + return nil, fmt.Errorf("failed to unmarshal CDI Spec: %w", err) } return raw, nil } @@ -279,7 +281,7 @@ func validateSpec(raw *cdi.Spec) error { } err := specValidator(raw) if err != nil { - return errors.Wrap(err, "Spec validation failed") + return fmt.Errorf("Spec validation failed: %w", err) } return nil } @@ -309,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. // @@ -329,7 +331,7 @@ func GenerateTransientSpecName(vendor, class, transientID string) string { func GenerateNameForSpec(raw *cdi.Spec) (string, error) { vendor, class := ParseQualifier(raw.Kind) if vendor == "" { - return "", errors.Errorf("invalid vendor/class %q in Spec", raw.Kind) + return "", fmt.Errorf("invalid vendor/class %q in Spec", raw.Kind) } return GenerateSpecName(vendor, class), nil @@ -343,7 +345,7 @@ func GenerateNameForSpec(raw *cdi.Spec) (string, error) { func GenerateNameForTransientSpec(raw *cdi.Spec, transientID string) (string, error) { vendor, class := ParseQualifier(raw.Kind) if vendor == "" { - return "", errors.Errorf("invalid vendor/class %q in Spec", raw.Kind) + return "", fmt.Errorf("invalid vendor/class %q in Spec", raw.Kind) } return GenerateTransientSpecName(vendor, class, transientID), nil diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_linux.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_linux.go index cca825c60d..9ad2739256 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_linux.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_linux.go @@ -17,9 +17,9 @@ package cdi import ( + "fmt" "os" - "github.com/pkg/errors" "golang.org/x/sys/unix" ) @@ -30,7 +30,7 @@ func renameIn(dir, src, dst string, overwrite bool) error { dirf, err := os.Open(dir) if err != nil { - return errors.Wrap(err, "rename failed") + return fmt.Errorf("rename failed: %w", err) } defer dirf.Close() @@ -41,7 +41,7 @@ func renameIn(dir, src, dst string, overwrite bool) error { dirFd := int(dirf.Fd()) err = unix.Renameat2(dirFd, src, dirFd, dst, flags) if err != nil { - return errors.Wrap(err, "rename failed") + return fmt.Errorf("rename failed: %w", err) } return nil diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/version.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/version.go new file mode 100644 index 0000000000..22534d9204 --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/version.go @@ -0,0 +1,188 @@ +/* + 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 cdi + +import ( + "strings" + + "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" +) + +const ( + // CurrentVersion is the current version of the CDI Spec. + CurrentVersion = cdi.CurrentVersion + + // vCurrent is the current version as a semver-comparable type + vCurrent version = "v" + CurrentVersion + + // These represent the released versions of the CDI specification + v010 version = "v0.1.0" + v020 version = "v0.2.0" + 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 +) + +// validSpecVersions stores a map of spec versions to functions to check the required versions. +// Adding new fields / spec versions requires that a `requiredFunc` be implemented and +// this map be updated. +var validSpecVersions = requiredVersionMap{ + v010: nil, + v020: nil, + v030: nil, + v040: requiresV040, + v050: requiresV050, + v060: requiresV060, +} + +// 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 +} + +// version represents a semantic version string +type version string + +// newVersion creates a version that can be used for semantic version comparisons. +func newVersion(v string) version { + return version("v" + strings.TrimPrefix(v, "v")) +} + +// String returns the string representation of the version. +// This trims a leading v if present. +func (v version) String() string { + return strings.TrimPrefix(string(v), "v") +} + +// IsGreaterThan checks with a version is greater than the specified version. +func (v version) IsGreaterThan(o version) bool { + return semver.Compare(string(v), string(o)) > 0 +} + +// IsLatest checks whether the version is the latest supported version +func (v version) IsLatest() bool { + return v == vCurrent +} + +type requiredFunc func(*cdi.Spec) bool + +type requiredVersionMap map[version]requiredFunc + +// isValidVersion checks whether the specified version is valid. +// A version is valid if it is contained in the required version map. +func (r requiredVersionMap) isValidVersion(specVersion string) bool { + _, ok := validSpecVersions[newVersion(specVersion)] + + return ok +} + +// requiredVersion returns the minimum version required for the given spec +func (r requiredVersionMap) requiredVersion(spec *cdi.Spec) version { + minVersion := vEarliest + + for v, isRequired := range validSpecVersions { + if isRequired == nil { + continue + } + if isRequired(spec) && v.IsGreaterThan(minVersion) { + minVersion = v + } + // If we have already detected the latest version then no later version could be detected + if minVersion.IsLatest() { + break + } + } + + 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 && !parser.IsLetter(rune(d.Name[0])) { + return true + } + edits = append(edits, &d.ContainerEdits) + } + + edits = append(edits, &spec.ContainerEdits) + for _, e := range edits { + for _, dn := range e.DeviceNodes { + // The HostPath field was added in v0.5.0 + if dn.HostPath != "" { + return true + } + } + } + return false +} + +// requiresV040 returns true if the spec uses v0.4.0 features +func requiresV040(spec *cdi.Spec) bool { + var edits []*cdi.ContainerEdits + + for _, d := range spec.Devices { + edits = append(edits, &d.ContainerEdits) + } + + edits = append(edits, &spec.ContainerEdits) + for _, e := range edits { + for _, m := range e.Mounts { + // The Type field was added in v0.4.0 + if m.Type != "" { + return true + } + } + } + return false +} diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/parser/parser.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/parser/parser.go new file mode 100644 index 0000000000..5325989541 --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/parser/parser.go @@ -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 +// +// "/=". +// +// 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 +// +// "/" +// +// 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) +} diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go index 3fa2e814bd..4043b858f2 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go @@ -3,21 +3,24 @@ package specs import "os" // 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 type Spec struct { Version string `json:"cdiVersion"` Kind string `json:"kind"` - - Devices []Device `json:"devices"` - ContainerEdits ContainerEdits `json:"containerEdits,omitempty"` + // Annotations add meta information per CDI spec. Note these are CDI-specific and do not affect container metadata. + Annotations map[string]string `json:"annotations,omitempty"` + Devices []Device `json:"devices"` + ContainerEdits ContainerEdits `json:"containerEdits,omitempty"` } // Device is a "Device" a container runtime can add to a container type Device struct { - Name string `json:"name"` - ContainerEdits ContainerEdits `json:"containerEdits"` + Name string `json:"name"` + // 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. diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go index 14a0f6a0ba..d709ecbc73 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go @@ -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) } -// 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 { return ApplyEditsToOCISpec(config, &cdi.ContainerEdits) } diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index 7e9122103c..5b4f691c70 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -319,6 +319,10 @@ type LinuxMemory struct { DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"` // Enables hierarchical memory accounting UseHierarchy *bool `json:"useHierarchy,omitempty"` + // CheckBeforeUpdate enables checking if a new memory limit is lower + // than the current usage during update, and if so, rejecting the new + // limit. + CheckBeforeUpdate *bool `json:"checkBeforeUpdate,omitempty"` } // LinuxCPU for Linux cgroup 'cpu' resource management @@ -327,6 +331,9 @@ type LinuxCPU struct { Shares *uint64 `json:"shares,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. Quota *int64 `json:"quota,omitempty"` + // CPU hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst in a + // given period. + Burst *uint64 `json:"burst,omitempty"` // CPU period to be used for hardcapping (in usecs). Period *uint64 `json:"period,omitempty"` // How much time realtime scheduling may use (in usecs). @@ -645,6 +652,10 @@ const ( // LinuxSeccompFlagSpecAllow can be used to disable Speculative Store // Bypass mitigation. (since Linux 4.17) LinuxSeccompFlagSpecAllow LinuxSeccompFlag = "SECCOMP_FILTER_FLAG_SPEC_ALLOW" + + // LinuxSeccompFlagWaitKillableRecv can be used to switch to the wait + // killable semantics. (since Linux 5.19) + LinuxSeccompFlagWaitKillableRecv LinuxSeccompFlag = "SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV" ) // Additional architectures permitted to be used for system calls diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go index 596af0c2fd..8ae4227b9b 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go @@ -6,12 +6,12 @@ const ( // VersionMajor is for an API incompatible changes VersionMajor = 1 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 0 + VersionMinor = 1 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 2 + VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. - VersionDev = "-dev" + VersionDev = "-rc.1" ) // Version is the specification version that the package types support. diff --git a/vendor/modules.txt b/vendor/modules.txt index 74395273b5..a3919bee57 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -503,10 +503,13 @@ github.com/cockroachdb/apd/v2 # github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be ## explicit github.com/common-nighthawk/go-figure -# github.com/container-orchestrated-devices/container-device-interface v0.5.3 +# github.com/container-orchestrated-devices/container-device-interface v0.6.0 ## explicit; go 1.17 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/parser github.com/container-orchestrated-devices/container-device-interface/specs-go # github.com/containerd/cgroups v1.0.5-0.20220816231112-7083cd60b721 ## explicit; go 1.17 @@ -1783,7 +1786,7 @@ github.com/opencontainers/runc/libcontainer/devices github.com/opencontainers/runc/libcontainer/user github.com/opencontainers/runc/libcontainer/userns github.com/opencontainers/runc/libcontainer/utils -# github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb +# github.com/opencontainers/runtime-spec v1.1.0-rc.1 ## explicit github.com/opencontainers/runtime-spec/specs-go # github.com/opencontainers/runtime-tools v0.9.1-0.20230110161035-a6a073817ab0