update CDI version to v0.5.4
Signed-off-by: Ed Bartosh <eduard.bartosh@intel.com>
This commit is contained in:
		| @@ -17,9 +17,9 @@ | ||||
| package cdi | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -34,14 +34,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 +70,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,11 +98,11 @@ 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", | ||||
| 		return "", fmt.Errorf("invalid name %q, first '%c' should be alphanumeric", | ||||
| 			name, c) | ||||
| 	} | ||||
| 	if len(name) > 2 { | ||||
| @@ -111,13 +111,13 @@ func AnnotationKey(pluginName, deviceID string) (string, error) { | ||||
| 			case isAlphaNumeric(c): | ||||
| 			case c == '_' || c == '-' || c == '.': | ||||
| 			default: | ||||
| 				return "", errors.Errorf("invalid name %q, invalid charcter '%c'", | ||||
| 				return "", fmt.Errorf("invalid name %q, invalid charcter '%c'", | ||||
| 					name, c) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if c := rune(name[len(name)-1]); !isAlphaNumeric(c) { | ||||
| 		return "", errors.Errorf("invalid name %q, last '%c' should be alphanumeric", | ||||
| 		return "", fmt.Errorf("invalid name %q, last '%c' should be alphanumeric", | ||||
| 			name, c) | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										106
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										106
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -17,16 +17,19 @@ | ||||
| package cdi | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"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" | ||||
| 	"github.com/hashicorp/go-multierror" | ||||
| 	oci "github.com/opencontainers/runtime-spec/specs-go" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| // Option is an option to change some aspect of default CDI behavior. | ||||
| @@ -93,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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -123,8 +126,8 @@ func (c *Cache) Refresh() error { | ||||
|  | ||||
| 	// collect and return cached errors, much like refresh() does it | ||||
| 	var result error | ||||
| 	for _, err := range c.errors { | ||||
| 		result = multierror.Append(result, err...) | ||||
| 	for _, errors := range c.errors { | ||||
| 		result = multierror.Append(result, errors...) | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
| @@ -155,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{}{} | ||||
| 		} | ||||
| @@ -165,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 | ||||
| 		} | ||||
|  | ||||
| @@ -194,11 +197,7 @@ func (c *Cache) refresh() error { | ||||
| 	c.devices = devices | ||||
| 	c.errors = specErrors | ||||
|  | ||||
| 	if len(result) > 0 { | ||||
| 		return multierror.Append(nil, result...) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| 	return multierror.New(result...) | ||||
| } | ||||
|  | ||||
| // RefreshIfRequired triggers a refresh if necessary. | ||||
| @@ -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,22 +243,33 @@ 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 | ||||
| } | ||||
|  | ||||
| // WriteSpec writes a Spec file with the given content. Priority is used | ||||
| // as an index into the list of Spec directories to pick a directory for | ||||
| // the file, adjusting for any under- or overflows. If name has a "json" | ||||
| // or "yaml" extension it choses the encoding. Otherwise JSON encoding | ||||
| // is used with a "json" extension. | ||||
| // highestPrioritySpecDir returns the Spec directory with highest priority | ||||
| // and its priority. | ||||
| func (c *Cache) highestPrioritySpecDir() (string, int) { | ||||
| 	if len(c.specDirs) == 0 { | ||||
| 		return "", -1 | ||||
| 	} | ||||
|  | ||||
| 	prio := len(c.specDirs) - 1 | ||||
| 	dir := c.specDirs[prio] | ||||
|  | ||||
| 	return dir, prio | ||||
| } | ||||
|  | ||||
| // WriteSpec writes a Spec file with the given content into the highest | ||||
| // priority Spec directory. If name has a "json" or "yaml" extension it | ||||
| // choses the encoding. Otherwise the default YAML encoding is used. | ||||
| func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error { | ||||
| 	var ( | ||||
| 		specDir string | ||||
| @@ -269,23 +279,51 @@ func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error { | ||||
| 		err     error | ||||
| 	) | ||||
|  | ||||
| 	if len(c.specDirs) == 0 { | ||||
| 	specDir, prio = c.highestPrioritySpecDir() | ||||
| 	if specDir == "" { | ||||
| 		return errors.New("no Spec directories to write to") | ||||
| 	} | ||||
|  | ||||
| 	prio = len(c.specDirs) - 1 | ||||
| 	specDir = c.specDirs[prio] | ||||
| 	path = filepath.Join(specDir, name) | ||||
| 	if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" { | ||||
| 		path += ".json" | ||||
| 		path += defaultSpecExt | ||||
| 	} | ||||
|  | ||||
| 	spec, err = NewSpec(raw, path, prio) | ||||
| 	spec, err = newSpec(raw, path, prio) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return spec.Write(true) | ||||
| 	return spec.write(true) | ||||
| } | ||||
|  | ||||
| // RemoveSpec removes a Spec with the given name from the highest | ||||
| // priority Spec directory. This function can be used to remove a | ||||
| // Spec previously written by WriteSpec(). If the file exists and | ||||
| // its removal fails RemoveSpec returns an error. | ||||
| func (c *Cache) RemoveSpec(name string) error { | ||||
| 	var ( | ||||
| 		specDir string | ||||
| 		path    string | ||||
| 		err     error | ||||
| 	) | ||||
|  | ||||
| 	specDir, _ = c.highestPrioritySpecDir() | ||||
| 	if specDir == "" { | ||||
| 		return errors.New("no Spec directories to remove from") | ||||
| 	} | ||||
|  | ||||
| 	path = filepath.Join(specDir, name) | ||||
| 	if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" { | ||||
| 		path += defaultSpecExt | ||||
| 	} | ||||
|  | ||||
| 	err = os.Remove(path) | ||||
| 	if err != nil && errors.Is(err, fs.ErrNotExist) { | ||||
| 		err = nil | ||||
| 	} | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // GetDevice returns the cached device for the given qualified name. | ||||
| @@ -370,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 | ||||
| @@ -436,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 | ||||
| 	} | ||||
| @@ -519,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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										26
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache_test_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache_test_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| //go:build !windows | ||||
| // +build !windows | ||||
|  | ||||
| /* | ||||
|    Copyright © 2021 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 "syscall" | ||||
|  | ||||
| func osSync() { | ||||
| 	syscall.Sync() | ||||
| } | ||||
							
								
								
									
										22
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache_test_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache_test_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| //go:build windows | ||||
| // +build windows | ||||
|  | ||||
| /* | ||||
|    Copyright © 2021 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 | ||||
|  | ||||
| func osSync() {} | ||||
| @@ -17,18 +17,16 @@ | ||||
| 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" | ||||
|  | ||||
| 	runc "github.com/opencontainers/runc/libcontainer/devices" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -142,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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -156,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 { | ||||
| @@ -211,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 | ||||
| @@ -236,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 persmissions %q", | ||||
| 				d.Path, d.Permissions) | ||||
| 		} | ||||
| 	} | ||||
| @@ -255,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 | ||||
| } | ||||
| @@ -289,37 +287,6 @@ func ensureOCIHooks(spec *oci.Spec) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // fillMissingInfo fills in missing mandatory attributes from the host device. | ||||
| func (d *DeviceNode) fillMissingInfo() error { | ||||
| 	if d.HostPath == "" { | ||||
| 		d.HostPath = d.Path | ||||
| 	} | ||||
|  | ||||
| 	if d.Type != "" && (d.Major != 0 || d.Type == "p") { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	hostDev, err := runc.DeviceFromPath(d.HostPath, "rwm") | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "failed to stat CDI host device %q", d.HostPath) | ||||
| 	} | ||||
|  | ||||
| 	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)", | ||||
| 				d.Path, d.HostPath, d.Type, string(hostDev.Type)) | ||||
| 		} | ||||
| 	} | ||||
| 	if d.Major == 0 && d.Type != "p" { | ||||
| 		d.Major = hostDev.Major | ||||
| 		d.Minor = hostDev.Minor | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // sortMounts sorts the mounts in the given OCI Spec. | ||||
| func sortMounts(specgen *ocigen.Generator) { | ||||
| 	mounts := specgen.Mounts() | ||||
| @@ -331,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. | ||||
|   | ||||
							
								
								
									
										57
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| //go:build !windows | ||||
| // +build !windows | ||||
|  | ||||
| /* | ||||
|    Copyright © 2021 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 ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	runc "github.com/opencontainers/runc/libcontainer/devices" | ||||
| ) | ||||
|  | ||||
| // fillMissingInfo fills in missing mandatory attributes from the host device. | ||||
| func (d *DeviceNode) fillMissingInfo() error { | ||||
| 	if d.HostPath == "" { | ||||
| 		d.HostPath = d.Path | ||||
| 	} | ||||
|  | ||||
| 	if d.Type != "" && (d.Major != 0 || d.Type == "p") { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	hostDev, err := runc.DeviceFromPath(d.HostPath, "rwm") | ||||
| 	if err != nil { | ||||
| 		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 fmt.Errorf("CDI device (%q, %q), host type mismatch (%s, %s)", | ||||
| 				d.Path, d.HostPath, d.Type, string(hostDev.Type)) | ||||
| 		} | ||||
| 	} | ||||
| 	if d.Major == 0 && d.Type != "p" { | ||||
| 		d.Major = hostDev.Major | ||||
| 		d.Minor = hostDev.Minor | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										27
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| //go:build windows | ||||
| // +build windows | ||||
|  | ||||
| /* | ||||
|    Copyright © 2021 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 "fmt" | ||||
|  | ||||
| // fillMissingInfo fills in missing mandatory attributes from the host device. | ||||
| func (d *DeviceNode) fillMissingInfo() error { | ||||
| 	return fmt.Errorf("unimplemented") | ||||
| } | ||||
| @@ -17,9 +17,10 @@ | ||||
| package cdi | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	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. | ||||
| @@ -69,10 +70,10 @@ func (d *Device) validate() error { | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										125
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										125
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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)) | ||||
| @@ -124,10 +122,15 @@ | ||||
| // | ||||
| // Generated Spec Files, Multiple Directories, Device Precedence | ||||
| // | ||||
| // There are systems where the set of available or usable CDI devices | ||||
| // changes dynamically and this needs to be reflected in the CDI Specs. | ||||
| // This is done by dynamically regenerating CDI Spec files which are | ||||
| // affected by these changes. | ||||
| // It is often necessary to generate Spec files dynamically. On some | ||||
| // systems the available or usable set of CDI devices might change | ||||
| // dynamically which then needs to be reflected in CDI Specs. For | ||||
| // some device classes it makes sense to enumerate the available | ||||
| // devices at every boot and generate Spec file entries for each | ||||
| // device found. Some CDI devices might need special client- or | ||||
| // request-specific configuration which can only be fulfilled by | ||||
| // dynamically generated client-specific entries in transient Spec | ||||
| // files. | ||||
| // | ||||
| // CDI can collect Spec files from multiple directories. Spec files are | ||||
| // automatically assigned priorities according to which directory they | ||||
| @@ -141,7 +144,111 @@ | ||||
| // separating dynamically generated CDI Spec files from static ones. | ||||
| // The default directories are '/etc/cdi' and '/var/run/cdi'. By putting | ||||
| // dynamically generated Spec files under '/var/run/cdi', those take | ||||
| // precedence over static ones in '/etc/cdi'. | ||||
| // precedence over static ones in '/etc/cdi'. With this scheme, static | ||||
| // Spec files, typically installed by distro-specific packages, go into | ||||
| // '/etc/cdi' while all the dynamically generated Spec files, transient | ||||
| // or other, go into '/var/run/cdi'. | ||||
| // | ||||
| // Spec File Generation | ||||
| // | ||||
| // CDI offers two functions for writing and removing dynamically generated | ||||
| // Specs from CDI Spec directories. These functions, WriteSpec() and | ||||
| // RemoveSpec() implicitly follow the principle of separating dynamic Specs | ||||
| // from the rest and therefore always write to and remove Specs from the | ||||
| // last configured directory. | ||||
| // | ||||
| // Corresponding functions are also provided for generating names for Spec | ||||
| // files. These functions follow a simple naming convention to ensure that | ||||
| // multiple entities generating Spec files simultaneously on the same host | ||||
| // do not end up using conflicting Spec file names. GenerateSpecName(), | ||||
| // GenerateNameForSpec(), GenerateTransientSpecName(), and | ||||
| // GenerateTransientNameForSpec() all generate names which can be passed | ||||
| // as such to WriteSpec() and subsequently to RemoveSpec(). | ||||
| // | ||||
| // Generating a Spec file for a vendor/device class can be done with a | ||||
| // code snippet similar to the following: | ||||
| // | ||||
| // import ( | ||||
| //     "fmt" | ||||
| //     ... | ||||
| //     "github.com/container-orchestrated-devices/container-device-interface/specs-go" | ||||
| //     "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" | ||||
| // ) | ||||
| // | ||||
| // func generateDeviceSpecs() error { | ||||
| //     registry := cdi.GetRegistry() | ||||
| //     spec := &specs.Spec{ | ||||
| //         Version: specs.CurrentVersion, | ||||
| //         Kind:    vendor+"/"+class, | ||||
| //     } | ||||
| // | ||||
| //     for _, dev := range enumerateDevices() { | ||||
| //         spec.Devices = append(spec.Devices, specs.Device{ | ||||
| //             Name: dev.Name, | ||||
| //             ContainerEdits: getContainerEditsForDevice(dev), | ||||
| //         }) | ||||
| //     } | ||||
| // | ||||
| //     specName, err := cdi.GenerateNameForSpec(spec) | ||||
| //     if err != nil { | ||||
| //         return fmt.Errorf("failed to generate Spec name: %w", err) | ||||
| //     } | ||||
| // | ||||
| //     return registry.SpecDB().WriteSpec(spec, specName) | ||||
| // } | ||||
| // | ||||
| // Similary, 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 | ||||
| // and removed once that container is removed. | ||||
| // | ||||
| // import ( | ||||
| //     "fmt" | ||||
| //     ... | ||||
| //     "github.com/container-orchestrated-devices/container-device-interface/specs-go" | ||||
| //     "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" | ||||
| // ) | ||||
| // | ||||
| // func generateTransientSpec(ctr Container) error { | ||||
| //     registry := cdi.GetRegistry() | ||||
| //     devices := getContainerDevs(ctr, vendor, class) | ||||
| //     spec := &specs.Spec{ | ||||
| //         Version: specs.CurrentVersion, | ||||
| //         Kind:    vendor+"/"+class, | ||||
| //     } | ||||
| // | ||||
| //     for _, dev := range devices { | ||||
| //         spec.Devices = append(spec.Devices, specs.Device{ | ||||
| //             // the generated name needs to be unique within the | ||||
| //             // vendor/class domain on the host/node. | ||||
| //             Name: generateUniqueDevName(dev, ctr), | ||||
| //             ContainerEdits: getEditsForContainer(dev), | ||||
| //         }) | ||||
| //     } | ||||
| // | ||||
| //     // transientID is expected to guarantee that the Spec file name | ||||
| //     // generated using <vendor, class, transientID> is unique within | ||||
| //     // the host/node. If more than one device is allocated with the | ||||
| //     // same vendor/class domain, either all generated Spec entries | ||||
| //     // should go to a single Spec file (like in this sample snippet), | ||||
| //     // or transientID should be unique for each generated Spec file. | ||||
| //     transientID := getSomeSufficientlyUniqueIDForContainer(ctr) | ||||
| //     specName, err := cdi.GenerateNameForTransientSpec(vendor, class, transientID) | ||||
| //     if err != nil { | ||||
| //         return fmt.Errorf("failed to generate Spec name: %w", err) | ||||
| //     } | ||||
| // | ||||
| //     return registry.SpecDB().WriteSpec(spec, specName) | ||||
| // } | ||||
| // | ||||
| // func removeTransientSpec(ctr Container) error { | ||||
| //     registry := cdi.GetRegistry() | ||||
| //     transientID := getSomeSufficientlyUniqueIDForContainer(ctr) | ||||
| //     specName := cdi.GenerateNameForTransientSpec(vendor, class, transientID) | ||||
| // | ||||
| //     return registry.SpecDB().RemoveSpec(specName) | ||||
| // } | ||||
| // | ||||
| // CDI Spec Validation | ||||
| // | ||||
|   | ||||
| @@ -17,9 +17,8 @@ | ||||
| package cdi | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| // QualifiedName returns the qualified name for a device. | ||||
| @@ -50,23 +49,23 @@ 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) | ||||
| 		return "", "", device, fmt.Errorf("unqualified device %q, missing vendor", device) | ||||
| 	} | ||||
| 	if class == "" { | ||||
| 		return "", "", device, errors.Errorf("unqualified device %q, missing class", device) | ||||
| 		return "", "", device, fmt.Errorf("unqualified device %q, missing class", device) | ||||
| 	} | ||||
| 	if name == "" { | ||||
| 		return "", "", device, errors.Errorf("unqualified device %q, missing device name", device) | ||||
| 		return "", "", device, fmt.Errorf("unqualified device %q, missing device name", device) | ||||
| 	} | ||||
|  | ||||
| 	if err := ValidateVendorName(vendor); err != nil { | ||||
| 		return "", "", device, errors.Wrapf(err, "invalid device %q", device) | ||||
| 		return "", "", device, fmt.Errorf("invalid device %q: %w", device, err) | ||||
| 	} | ||||
| 	if err := ValidateClassName(class); err != nil { | ||||
| 		return "", "", device, errors.Wrapf(err, "invalid device %q", device) | ||||
| 		return "", "", device, fmt.Errorf("invalid device %q: %w", device, err) | ||||
| 	} | ||||
| 	if err := ValidateDeviceName(name); err != nil { | ||||
| 		return "", "", device, errors.Wrapf(err, "invalid device %q", device) | ||||
| 		return "", "", device, fmt.Errorf("invalid device %q: %w", device, err) | ||||
| 	} | ||||
|  | ||||
| 	return vendor, class, name, nil | ||||
| @@ -115,22 +114,22 @@ func ParseQualifier(kind string) (string, string) { | ||||
| //   - underscore, dash, and dot ('_', '-', and '.') | ||||
| func ValidateVendorName(vendor string) error { | ||||
| 	if vendor == "" { | ||||
| 		return errors.Errorf("invalid (empty) vendor name") | ||||
| 		return fmt.Errorf("invalid (empty) vendor name") | ||||
| 	} | ||||
| 	if !isLetter(rune(vendor[0])) { | ||||
| 		return errors.Errorf("invalid vendor %q, should start with letter", vendor) | ||||
| 		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 errors.Errorf("invalid character '%c' in vendor name %q", | ||||
| 			return fmt.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 fmt.Errorf("invalid vendor %q, should end with a letter or digit", vendor) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @@ -143,22 +142,22 @@ func ValidateVendorName(vendor string) error { | ||||
| //   - underscore and dash ('_', '-') | ||||
| func ValidateClassName(class string) error { | ||||
| 	if class == "" { | ||||
| 		return errors.Errorf("invalid (empty) device class") | ||||
| 		return fmt.Errorf("invalid (empty) device class") | ||||
| 	} | ||||
| 	if !isLetter(rune(class[0])) { | ||||
| 		return errors.Errorf("invalid class %q, should start with letter", class) | ||||
| 		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 errors.Errorf("invalid character '%c' in device class %q", | ||||
| 			return fmt.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 fmt.Errorf("invalid class %q, should end with a letter or digit", class) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -170,10 +169,10 @@ func ValidateClassName(class string) error { | ||||
| //   - underscore, dash, dot, colon ('_', '-', '.', ':') | ||||
| func ValidateDeviceName(name string) error { | ||||
| 	if name == "" { | ||||
| 		return errors.Errorf("invalid (empty) device name") | ||||
| 		return fmt.Errorf("invalid (empty) device name") | ||||
| 	} | ||||
| 	if !isAlphaNumeric(rune(name[0])) { | ||||
| 		return errors.Errorf("invalid class %q, should start with a letter or digit", name) | ||||
| 		return fmt.Errorf("invalid class %q, should start with a letter or digit", name) | ||||
| 	} | ||||
| 	if len(name) == 1 { | ||||
| 		return nil | ||||
| @@ -183,12 +182,12 @@ func ValidateDeviceName(name string) error { | ||||
| 		case isAlphaNumeric(c): | ||||
| 		case c == '_' || c == '-' || c == '.' || c == ':': | ||||
| 		default: | ||||
| 			return errors.Errorf("invalid character '%c' in device name %q", | ||||
| 			return fmt.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 fmt.Errorf("invalid name %q, should end with a letter or digit", name) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -107,6 +107,7 @@ type RegistrySpecDB interface { | ||||
| 	GetVendorSpecs(vendor string) []*Spec | ||||
| 	GetSpecErrors(*Spec) []error | ||||
| 	WriteSpec(raw *cdi.Spec, name string) error | ||||
| 	RemoveSpec(name string) error | ||||
| } | ||||
|  | ||||
| type registry struct { | ||||
|   | ||||
							
								
								
									
										146
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										146
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -18,29 +18,28 @@ package cdi | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	oci "github.com/opencontainers/runtime-spec/specs-go" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"sigs.k8s.io/yaml" | ||||
|  | ||||
| 	cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// Valid CDI Spec versions. | ||||
| 	validSpecVersions = map[string]struct{}{ | ||||
| 		"0.1.0": {}, | ||||
| 		"0.2.0": {}, | ||||
| 		"0.3.0": {}, | ||||
| 		"0.4.0": {}, | ||||
| 		"0.5.0": {}, | ||||
| 	} | ||||
| const ( | ||||
| 	// defaultSpecExt is the file extension for the default encoding. | ||||
| 	defaultSpecExt = ".yaml" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// Externally set CDI Spec validation function. | ||||
| 	specValidator func(*cdi.Spec) error | ||||
| 	validatorLock sync.RWMutex | ||||
| ) | ||||
|  | ||||
| // Spec represents a single CDI Spec. It is usually loaded from a | ||||
| @@ -67,18 +66,18 @@ 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) | ||||
| 	spec, err := newSpec(raw, path, priority) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -86,11 +85,11 @@ func ReadSpec(path string, priority int) (*Spec, error) { | ||||
| 	return spec, nil | ||||
| } | ||||
|  | ||||
| // NewSpec creates a new Spec from the given CDI Spec data. The | ||||
| // newSpec creates a new Spec from the given CDI Spec data. The | ||||
| // Spec is marked as loaded from the given path with the given | ||||
| // priority. If Spec data validation fails NewSpec returns a nil | ||||
| // priority. If Spec data validation fails newSpec returns a nil | ||||
| // Spec and an error. | ||||
| func NewSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) { | ||||
| func newSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) { | ||||
| 	err := validateSpec(raw) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -102,18 +101,22 @@ func NewSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) { | ||||
| 		priority: priority, | ||||
| 	} | ||||
|  | ||||
| 	if ext := filepath.Ext(spec.path); ext != ".yaml" && ext != ".json" { | ||||
| 		spec.path += defaultSpecExt | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| // Write the CDI Spec to the file associated with it during instantiation | ||||
| // by NewSpec() or ReadSpec(). | ||||
| func (s *Spec) Write(overwrite bool) error { | ||||
| // by newSpec() or ReadSpec(). | ||||
| func (s *Spec) write(overwrite bool) error { | ||||
| 	var ( | ||||
| 		data []byte | ||||
| 		dir  string | ||||
| @@ -132,30 +135,30 @@ func (s *Spec) Write(overwrite bool) error { | ||||
| 		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 | ||||
| @@ -201,6 +204,15 @@ 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 minumum 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 | ||||
| 	} | ||||
| @@ -215,10 +227,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 | ||||
| 	} | ||||
| @@ -228,8 +240,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 | ||||
| @@ -240,26 +252,96 @@ 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 | ||||
| } | ||||
|  | ||||
| // SetSpecValidator sets a CDI Spec validator function. This function | ||||
| // is used for extra CDI Spec content validation whenever a Spec file | ||||
| // loaded (using ReadSpec() or NewSpec()) or written (Spec.Write()). | ||||
| // loaded (using ReadSpec() or written (using WriteSpec()). | ||||
| func SetSpecValidator(fn func(*cdi.Spec) error) { | ||||
| 	validatorLock.Lock() | ||||
| 	defer validatorLock.Unlock() | ||||
| 	specValidator = fn | ||||
| } | ||||
|  | ||||
| // validateSpec validates the Spec using the extneral validator. | ||||
| func validateSpec(raw *cdi.Spec) error { | ||||
| 	validatorLock.RLock() | ||||
| 	defer validatorLock.RUnlock() | ||||
|  | ||||
| 	if specValidator == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	err := specValidator(raw) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "Spec validation failed") | ||||
| 		return fmt.Errorf("Spec validation failed: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GenerateSpecName generates a vendor+class scoped Spec file name. The | ||||
| // name can be passed to WriteSpec() to write a Spec file to the file | ||||
| // system. | ||||
| // | ||||
| // vendor and class should match the vendor and class of the CDI Spec. | ||||
| // The file name is generated without a ".json" or ".yaml" extension. | ||||
| // The caller can append the desired extension to choose a particular | ||||
| // encoding. Otherwise WriteSpec() will use its default encoding. | ||||
| // | ||||
| // This function always returns the same name for the same vendor/class | ||||
| // combination. Therefore it cannot be used as such to generate multiple | ||||
| // Spec file names for a single vendor and class. | ||||
| func GenerateSpecName(vendor, class string) string { | ||||
| 	return vendor + "-" + class | ||||
| } | ||||
|  | ||||
| // GenerateTransientSpecName generates a vendor+class scoped transient | ||||
| // Spec file name. The name can be passed to WriteSpec() to write a Spec | ||||
| // file to the file system. | ||||
| // | ||||
| // Transient Specs are those whose lifecycle is tied to that of some | ||||
| // external entity, for instance a container. vendor and class should | ||||
| // 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 | ||||
| // is tied to has a unique ID of its own, then this is usually a | ||||
| // good choice for transientID. | ||||
| // | ||||
| // The file name is generated without a ".json" or ".yaml" extension. | ||||
| // The caller can append the desired extension to choose a particular | ||||
| // encoding. Otherwise WriteSpec() will use its default encoding. | ||||
| func GenerateTransientSpecName(vendor, class, transientID string) string { | ||||
| 	transientID = strings.ReplaceAll(transientID, "/", "_") | ||||
| 	return GenerateSpecName(vendor, class) + "_" + transientID | ||||
| } | ||||
|  | ||||
| // GenerateNameForSpec generates a name for the given Spec using | ||||
| // GenerateSpecName with the vendor and class taken from the Spec. | ||||
| // On success it returns the generated name and a nil error. If | ||||
| // the Spec does not contain a valid vendor or class, it returns | ||||
| // an empty name and a non-nil error. | ||||
| func GenerateNameForSpec(raw *cdi.Spec) (string, error) { | ||||
| 	vendor, class := ParseQualifier(raw.Kind) | ||||
| 	if vendor == "" { | ||||
| 		return "", fmt.Errorf("invalid vendor/class %q in Spec", raw.Kind) | ||||
| 	} | ||||
|  | ||||
| 	return GenerateSpecName(vendor, class), nil | ||||
| } | ||||
|  | ||||
| // GenerateNameForTransientSpec generates a name for the given transient | ||||
| // Spec using GenerateTransientSpecName with the vendor and class taken | ||||
| // from the Spec. On success it returns the generated name and a nil error. | ||||
| // If the Spec does not contain a valid vendor or class, it returns an | ||||
| // an empty name and a non-nil error. | ||||
| func GenerateNameForTransientSpec(raw *cdi.Spec, transientID string) (string, error) { | ||||
| 	vendor, class := ParseQualifier(raw.Kind) | ||||
| 	if vendor == "" { | ||||
| 		return "", fmt.Errorf("invalid vendor/class %q in Spec", raw.Kind) | ||||
| 	} | ||||
|  | ||||
| 	return GenerateTransientSpecName(vendor, class, transientID), nil | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										160
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/version.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/version.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | ||||
| /* | ||||
|    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" | ||||
|  | ||||
| 	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" | ||||
|  | ||||
| 	// 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, | ||||
| } | ||||
|  | ||||
| // MinimumRequiredVersion determines the minumum 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 | ||||
| } | ||||
|  | ||||
| // requiresV050 returns true if the spec uses v0.5.0 features | ||||
| func requiresV050(spec *cdi.Spec) bool { | ||||
| 	var edits []*cdi.ContainerEdits | ||||
|  | ||||
| 	for _, d := range spec.Devices { | ||||
| 		// The v0.5.0 spec allowed device names to start with a digit instead of requiring a letter | ||||
| 		if len(d.Name) > 0 && !isLetter(rune(d.Name[0])) { | ||||
| 			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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Ed Bartosh
					Ed Bartosh