update cdi version to v0.5.1
Signed-off-by: xiaoyang zhu <zhuxiaoyang1996@gmail.com>
This commit is contained in:
		
							
								
								
									
										292
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										292
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -22,6 +22,8 @@ import ( | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	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" | ||||
| @@ -33,30 +35,46 @@ type Option func(*Cache) error | ||||
| // Cache stores CDI Specs loaded from Spec directories. | ||||
| type Cache struct { | ||||
| 	sync.Mutex | ||||
| 	specDirs []string | ||||
| 	specs    map[string][]*Spec | ||||
| 	devices  map[string]*Device | ||||
| 	errors   map[string][]error | ||||
| 	specDirs  []string | ||||
| 	specs     map[string][]*Spec | ||||
| 	devices   map[string]*Device | ||||
| 	errors    map[string][]error | ||||
| 	dirErrors map[string]error | ||||
|  | ||||
| 	autoRefresh bool | ||||
| 	watch       *watch | ||||
| } | ||||
|  | ||||
| // WithAutoRefresh returns an option to control automatic Cache refresh. | ||||
| // By default auto-refresh is enabled, the list of Spec directories are | ||||
| // monitored and the Cache is automatically refreshed whenever a change | ||||
| // is detected. This option can be used to disable this behavior when a | ||||
| // manually refreshed mode is preferable. | ||||
| func WithAutoRefresh(autoRefresh bool) Option { | ||||
| 	return func(c *Cache) error { | ||||
| 		c.autoRefresh = autoRefresh | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewCache creates a new CDI Cache. The cache is populated from a set | ||||
| // of CDI Spec directories. These can be specified using a WithSpecDirs | ||||
| // option. The default set of directories is exposed in DefaultSpecDirs. | ||||
| func NewCache(options ...Option) (*Cache, error) { | ||||
| 	c := &Cache{} | ||||
|  | ||||
| 	if err := c.Configure(options...); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if len(c.specDirs) == 0 { | ||||
| 		c.Configure(WithSpecDirs(DefaultSpecDirs...)) | ||||
| 	c := &Cache{ | ||||
| 		autoRefresh: true, | ||||
| 		watch:       &watch{}, | ||||
| 	} | ||||
|  | ||||
| 	return c, c.Refresh() | ||||
| 	WithSpecDirs(DefaultSpecDirs...)(c) | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	return c, c.configure(options...) | ||||
| } | ||||
|  | ||||
| // Configure applies options to the cache. Updates the cache if options have | ||||
| // changed. | ||||
| // Configure applies options to the Cache. Updates and refreshes the | ||||
| // Cache if options have changed. | ||||
| func (c *Cache) Configure(options ...Option) error { | ||||
| 	if len(options) == 0 { | ||||
| 		return nil | ||||
| @@ -65,17 +83,54 @@ func (c *Cache) Configure(options ...Option) error { | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	return c.configure(options...) | ||||
| } | ||||
|  | ||||
| // Configure the Cache. Start/stop CDI Spec directory watch, refresh | ||||
| // the Cache if necessary. | ||||
| func (c *Cache) configure(options ...Option) error { | ||||
| 	var err error | ||||
|  | ||||
| 	for _, o := range options { | ||||
| 		if err := o(c); err != nil { | ||||
| 		if err = o(c); err != nil { | ||||
| 			return errors.Wrapf(err, "failed to apply cache options") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	c.dirErrors = make(map[string]error) | ||||
|  | ||||
| 	c.watch.stop() | ||||
| 	if c.autoRefresh { | ||||
| 		c.watch.setup(c.specDirs, c.dirErrors) | ||||
| 		c.watch.start(&c.Mutex, c.refresh, c.dirErrors) | ||||
| 	} | ||||
| 	c.refresh() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Refresh rescans the CDI Spec directories and refreshes the Cache. | ||||
| // In manual refresh mode the cache is always refreshed. In auto- | ||||
| // refresh mode the cache is only refreshed if it is out of date. | ||||
| func (c *Cache) Refresh() error { | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	// force a refresh in manual mode | ||||
| 	if refreshed, err := c.refreshIfRequired(!c.autoRefresh); refreshed { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// collect and return cached errors, much like refresh() does it | ||||
| 	var result error | ||||
| 	for _, err := range c.errors { | ||||
| 		result = multierror.Append(result, err...) | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // Refresh the Cache by rescanning CDI Spec directories and files. | ||||
| func (c *Cache) refresh() error { | ||||
| 	var ( | ||||
| 		specs      = map[string][]*Spec{} | ||||
| 		devices    = map[string]*Device{} | ||||
| @@ -135,9 +190,6 @@ func (c *Cache) Refresh() error { | ||||
| 		delete(devices, conflict) | ||||
| 	} | ||||
|  | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	c.specs = specs | ||||
| 	c.devices = devices | ||||
| 	c.errors = specErrors | ||||
| @@ -149,6 +201,17 @@ func (c *Cache) Refresh() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RefreshIfRequired triggers a refresh if necessary. | ||||
| func (c *Cache) refreshIfRequired(force bool) (bool, error) { | ||||
| 	// We need to refresh if | ||||
| 	// - it's forced by an explicitly call to Refresh() in manual mode | ||||
| 	// - a missing Spec dir appears (added to watch) in auto-refresh mode | ||||
| 	if force || (c.autoRefresh && c.watch.update(c.dirErrors)) { | ||||
| 		return true, c.refresh() | ||||
| 	} | ||||
| 	return false, nil | ||||
| } | ||||
|  | ||||
| // InjectDevices injects the given qualified devices to an OCI Spec. It | ||||
| // returns any unresolvable devices and an error if injection fails for | ||||
| // any of the devices. | ||||
| @@ -162,6 +225,8 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	c.refreshIfRequired(false) | ||||
|  | ||||
| 	edits := &ContainerEdits{} | ||||
| 	specs := map[*Spec]struct{}{} | ||||
|  | ||||
| @@ -190,11 +255,46 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e | ||||
| 	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. | ||||
| func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error { | ||||
| 	var ( | ||||
| 		specDir string | ||||
| 		path    string | ||||
| 		prio    int | ||||
| 		spec    *Spec | ||||
| 		err     error | ||||
| 	) | ||||
|  | ||||
| 	if len(c.specDirs) == 0 { | ||||
| 		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" | ||||
| 	} | ||||
|  | ||||
| 	spec, err = NewSpec(raw, path, prio) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return spec.Write(true) | ||||
| } | ||||
|  | ||||
| // GetDevice returns the cached device for the given qualified name. | ||||
| func (c *Cache) GetDevice(device string) *Device { | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	c.refreshIfRequired(false) | ||||
|  | ||||
| 	return c.devices[device] | ||||
| } | ||||
|  | ||||
| @@ -205,6 +305,8 @@ func (c *Cache) ListDevices() []string { | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	c.refreshIfRequired(false) | ||||
|  | ||||
| 	for name := range c.devices { | ||||
| 		devices = append(devices, name) | ||||
| 	} | ||||
| @@ -220,6 +322,8 @@ func (c *Cache) ListVendors() []string { | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	c.refreshIfRequired(false) | ||||
|  | ||||
| 	for vendor := range c.specs { | ||||
| 		vendors = append(vendors, vendor) | ||||
| 	} | ||||
| @@ -238,6 +342,8 @@ func (c *Cache) ListClasses() []string { | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	c.refreshIfRequired(false) | ||||
|  | ||||
| 	for _, specs := range c.specs { | ||||
| 		for _, spec := range specs { | ||||
| 			cmap[spec.GetClass()] = struct{}{} | ||||
| @@ -256,6 +362,8 @@ func (c *Cache) GetVendorSpecs(vendor string) []*Spec { | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	c.refreshIfRequired(false) | ||||
|  | ||||
| 	return c.specs[vendor] | ||||
| } | ||||
|  | ||||
| @@ -268,12 +376,158 @@ func (c *Cache) GetSpecErrors(spec *Spec) []error { | ||||
| // GetErrors returns all errors encountered during the last | ||||
| // cache refresh. | ||||
| func (c *Cache) GetErrors() map[string][]error { | ||||
| 	return c.errors | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	errors := map[string][]error{} | ||||
| 	for path, errs := range c.errors { | ||||
| 		errors[path] = errs | ||||
| 	} | ||||
| 	for path, err := range c.dirErrors { | ||||
| 		errors[path] = []error{err} | ||||
| 	} | ||||
|  | ||||
| 	return errors | ||||
| } | ||||
|  | ||||
| // GetSpecDirectories returns the CDI Spec directories currently in use. | ||||
| func (c *Cache) GetSpecDirectories() []string { | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	dirs := make([]string, len(c.specDirs)) | ||||
| 	copy(dirs, c.specDirs) | ||||
| 	return dirs | ||||
| } | ||||
|  | ||||
| // GetSpecDirErrors returns any errors related to configured Spec directories. | ||||
| func (c *Cache) GetSpecDirErrors() map[string]error { | ||||
| 	if c.dirErrors == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	errors := make(map[string]error) | ||||
| 	for dir, err := range c.dirErrors { | ||||
| 		errors[dir] = err | ||||
| 	} | ||||
| 	return errors | ||||
| } | ||||
|  | ||||
| // Our fsnotify helper wrapper. | ||||
| type watch struct { | ||||
| 	watcher *fsnotify.Watcher | ||||
| 	tracked map[string]bool | ||||
| } | ||||
|  | ||||
| // Setup monitoring for the given Spec directories. | ||||
| func (w *watch) setup(dirs []string, dirErrors map[string]error) { | ||||
| 	var ( | ||||
| 		dir string | ||||
| 		err error | ||||
| 	) | ||||
| 	w.tracked = make(map[string]bool) | ||||
| 	for _, dir = range dirs { | ||||
| 		w.tracked[dir] = false | ||||
| 	} | ||||
|  | ||||
| 	w.watcher, err = fsnotify.NewWatcher() | ||||
| 	if err != nil { | ||||
| 		for _, dir := range dirs { | ||||
| 			dirErrors[dir] = errors.Wrap(err, "failed to create watcher") | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	w.update(dirErrors) | ||||
| } | ||||
|  | ||||
| // Start watching Spec directories for relevant changes. | ||||
| func (w *watch) start(m *sync.Mutex, refresh func() error, dirErrors map[string]error) { | ||||
| 	go w.watch(w.watcher, m, refresh, dirErrors) | ||||
| } | ||||
|  | ||||
| // Stop watching directories. | ||||
| func (w *watch) stop() { | ||||
| 	if w.watcher == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	w.watcher.Close() | ||||
| 	w.tracked = nil | ||||
| } | ||||
|  | ||||
| // Watch Spec directory changes, triggering a refresh if necessary. | ||||
| func (w *watch) watch(fsw *fsnotify.Watcher, m *sync.Mutex, refresh func() error, dirErrors map[string]error) { | ||||
| 	watch := fsw | ||||
| 	if watch == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| 		select { | ||||
| 		case event, ok := <-watch.Events: | ||||
| 			if !ok { | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			if (event.Op & (fsnotify.Rename | fsnotify.Remove | fsnotify.Write)) == 0 { | ||||
| 				continue | ||||
| 			} | ||||
| 			if event.Op == fsnotify.Write { | ||||
| 				if ext := filepath.Ext(event.Name); ext != ".json" && ext != ".yaml" { | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			m.Lock() | ||||
| 			if event.Op == fsnotify.Remove && w.tracked[event.Name] { | ||||
| 				w.update(dirErrors, event.Name) | ||||
| 			} else { | ||||
| 				w.update(dirErrors) | ||||
| 			} | ||||
| 			refresh() | ||||
| 			m.Unlock() | ||||
|  | ||||
| 		case _, ok := <-watch.Errors: | ||||
| 			if !ok { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Update watch with pending/missing or removed directories. | ||||
| func (w *watch) update(dirErrors map[string]error, removed ...string) bool { | ||||
| 	var ( | ||||
| 		dir    string | ||||
| 		ok     bool | ||||
| 		err    error | ||||
| 		update bool | ||||
| 	) | ||||
|  | ||||
| 	for dir, ok = range w.tracked { | ||||
| 		if ok { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		err = w.watcher.Add(dir) | ||||
| 		if err == nil { | ||||
| 			w.tracked[dir] = true | ||||
| 			delete(dirErrors, dir) | ||||
| 			update = true | ||||
| 		} else { | ||||
| 			w.tracked[dir] = false | ||||
| 			dirErrors[dir] = errors.Wrap(err, "failed to monitor for changes") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, dir = range removed { | ||||
| 		w.tracked[dir] = false | ||||
| 		dirErrors[dir] = errors.New("directory removed") | ||||
| 		update = true | ||||
| 	} | ||||
|  | ||||
| 	return update | ||||
| } | ||||
|   | ||||
| @@ -85,11 +85,13 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range e.DeviceNodes { | ||||
| 		dev := d.ToOCI() | ||||
| 		if err := fillMissingInfo(&dev); err != nil { | ||||
| 		dn := DeviceNode{d} | ||||
|  | ||||
| 		err := dn.fillMissingInfo() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		dev := d.ToOCI() | ||||
| 		if dev.UID == nil && spec.Process != nil { | ||||
| 			if uid := spec.Process.User.UID; uid > 0 { | ||||
| 				dev.UID = &uid | ||||
| @@ -288,26 +290,31 @@ func ensureOCIHooks(spec *oci.Spec) { | ||||
| } | ||||
|  | ||||
| // fillMissingInfo fills in missing mandatory attributes from the host device. | ||||
| func fillMissingInfo(dev *oci.LinuxDevice) error { | ||||
| 	if dev.Type != "" && (dev.Major != 0 || dev.Type == "p") { | ||||
| 		return nil | ||||
| 	} | ||||
| 	hostDev, err := runc.DeviceFromPath(dev.Path, "rwm") | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "failed to stat CDI host device %q", dev.Path) | ||||
| func (d *DeviceNode) fillMissingInfo() error { | ||||
| 	if d.HostPath == "" { | ||||
| 		d.HostPath = d.Path | ||||
| 	} | ||||
|  | ||||
| 	if dev.Type == "" { | ||||
| 		dev.Type = string(hostDev.Type) | ||||
| 	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 dev.Type != string(hostDev.Type) { | ||||
| 			return errors.Errorf("CDI device %q, host type mismatch (%s, %s)", | ||||
| 				dev.Path, dev.Type, string(hostDev.Type)) | ||||
| 		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 dev.Major == 0 && dev.Type != "p" { | ||||
| 		dev.Major = hostDev.Major | ||||
| 		dev.Minor = hostDev.Minor | ||||
| 	if d.Major == 0 && d.Type != "p" { | ||||
| 		d.Major = hostDev.Major | ||||
| 		d.Minor = hostDev.Minor | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
|   | ||||
| @@ -67,6 +67,21 @@ | ||||
| // | ||||
| // Cache Refresh | ||||
| // | ||||
| // By default the CDI Spec cache monitors the configured Spec directories | ||||
| // and automatically refreshes itself when necessary. This behavior can be | ||||
| // disabled using the WithAutoRefresh(false) option. | ||||
| // | ||||
| // Failure to set up monitoring for a Spec directory causes the directory to | ||||
| // get ignored and an error to be recorded among the Spec directory errors. | ||||
| // These errors can be queried using the GetSpecDirErrors() function. If the | ||||
| // error condition is transient, for instance a missing directory which later | ||||
| // gets created, the corresponding error will be removed once the condition | ||||
| // is over. | ||||
| // | ||||
| // With auto-refresh enabled injecting any CDI devices can be done without | ||||
| // an explicit call to Refresh(), using a code snippet similar to the | ||||
| // following: | ||||
| // | ||||
| // In a runtime implementation one typically wants to make sure the | ||||
| // CDI Spec cache is up to date before performing device injection. | ||||
| // A code snippet similar to the following accmplishes that: | ||||
| @@ -146,5 +161,5 @@ | ||||
| // schema names which switch the used schema to the in-repo validation | ||||
| // schema embedded into the binary or the now default no-op schema | ||||
| // correspondingly. Other names are interpreted as the path to the actual | ||||
| /// validation schema to load and use. | ||||
| // validation schema to load and use. | ||||
| package cdi | ||||
|   | ||||
| @@ -130,7 +130,7 @@ func ValidateVendorName(vendor string) error { | ||||
| 		} | ||||
| 	} | ||||
| 	if !isAlphaNumeric(rune(vendor[len(vendor)-1])) { | ||||
| 		return errors.Errorf("invalid vendor %q, should end with letter", vendor) | ||||
| 		return errors.Errorf("invalid vendor %q, should end with a letter or digit", vendor) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @@ -158,7 +158,7 @@ func ValidateClassName(class string) error { | ||||
| 		} | ||||
| 	} | ||||
| 	if !isAlphaNumeric(rune(class[len(class)-1])) { | ||||
| 		return errors.Errorf("invalid class %q, should end with letter", class) | ||||
| 		return errors.Errorf("invalid class %q, should end with a letter or digit", class) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -172,8 +172,11 @@ func ValidateDeviceName(name string) error { | ||||
| 	if name == "" { | ||||
| 		return errors.Errorf("invalid (empty) device name") | ||||
| 	} | ||||
| 	if !isLetter(rune(name[0])) { | ||||
| 		return errors.Errorf("invalid name %q, should start with letter", 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 { | ||||
| @@ -185,7 +188,7 @@ func ValidateDeviceName(name string) error { | ||||
| 		} | ||||
| 	} | ||||
| 	if !isAlphaNumeric(rune(name[len(name)-1])) { | ||||
| 		return errors.Errorf("invalid name %q, should start with letter", name) | ||||
| 		return errors.Errorf("invalid name %q, should end with a letter or digit", name) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -19,6 +19,7 @@ package cdi | ||||
| import ( | ||||
| 	"sync" | ||||
|  | ||||
| 	cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" | ||||
| 	oci "github.com/opencontainers/runtime-spec/specs-go" | ||||
| ) | ||||
|  | ||||
| @@ -40,6 +41,8 @@ type Registry interface { | ||||
| // RegistryRefresher is the registry interface for refreshing the | ||||
| // cache of CDI Specs and devices. | ||||
| // | ||||
| // Configure reconfigures the registry with the given options. | ||||
| // | ||||
| // Refresh rescans all CDI Spec directories and updates the | ||||
| // state of the cache to reflect any changes. It returns any | ||||
| // errors encountered during the refresh. | ||||
| @@ -50,10 +53,15 @@ type Registry interface { | ||||
| // GetSpecDirectories returns the set up CDI Spec directories | ||||
| // currently in use. The directories are returned in the scan | ||||
| // order of Refresh(). | ||||
| // | ||||
| // GetSpecDirErrors returns any errors related to the configured | ||||
| // Spec directories. | ||||
| type RegistryRefresher interface { | ||||
| 	Configure(...Option) error | ||||
| 	Refresh() error | ||||
| 	GetErrors() map[string][]error | ||||
| 	GetSpecDirectories() []string | ||||
| 	GetSpecDirErrors() map[string]error | ||||
| } | ||||
|  | ||||
| // RegistryResolver is the registry interface for injecting CDI | ||||
| @@ -90,11 +98,15 @@ type RegistryDeviceDB interface { | ||||
| // | ||||
| // GetSpecErrors returns any errors for the Spec encountered during | ||||
| // the last cache refresh. | ||||
| // | ||||
| // WriteSpec writes the Spec with the given content and name to the | ||||
| // last Spec directory. | ||||
| type RegistrySpecDB interface { | ||||
| 	ListVendors() []string | ||||
| 	ListClasses() []string | ||||
| 	GetVendorSpecs(vendor string) []*Spec | ||||
| 	GetSpecErrors(*Spec) []error | ||||
| 	WriteSpec(raw *cdi.Spec, name string) error | ||||
| } | ||||
|  | ||||
| type registry struct { | ||||
|   | ||||
| @@ -17,10 +17,10 @@ | ||||
| package cdi | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io/fs" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -45,10 +45,11 @@ var ( | ||||
| // WithSpecDirs returns an option to override the CDI Spec directories. | ||||
| func WithSpecDirs(dirs ...string) Option { | ||||
| 	return func(c *Cache) error { | ||||
| 		c.specDirs = make([]string, len(dirs)) | ||||
| 		specDirs := make([]string, len(dirs)) | ||||
| 		for i, dir := range dirs { | ||||
| 			c.specDirs[i] = filepath.Clean(dir) | ||||
| 			specDirs[i] = filepath.Clean(dir) | ||||
| 		} | ||||
| 		c.specDirs = specDirs | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| @@ -78,6 +79,9 @@ func scanSpecDirs(dirs []string, scanFn scanSpecFunc) error { | ||||
| 		err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | ||||
| 			// for initial stat failure Walk calls us with nil info | ||||
| 			if info == nil { | ||||
| 				if errors.Is(err, fs.ErrNotExist) { | ||||
| 					return nil | ||||
| 				} | ||||
| 				return err | ||||
| 			} | ||||
| 			// first call from Walk is for dir itself, others we skip | ||||
|   | ||||
| @@ -17,6 +17,7 @@ | ||||
| package cdi | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| @@ -34,7 +35,12 @@ var ( | ||||
| 		"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 | ||||
| ) | ||||
|  | ||||
| // Spec represents a single CDI Spec. It is usually loaded from a | ||||
| @@ -64,7 +70,7 @@ func ReadSpec(path string, priority int) (*Spec, error) { | ||||
| 		return nil, errors.Wrapf(err, "failed to read CDI Spec %q", path) | ||||
| 	} | ||||
|  | ||||
| 	raw, err := parseSpec(data) | ||||
| 	raw, err := ParseSpec(data) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrapf(err, "failed to parse CDI Spec %q", path) | ||||
| 	} | ||||
| @@ -85,7 +91,7 @@ func ReadSpec(path string, priority int) (*Spec, error) { | ||||
| // 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) { | ||||
| 	err := validateWithSchema(raw) | ||||
| 	err := validateSpec(raw) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -105,6 +111,56 @@ func NewSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) { | ||||
| 	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 { | ||||
| 	var ( | ||||
| 		data []byte | ||||
| 		dir  string | ||||
| 		tmp  *os.File | ||||
| 		err  error | ||||
| 	) | ||||
|  | ||||
| 	err = validateSpec(s.Spec) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if filepath.Ext(s.path) == ".yaml" { | ||||
| 		data, err = yaml.Marshal(s.Spec) | ||||
| 	} else { | ||||
| 		data, err = json.Marshal(s.Spec) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to marshal Spec file") | ||||
| 	} | ||||
|  | ||||
| 	dir = filepath.Dir(s.path) | ||||
| 	err = os.MkdirAll(dir, 0o755) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to create Spec dir") | ||||
| 	} | ||||
|  | ||||
| 	tmp, err = os.CreateTemp(dir, "spec.*.tmp") | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to create Spec file") | ||||
| 	} | ||||
| 	_, err = tmp.Write(data) | ||||
| 	tmp.Close() | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to write Spec file") | ||||
| 	} | ||||
|  | ||||
| 	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") | ||||
| 	} | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // GetVendor returns the vendor of this Spec. | ||||
| func (s *Spec) GetVendor() string { | ||||
| 	return s.vendor | ||||
| @@ -179,8 +235,8 @@ func validateVersion(version string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Parse raw CDI Spec file data. | ||||
| func parseSpec(data []byte) (*cdi.Spec, error) { | ||||
| // ParseSpec parses CDI Spec data into a raw CDI Spec. | ||||
| func ParseSpec(data []byte) (*cdi.Spec, error) { | ||||
| 	var raw *cdi.Spec | ||||
| 	err := yaml.UnmarshalStrict(data, &raw) | ||||
| 	if err != nil { | ||||
| @@ -188,3 +244,22 @@ func parseSpec(data []byte) (*cdi.Spec, error) { | ||||
| 	} | ||||
| 	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()). | ||||
| func SetSpecValidator(fn func(*cdi.Spec) error) { | ||||
| 	specValidator = fn | ||||
| } | ||||
|  | ||||
| // validateSpec validates the Spec using the extneral validator. | ||||
| func validateSpec(raw *cdi.Spec) error { | ||||
| 	if specValidator == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	err := specValidator(raw) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "Spec validation failed") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -17,30 +17,32 @@ | ||||
| package cdi | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/container-orchestrated-devices/container-device-interface/schema" | ||||
| 	cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// DefaultExternalSchema is the JSON schema to load if found. | ||||
| 	DefaultExternalSchema = "/etc/cdi/schema/schema.json" | ||||
| ) | ||||
| // Rename src to dst, both relative to the directory dir. If dst already exists | ||||
| // refuse renaming with an error unless overwrite is explicitly asked for. | ||||
| func renameIn(dir, src, dst string, overwrite bool) error { | ||||
| 	var flags uint | ||||
| 
 | ||||
| // SetSchema sets the Spec JSON validation schema to use. | ||||
| func SetSchema(name string) error { | ||||
| 	s, err := schema.Load(name) | ||||
| 	dirf, err := os.Open(dir) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return errors.Wrap(err, "rename failed") | ||||
| 	} | ||||
| 	schema.Set(s) | ||||
| 	defer dirf.Close() | ||||
| 
 | ||||
| 	if !overwrite { | ||||
| 		flags = unix.RENAME_NOREPLACE | ||||
| 	} | ||||
| 
 | ||||
| 	dirFd := int(dirf.Fd()) | ||||
| 	err = unix.Renameat2(dirFd, src, dirFd, dst, flags) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "rename failed") | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Validate CDI Spec against JSON Schema. | ||||
| func validateWithSchema(raw *cdi.Spec) error { | ||||
| 	return schema.ValidateType(raw) | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
| 	SetSchema(DefaultExternalSchema) | ||||
| } | ||||
							
								
								
									
										39
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_other.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_other.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| //go:build !linux | ||||
| // +build !linux | ||||
|  | ||||
| /* | ||||
|    Copyright © 2022 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 ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| // Rename src to dst, both relative to the directory dir. If dst already exists | ||||
| // refuse renaming with an error unless overwrite is explicitly asked for. | ||||
| func renameIn(dir, src, dst string, overwrite bool) error { | ||||
| 	src = filepath.Join(dir, src) | ||||
| 	dst = filepath.Join(dir, dst) | ||||
|  | ||||
| 	_, err := os.Stat(dst) | ||||
| 	if err == nil && !overwrite { | ||||
| 		return os.ErrExist | ||||
| 	} | ||||
|  | ||||
| 	return os.Rename(src, dst) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 xiaoyang zhu
					xiaoyang zhu