diff options
author | Evan Lezar <elezar@nvidia.com> | 2022-01-12 14:22:29 +0100 |
---|---|---|
committer | Evan Lezar <elezar@nvidia.com> | 2022-01-14 13:35:22 +0100 |
commit | 968deb7c2cd2e4fab7edf03f2a73fb62bb652171 (patch) | |
tree | ea0349b4a36a44625e5f6dda891034986da8417f /vendor/github.com/container-orchestrated-devices/container-device-interface | |
parent | 2c510146aa03c74fb00a15bcf81c62b14df9c7ea (diff) | |
download | podman-968deb7c2cd2e4fab7edf03f2a73fb62bb652171.tar.gz podman-968deb7c2cd2e4fab7edf03f2a73fb62bb652171.tar.bz2 podman-968deb7c2cd2e4fab7edf03f2a73fb62bb652171.zip |
Use new CDI API
This change updates the CDI API to commit 46367ec063fda9da931d050b308ccd768e824364
which addresses some inconistencies in the previous implementation.
Signed-off-by: Evan Lezar <elezar@nvidia.com>
Diffstat (limited to 'vendor/github.com/container-orchestrated-devices/container-device-interface')
11 files changed, 1363 insertions, 205 deletions
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 new file mode 100644 index 000000000..c4befc83e --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go @@ -0,0 +1,275 @@ +/* + 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 ( + "path/filepath" + "sort" + "strings" + "sync" + + "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. +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 +} + +// 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...)) + } + + return c, c.Refresh() +} + +// Configure applies options to the cache. Updates the cache if options have +// changed. +func (c *Cache) Configure(options ...Option) error { + if len(options) == 0 { + return nil + } + + c.Lock() + defer c.Unlock() + + for _, o := range options { + if err := o(c); err != nil { + return errors.Wrapf(err, "failed to apply cache options") + } + } + + return nil +} + +// Refresh rescans the CDI Spec directories and refreshes the Cache. +func (c *Cache) Refresh() error { + var ( + specs = map[string][]*Spec{} + devices = map[string]*Device{} + conflicts = map[string]struct{}{} + specErrors = map[string][]error{} + result []error + ) + + // collect errors per spec file path and once globally + collectError := func(err error, paths ...string) { + result = append(result, err) + for _, path := range paths { + specErrors[path] = append(specErrors[path], err) + } + } + // resolve conflicts based on device Spec priority (order of precedence) + resolveConflict := func(name string, dev *Device, old *Device) bool { + devSpec, oldSpec := dev.GetSpec(), old.GetSpec() + devPrio, oldPrio := devSpec.GetPriority(), oldSpec.GetPriority() + switch { + case devPrio > oldPrio: + return false + case devPrio == oldPrio: + devPath, oldPath := devSpec.GetPath(), oldSpec.GetPath() + collectError(errors.Errorf("conflicting device %q (specs %q, %q)", + name, devPath, oldPath), devPath, oldPath) + conflicts[name] = struct{}{} + } + return true + } + + _ = 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) + return nil + } + + vendor := spec.GetVendor() + specs[vendor] = append(specs[vendor], spec) + + for _, dev := range spec.devices { + qualified := dev.GetQualifiedName() + other, ok := devices[qualified] + if ok { + if resolveConflict(qualified, dev, other) { + continue + } + } + devices[qualified] = dev + } + + return nil + }) + + for conflict := range conflicts { + delete(devices, conflict) + } + + c.Lock() + defer c.Unlock() + + c.specs = specs + c.devices = devices + c.errors = specErrors + + if len(result) > 0 { + return multierror.Append(nil, result...) + } + + return 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. +func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, error) { + var ( + unresolved []string + specs = map[*Spec]struct{}{} + ) + + if ociSpec == nil { + return devices, errors.Errorf("can't inject devices, nil OCI Spec") + } + + c.Lock() + defer c.Unlock() + + for _, device := range devices { + d := c.devices[device] + if d == nil { + unresolved = append(unresolved, device) + continue + } + if _, ok := specs[d.GetSpec()]; !ok { + specs[d.GetSpec()] = struct{}{} + d.GetSpec().ApplyEdits(ociSpec) + } + d.ApplyEdits(ociSpec) + } + + if unresolved != nil { + return unresolved, errors.Errorf("unresolvable CDI devices %s", + strings.Join(devices, ", ")) + } + + return nil, nil +} + +// GetDevice returns the cached device for the given qualified name. +func (c *Cache) GetDevice(device string) *Device { + c.Lock() + defer c.Unlock() + + return c.devices[device] +} + +// ListDevices lists all cached devices by qualified name. +func (c *Cache) ListDevices() []string { + var devices []string + + c.Lock() + defer c.Unlock() + + for name := range c.devices { + devices = append(devices, name) + } + sort.Strings(devices) + + return devices +} + +// ListVendors lists all vendors known to the cache. +func (c *Cache) ListVendors() []string { + var vendors []string + + c.Lock() + defer c.Unlock() + + for vendor := range c.specs { + vendors = append(vendors, vendor) + } + sort.Strings(vendors) + + return vendors +} + +// ListClasses lists all device classes known to the cache. +func (c *Cache) ListClasses() []string { + var ( + cmap = map[string]struct{}{} + classes []string + ) + + c.Lock() + defer c.Unlock() + + for _, specs := range c.specs { + for _, spec := range specs { + cmap[spec.GetClass()] = struct{}{} + } + } + for class := range cmap { + classes = append(classes, class) + } + sort.Strings(classes) + + return classes +} + +// GetVendorSpecs returns all specs for the given vendor. +func (c *Cache) GetVendorSpecs(vendor string) []*Spec { + c.Lock() + defer c.Unlock() + + return c.specs[vendor] +} + +// 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()] +} + +// GetErrors returns all errors encountered during the last +// cache refresh. +func (c *Cache) GetErrors() map[string][]error { + return c.errors +} + +// GetSpecDirectories returns the CDI Spec directories currently in use. +func (c *Cache) GetSpecDirectories() []string { + dirs := make([]string, len(c.specDirs)) + copy(dirs, c.specDirs) + return dirs +} 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 new file mode 100644 index 000000000..767cba57b --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go @@ -0,0 +1,222 @@ +/* + 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 ( + "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" +) + +const ( + // PrestartHook is the name of the OCI "prestart" hook. + PrestartHook = "prestart" + // CreateRuntimeHook is the name of the OCI "createRuntime" hook. + CreateRuntimeHook = "createRuntime" + // CreateContainerHook is the name of the OCI "createContainer" hook. + CreateContainerHook = "createContainer" + // StartContainerHook is the name of the OCI "startContainer" hook. + StartContainerHook = "startContainer" + // PoststartHook is the name of the OCI "poststart" hook. + PoststartHook = "poststart" + // PoststopHook is the name of the OCI "poststop" hook. + PoststopHook = "poststop" +) + +var ( + // Names of recognized hooks. + validHookNames = map[string]struct{}{ + PrestartHook: {}, + CreateRuntimeHook: {}, + CreateContainerHook: {}, + StartContainerHook: {}, + PoststartHook: {}, + PoststopHook: {}, + } +) + +// ContainerEdits represent updates to be applied to an OCI Spec. +// These updates can be specific to a CDI device, or they can be +// specific to a CDI Spec. In the former case these edits should +// be applied to all OCI Specs where the corresponding CDI device +// is injected. In the latter case, these edits should be applied +// to all OCI Specs where at least one devices from the CDI Spec +// is injected. +type ContainerEdits struct { + *specs.ContainerEdits +} + +// Apply edits to the given OCI Spec. Updates the OCI Spec in place. +// Returns an error if the update fails. +func (e *ContainerEdits) Apply(spec *oci.Spec) error { + if spec == nil { + return errors.New("can't edit nil OCI Spec") + } + if e == nil || e.ContainerEdits == nil { + return nil + } + + specgen := ocigen.NewFromSpec(spec) + if len(e.Env) > 0 { + specgen.AddMultipleProcessEnv(e.Env) + } + for _, d := range e.DeviceNodes { + specgen.AddDevice(d.ToOCI()) + } + for _, m := range e.Mounts { + specgen.AddMount(m.ToOCI()) + } + for _, h := range e.Hooks { + switch h.HookName { + case PrestartHook: + specgen.AddPreStartHook(h.ToOCI()) + case PoststartHook: + specgen.AddPostStartHook(h.ToOCI()) + case PoststopHook: + specgen.AddPostStopHook(h.ToOCI()) + // TODO: Maybe runtime-tools/generate should be updated with these... + case CreateRuntimeHook: + ensureOCIHooks(spec) + spec.Hooks.CreateRuntime = append(spec.Hooks.CreateRuntime, h.ToOCI()) + case CreateContainerHook: + ensureOCIHooks(spec) + spec.Hooks.CreateContainer = append(spec.Hooks.CreateContainer, h.ToOCI()) + case StartContainerHook: + ensureOCIHooks(spec) + spec.Hooks.StartContainer = append(spec.Hooks.StartContainer, h.ToOCI()) + default: + return errors.Errorf("unknown hook name %q", h.HookName) + } + } + + return nil +} + +// Validate container edits. +func (e *ContainerEdits) Validate() error { + if e == nil || e.ContainerEdits == nil { + return nil + } + + if err := ValidateEnv(e.Env); err != nil { + return errors.Wrap(err, "invalid container edits") + } + for _, d := range e.DeviceNodes { + if err := (&DeviceNode{d}).Validate(); err != nil { + return err + } + } + for _, h := range e.Hooks { + if err := (&Hook{h}).Validate(); err != nil { + return err + } + } + for _, m := range e.Mounts { + if err := (&Mount{m}).Validate(); err != nil { + return err + } + } + + return nil +} + +// isEmpty returns true if these edits are empty. This is valid in a +// global Spec context but invalid in a Device context. +func (e *ContainerEdits) isEmpty() bool { + if e == nil { + return false + } + return len(e.Env)+len(e.DeviceNodes)+len(e.Hooks)+len(e.Mounts) == 0 +} + +// ValidateEnv validates the given environment variables. +func ValidateEnv(env []string) error { + for _, v := range env { + if strings.IndexByte(v, byte('=')) <= 0 { + return errors.Errorf("invalid environment variable %q", v) + } + } + return nil +} + +// DeviceNode is a CDI Spec DeviceNode wrapper, used for validating DeviceNodes. +type DeviceNode struct { + *specs.DeviceNode +} + +// Validate a CDI Spec DeviceNode. +func (d *DeviceNode) Validate() error { + if d.Path == "" { + return errors.New("invalid (empty) device path") + } + if d.Type != "" && d.Type != "b" && d.Type != "c" { + return errors.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", + d.Path, d.Permissions) + } + } + return nil +} + +// Hook is a CDI Spec Hook wrapper, used for validating hooks. +type Hook struct { + *specs.Hook +} + +// Validate a hook. +func (h *Hook) Validate() error { + if _, ok := validHookNames[h.HookName]; !ok { + return errors.Errorf("invalid hook name %q", h.HookName) + } + if h.Path == "" { + return errors.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 nil +} + +// Mount is a CDI Mount wrapper, used for validating mounts. +type Mount struct { + *specs.Mount +} + +// Validate a mount. +func (m *Mount) Validate() error { + if m.HostPath == "" { + return errors.New("invalid mount, empty host path") + } + if m.ContainerPath == "" { + return errors.New("invalid mount, empty container path") + } + return nil +} + +// Ensure OCI Spec hooks are not nil so we can add hooks. +func ensureOCIHooks(spec *oci.Spec) { + if spec.Hooks == nil { + spec.Hooks = &oci.Hooks{} + } +} 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 new file mode 100644 index 000000000..84b71429c --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go @@ -0,0 +1,74 @@ +/* + 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 ( + 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. +type Device struct { + *cdi.Device + spec *Spec +} + +// Create a new Device, associate it with the given Spec. +func newDevice(spec *Spec, d cdi.Device) (*Device, error) { + dev := &Device{ + Device: &d, + spec: spec, + } + + if err := dev.validate(); err != nil { + return nil, err + } + + return dev, nil +} + +// GetSpec returns the Spec this device is defined in. +func (d *Device) GetSpec() *Spec { + return d.spec +} + +// GetQualifiedName returns the qualified name for this device. +func (d *Device) GetQualifiedName() string { + return QualifiedName(d.spec.GetVendor(), d.spec.GetClass(), d.Name) +} + +// ApplyEdits applies the device-speific container edits to an OCI Spec. +func (d *Device) ApplyEdits(ociSpec *oci.Spec) error { + e := ContainerEdits{&d.ContainerEdits} + return e.Apply(ociSpec) +} + +// Validate the device. +func (d *Device) validate() error { + if err := ValidateDeviceName(d.Name); err != nil { + return err + } + edits := ContainerEdits{&d.ContainerEdits} + if edits.isEmpty() { + return errors.Errorf("invalid device, empty device edits") + } + if err := edits.Validate(); err != nil { + return errors.Wrapf(err, "invalid device %q", d.Name) + } + 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 new file mode 100644 index 000000000..4fcdc44db --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go @@ -0,0 +1,130 @@ +// Package cdi has the primary purpose of providing an API for +// interacting with CDI and consuming CDI devices. +// +// For more information about Container Device Interface, please refer to +// https://github.com/container-orchestrated-devices/container-device-interface +// +// Container Device Interface +// +// Container Device Interface, or CDI for short, provides comprehensive +// third party device support for container runtimes. CDI uses vendor +// provided specification files, CDI Specs for short, to describe how a +// container's runtime environment should be modified when one or more +// of the vendor-specific devices is injected into the container. Beyond +// describing the low level platform-specific details of how to gain +// basic access to a device, CDI Specs allow more fine-grained device +// initialization, and the automatic injection of any necessary vendor- +// or device-specific software that might be required for a container +// to use a device or take full advantage of it. +// +// In the CDI device model containers request access to a device using +// fully qualified device names, qualified names for short, consisting of +// a vendor identifier, a device class and a device name or identifier. +// These pieces of information together uniquely identify a device among +// all device vendors, classes and device instances. +// +// This package implements an API for easy consumption of CDI. The API +// implements discovery, loading and caching of CDI Specs and injection +// of CDI devices into containers. This is the most common functionality +// the vast majority of CDI consumers need. The API should be usable both +// by OCI runtime clients and runtime implementations. +// +// CDI Registry +// +// The primary interface to interact with CDI devices is the Registry. It +// is essentially a cache of all Specs and devices discovered in standard +// CDI directories on the host. The registry has two main functionality, +// injecting devices into an OCI Spec and refreshing the cache of CDI +// Specs and devices. +// +// Device Injection +// +// Using the Registry one can inject CDI devices into a container with code +// similar to the following snippet: +// +// import ( +// "fmt" +// "strings" +// +// "github.com/pkg/errors" +// log "github.com/sirupsen/logrus" +// +// "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" +// oci "github.com/opencontainers/runtime-spec/specs-go" +// ) +// +// func injectCDIDevices(spec *oci.Spec, devices []string) error { +// log.Debug("pristine OCI Spec: %s", dumpSpec(spec)) +// +// unresolved, err := cdi.GetRegistry().InjectDevices(spec, devices) +// if err != nil { +// return errors.Wrap(err, "CDI device injection failed") +// } +// +// log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec)) +// return nil +// } +// +// Cache Refresh +// +// 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: +// +// import ( +// "fmt" +// "strings" +// +// "github.com/pkg/errors" +// log "github.com/sirupsen/logrus" +// +// "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" +// oci "github.com/opencontainers/runtime-spec/specs-go" +// ) +// +// func injectCDIDevices(spec *oci.Spec, devices []string) error { +// registry := cdi.GetRegistry() +// +// if err := registry.Refresh(); err != nil { +// // Note: +// // It is up to the implementation to decide whether +// // to abort injection on errors. A failed Refresh() +// // does not necessarily render the registry unusable. +// // For instance, a parse error in a Spec file for +// // vendor A does not have any effect on devices of +// // vendor B... +// log.Warnf("pre-injection Refresh() failed: %v", err) +// } +// +// log.Debug("pristine OCI Spec: %s", dumpSpec(spec)) +// +// unresolved, err := registry.InjectDevices(spec, devices) +// if err != nil { +// return errors.Wrap(err, "CDI device injection failed") +// } +// +// log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec)) +// return nil +// } +// +// 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. +// +// CDI can collect Spec files from multiple directories. Spec files are +// automatically assigned priorities according to which directory they +// 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 +// Spec file with the highest priority. +// +// The default CDI directory configuration is chosen to encourage +// 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'. +package cdi 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 new file mode 100644 index 000000000..54f19143c --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go @@ -0,0 +1,203 @@ +/* + 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 ( + "strings" + + "github.com/pkg/errors" +) + +// QualifiedName returns the qualified name for a device. +// The syntax for a qualified device names is +// "<vendor>/<class>=<name>". +// A valid vendor name may contain the following runes: +// 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'. +// A valid class name may contain the following runes: +// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_'. +// A valid device name may containe the following runes: +// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':' +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, 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 +} + +// ParseDevice tries to split a device name into vendor, class, and name. +// If this fails, for instance in the case of unqualified device names, +// ParseDevice returns an empty vendor and class together with name set +// to the verbatim input. +func ParseDevice(device string) (string, string, string) { + if device == "" || device[0] == '/' { + return "", "", device + } + + parts := strings.SplitN(device, "=", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", "", device + } + + name := parts[1] + vendor, class := ParseQualifier(parts[0]) + if vendor == "" { + return "", "", device + } + + return vendor, class, name +} + +// ParseQualifier splits a device qualifier into vendor and class. +// The syntax for a device qualifier is +// "<vendor>/<class>" +// If parsing fails, an empty vendor and the class set to the +// verbatim input is returned. +func ParseQualifier(kind string) (string, string) { + parts := strings.SplitN(kind, "/", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", kind + } + return parts[0], parts[1] +} + +// ValidateVendorName checks the validity of a vendor name. +// A vendor name may contain the following ASCII characters: +// - upper- and lowercase letters ('A'-'Z', 'a'-'z') +// - digits ('0'-'9') +// - underscore, dash, and dot ('_', '-', and '.') +func ValidateVendorName(vendor string) error { + 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 letter", vendor) + } + + return nil +} + +// 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 ('_', '-') +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 letter", class) + } + 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 errors.Errorf("invalid (empty) device name") + } + if !isLetter(rune(name[0])) { + return errors.Errorf("invalid name %q, should start with letter", name) + } + 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 start with letter", 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) +} 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 new file mode 100644 index 000000000..fa6e0af69 --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go @@ -0,0 +1,139 @@ +/* + 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 ( + "sync" + + 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 + DeviceDB() RegistryDeviceDB + SpecDB() RegistrySpecDB +} + +// RegistryRefresher is the registry interface for refreshing the +// cache of CDI Specs and devices. +// +// 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. +// +// GetErrors returns all errors encountered for any of the scanned +// Spec files during the last cache refresh. +// +// GetSpecDirectories returns the set up CDI Spec directories +// currently in use. The directories are returned in the scan +// order of Refresh(). +type RegistryRefresher interface { + Refresh() error + GetErrors() map[string][]error + GetSpecDirectories() []string +} + +// RegistryResolver is the registry interface for injecting CDI +// devices into an OCI Spec. +// +// InjectDevices takes an OCI Spec and injects into it a set of +// CDI devices given by qualified name. It returns the names of +// any unresolved devices and an error if injection fails. +type RegistryResolver interface { + InjectDevices(spec *oci.Spec, device ...string) (unresolved []string, err error) +} + +// RegistryDeviceDB is the registry interface for querying devices. +// +// GetDevice returns the CDI device for the given qualified name. If +// the device is not GetDevice returns nil. +// +// ListDevices returns a slice with the names of qualified device +// known. The returned slice is sorted. +type RegistryDeviceDB interface { + GetDevice(device string) *Device + ListDevices() []string +} + +// RegistrySpecDB is the registry interface for querying CDI Specs. +// +// ListVendors returns a slice with all vendors known. The returned +// slice is sorted. +// +// ListClasses returns a slice with all classes known. The returned +// slice is sorted. +// +// GetVendorSpecs returns a slice of all Specs for the vendor. +// +// GetSpecErrors returns any errors for the Spec encountered during +// the last cache refresh. +type RegistrySpecDB interface { + ListVendors() []string + ListClasses() []string + GetVendorSpecs(vendor string) []*Spec + GetSpecErrors(*Spec) []error +} + +type registry struct { + *Cache +} + +var _ Registry = ®istry{} + +var ( + reg *registry + initOnce sync.Once +) + +// GetRegistry returns the CDI registry. If any options are given, those +// are applied to the registry. +func GetRegistry(options ...Option) Registry { + var new bool + initOnce.Do(func() { + reg, _ = getRegistry(options...) + new = true + }) + if !new && len(options) > 0 { + reg.Configure(options...) + reg.Refresh() + } + return reg +} + +// DeviceDB returns the registry interface for querying devices. +func (r *registry) DeviceDB() RegistryDeviceDB { + return r +} + +// SpecDB returns the registry interface for querying Specs. +func (r *registry) SpecDB() RegistrySpecDB { + return r +} + +func getRegistry(options ...Option) (*registry, error) { + c, err := NewCache(options...) + return ®istry{c}, err +} diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec-dirs.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec-dirs.go new file mode 100644 index 000000000..ad017fec7 --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec-dirs.go @@ -0,0 +1,110 @@ +/* + 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 ( + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +const ( + // DefaultStaticDir is the default directory for static CDI Specs. + DefaultStaticDir = "/etc/cdi" + // DefaultDynamicDir is the default directory for generated CDI Specs + DefaultDynamicDir = "/var/run/cdi" +) + +var ( + // DefaultSpecDirs is the default Spec directory configuration. + // While altering this variable changes the package defaults, + // the preferred way of overriding the default directories is + // to use a WithSpecDirs options. Otherwise the change is only + // effective if it takes place before creating the Registry or + // other Cache instances. + DefaultSpecDirs = []string{DefaultStaticDir, DefaultDynamicDir} + // ErrStopScan can be returned from a ScanSpecFunc to stop the scan. + ErrStopScan = errors.New("stop Spec scan") +) + +// 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)) + for i, dir := range dirs { + c.specDirs[i] = filepath.Clean(dir) + } + return nil + } +} + +// scanSpecFunc is a function for processing CDI Spec files. +type scanSpecFunc func(string, int, *Spec, error) error + +// ScanSpecDirs scans the given directories looking for CDI Spec files, +// which are all files with a '.json' or '.yaml' suffix. For every Spec +// file discovered, ScanSpecDirs loads a Spec from the file then calls +// the scan function passing it the path to the file, the priority (the +// index of the directory in the slice of directories given), the Spec +// itself, and any error encountered while loading the Spec. +// +// Scanning stops once all files have been processed or when the scan +// function returns an error. The result of ScanSpecDirs is the error +// returned by the scan function, if any. The special error ErrStopScan +// can be used to terminate the scan gracefully without ScanSpecDirs +// returning an error. ScanSpecDirs silently skips any subdirectories. +func scanSpecDirs(dirs []string, scanFn scanSpecFunc) error { + var ( + spec *Spec + err error + ) + + for priority, dir := range dirs { + 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 { + return err + } + // first call from Walk is for dir itself, others we skip + if info.IsDir() { + if path == dir { + return nil + } + return filepath.SkipDir + } + + // ignore obviously non-Spec files + if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" { + return nil + } + + if err != nil { + return scanFn(path, priority, nil, err) + } + + spec, err = ReadSpec(path, priority) + return scanFn(path, priority, spec, err) + }) + + if err != nil && err != ErrStopScan { + return err + } + } + + return nil +} 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 new file mode 100644 index 000000000..20f188498 --- /dev/null +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go @@ -0,0 +1,172 @@ +/* + 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 ( + "io/ioutil" + "os" + "path/filepath" + + 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": {}, + } +) + +// Spec represents a single CDI Spec. It is usually loaded from a +// file and stored in a cache. The Spec has an associated priority. +// This priority is inherited from the associated priority of the +// CDI Spec directory that contains the CDI Spec file and is used +// to resolve conflicts if multiple CDI Spec files contain entries +// for the same fully qualified device. +type Spec struct { + *cdi.Spec + vendor string + class string + path string + priority int + devices map[string]*Device +} + +// ReadSpec reads the given CDI Spec file. The resulting Spec is +// assigned the given priority. If reading or parsing the Spec +// data fails ReadSpec returns a nil Spec and an error. +func ReadSpec(path string, priority int) (*Spec, error) { + data, err := ioutil.ReadFile(path) + switch { + case os.IsNotExist(err): + return nil, err + case err != nil: + return nil, errors.Wrapf(err, "failed to read CDI Spec %q", path) + } + + raw, err := parseSpec(data) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse CDI Spec %q", path) + } + + return NewSpec(raw, path, priority) +} + +// 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 +// Spec and an error. +func NewSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) { + var err error + + spec := &Spec{ + Spec: raw, + path: filepath.Clean(path), + priority: priority, + } + + spec.vendor, spec.class = ParseQualifier(spec.Kind) + + if spec.devices, err = spec.validate(); err != nil { + return nil, errors.Wrap(err, "invalid CDI Spec") + } + + return spec, nil +} + +// GetVendor returns the vendor of this Spec. +func (s *Spec) GetVendor() string { + return s.vendor +} + +// GetClass returns the device class of this Spec. +func (s *Spec) GetClass() string { + return s.class +} + +// GetDevice returns the device for the given unqualified name. +func (s *Spec) GetDevice(name string) *Device { + return s.devices[name] +} + +// GetPath returns the filesystem path of this Spec. +func (s *Spec) GetPath() string { + return s.path +} + +// GetPriority returns the priority of this Spec. +func (s *Spec) GetPriority() int { + return s.priority +} + +// ApplyEdits applies the Spec's global-scope container edits to an OCI Spec. +func (s *Spec) ApplyEdits(ociSpec *oci.Spec) error { + e := ContainerEdits{&s.ContainerEdits} + return e.Apply(ociSpec) +} + +// Validate the Spec. +func (s *Spec) validate() (map[string]*Device, error) { + if _, ok := validSpecVersions[s.Version]; !ok { + return nil, errors.Errorf("invalid version %q", s.Version) + } + if err := ValidateVendorName(s.vendor); err != nil { + return nil, err + } + if err := ValidateClassName(s.class); err != nil { + return nil, err + } + edits := &ContainerEdits{&s.ContainerEdits} + if err := edits.Validate(); err != nil { + return nil, err + } + + devices := make(map[string]*Device) + for _, d := range s.Devices { + dev, err := newDevice(s, d) + if err != nil { + return nil, errors.Wrapf(err, "failed add device %q", d.Name) + } + if _, conflict := devices[d.Name]; conflict { + return nil, errors.Errorf("invalid spec, multiple device %q", d.Name) + } + devices[d.Name] = dev + } + + return devices, nil +} + +// Parse raw CDI Spec file data. +func parseSpec(data []byte) (*cdi.Spec, error) { + raw := &cdi.Spec{} + err := yaml.UnmarshalStrict(data, &raw) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal CDI Spec") + } + return raw, validateJSONSchema(raw) +} + +// Validate CDI Spec against JSON Schema. +func validateJSONSchema(raw *cdi.Spec) error { + // TODO + return nil +} diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/devices.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/devices.go deleted file mode 100644 index e66fd36c0..000000000 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/devices.go +++ /dev/null @@ -1,180 +0,0 @@ -package pkg - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - cdispec "github.com/container-orchestrated-devices/container-device-interface/specs-go" - spec "github.com/opencontainers/runtime-spec/specs-go" -) - -const ( - root = "/etc/cdi" -) - -func collectCDISpecs() (map[string]*cdispec.Spec, error) { - var files []string - vendor := make(map[string]*cdispec.Spec) - - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if info == nil || info.IsDir() { - return nil - } - - if filepath.Ext(path) != ".json" { - return nil - } - - files = append(files, path) - return nil - }) - - if err != nil { - return nil, err - } - - for _, path := range files { - spec, err := loadCDIFile(path) - if err != nil { - continue - } - - if _, ok := vendor[spec.Kind]; ok { - continue - } - - vendor[spec.Kind] = spec - } - - return vendor, nil -} - -// TODO: Validate (e.g: duplicate device names) -func loadCDIFile(path string) (*cdispec.Spec, error) { - file, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - var spec *cdispec.Spec - err = json.Unmarshal([]byte(file), &spec) - if err != nil { - return nil, err - } - - return spec, nil -} - -/* -* Pattern "vendor.com/device=myDevice" with the vendor being optional - */ -func extractVendor(dev string) (string, string) { - if strings.IndexByte(dev, '=') == -1 { - return "", dev - } - - split := strings.SplitN(dev, "=", 2) - return split[0], split[1] -} - -// GetCDIForDevice returns the CDI specification that matches the device name the user provided. -func GetCDIForDevice(dev string, specs map[string]*cdispec.Spec) (*cdispec.Spec, error) { - vendor, device := extractVendor(dev) - - if vendor != "" { - s, ok := specs[vendor] - if !ok { - return nil, fmt.Errorf("Could not find vendor %q for device %q", vendor, device) - } - - for _, d := range s.Devices { - if d.Name != device { - continue - } - - return s, nil - } - - return nil, fmt.Errorf("Could not find device %q for vendor %q", device, vendor) - } - - var found []*cdispec.Spec - var vendors []string - for vendor, spec := range specs { - - for _, d := range spec.Devices { - if d.Name != device { - continue - } - - found = append(found, spec) - vendors = append(vendors, vendor) - } - } - - if len(found) > 1 { - return nil, fmt.Errorf("%q is ambiguous and currently refers to multiple devices from different vendors: %q", dev, vendors) - } - - if len(found) == 1 { - return found[0], nil - } - - return nil, fmt.Errorf("Could not find device %q", dev) -} - -// HasDevice returns true if a device is a CDI device -// an error may be returned in cases where permissions may be required -func HasDevice(dev string) (bool, error) { - specs, err := collectCDISpecs() - if err != nil { - return false, err - } - - d, err := GetCDIForDevice(dev, specs) - if err != nil { - return false, err - } - - return d != nil, nil -} - -// UpdateOCISpecForDevices updates the given OCI spec based on the requested CDI devices -func UpdateOCISpecForDevices(ociconfig *spec.Spec, devs []string) error { - specs, err := collectCDISpecs() - if err != nil { - return err - } - - return UpdateOCISpecForDevicesWithSpec(ociconfig, devs, specs) -} - -// UpdateOCISpecForDevicesWithLoggerAndSpecs is mainly used for testing -func UpdateOCISpecForDevicesWithSpec(ociconfig *spec.Spec, devs []string, specs map[string]*cdispec.Spec) error { - edits := make(map[string]*cdispec.Spec) - - for _, d := range devs { - spec, err := GetCDIForDevice(d, specs) - if err != nil { - return err - } - - edits[spec.Kind] = spec - err = cdispec.ApplyOCIEditsForDevice(ociconfig, spec, d) - if err != nil { - return err - } - } - - for _, spec := range edits { - if err := cdispec.ApplyOCIEdits(ociconfig, spec); err != nil { - return err - } - } - - return nil -} 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 0223bb703..20914e5b6 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 @@ -1,20 +1,20 @@ package specs +import "os" + // Spec is the base configuration for CDI type Spec struct { Version string `json:"cdiVersion"` Kind string `json:"kind"` - KindShort []string `json:"kindShort,omitempty"` ContainerRuntime []string `json:"containerRuntime,omitempty"` - Devices []Devices `json:"devices"` + Devices []Device `json:"devices"` ContainerEdits ContainerEdits `json:"containerEdits,omitempty"` } -// Devices is a "Device" a container runtime can add to a container -type Devices struct { +// Device is a "Device" a container runtime can add to a container +type Device struct { Name string `json:"name"` - NameShort []string `json:"nameShort"` ContainerEdits ContainerEdits `json:"containerEdits"` } @@ -28,9 +28,14 @@ type ContainerEdits struct { // DeviceNode represents a device node that needs to be added to the OCI spec. type DeviceNode struct { - HostPath string `json:"hostPath"` - ContainerPath string `json:"containerPath"` - Permissions []string `json:"permissions,omitempty"` + Path string `json:"path"` + Type string `json:"type,omitempty"` + Major int64 `json:"major,omitempty"` + Minor int64 `json:"minor,omitempty"` + FileMode *os.FileMode `json:"fileMode,omitempty"` + Permissions string `json:"permissions,omitempty"` + UID *uint32 `json:"uid,omitempty"` + GID *uint32 `json:"gid,omitempty"` } // Mount represents a mount that needs to be added to the OCI spec. 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 c59cda55d..10bc9fa23 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 @@ -37,20 +37,21 @@ func ApplyEditsToOCISpec(config *spec.Spec, edits *ContainerEdits) error { } if len(edits.Env) > 0 { - if config.Process == nil { config.Process = &spec.Process{} } - config.Process.Env = append(config.Process.Env, edits.Env...) } for _, d := range edits.DeviceNodes { - config.Mounts = append(config.Mounts, toOCIDevice(d)) + if config.Linux == nil { + config.Linux = &spec.Linux{} + } + config.Linux.Devices = append(config.Linux.Devices, d.ToOCI()) } for _, m := range edits.Mounts { - config.Mounts = append(config.Mounts, toOCIMount(m)) + config.Mounts = append(config.Mounts, m.ToOCI()) } for _, h := range edits.Hooks { @@ -59,17 +60,17 @@ func ApplyEditsToOCISpec(config *spec.Spec, edits *ContainerEdits) error { } switch h.HookName { case "prestart": - config.Hooks.Prestart = append(config.Hooks.Prestart, toOCIHook(h)) + config.Hooks.Prestart = append(config.Hooks.Prestart, h.ToOCI()) case "createRuntime": - config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, toOCIHook(h)) + config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, h.ToOCI()) case "createContainer": - config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, toOCIHook(h)) + config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, h.ToOCI()) case "startContainer": - config.Hooks.StartContainer = append(config.Hooks.StartContainer, toOCIHook(h)) + config.Hooks.StartContainer = append(config.Hooks.StartContainer, h.ToOCI()) case "poststart": - config.Hooks.Poststart = append(config.Hooks.Poststart, toOCIHook(h)) + config.Hooks.Poststart = append(config.Hooks.Poststart, h.ToOCI()) case "poststop": - config.Hooks.Poststop = append(config.Hooks.Poststop, toOCIHook(h)) + config.Hooks.Poststop = append(config.Hooks.Poststop, h.ToOCI()) default: fmt.Printf("CDI: Unknown hook %q\n", h.HookName) } @@ -78,7 +79,8 @@ func ApplyEditsToOCISpec(config *spec.Spec, edits *ContainerEdits) error { return nil } -func toOCIHook(h *Hook) spec.Hook { +// ToOCI returns the opencontainers runtime Spec Hook for this Hook. +func (h *Hook) ToOCI() spec.Hook { return spec.Hook{ Path: h.Path, Args: h.Args, @@ -87,7 +89,8 @@ func toOCIHook(h *Hook) spec.Hook { } } -func toOCIMount(m *Mount) spec.Mount { +// ToOCI returns the opencontainers runtime Spec Mount for this Mount. +func (m *Mount) ToOCI() spec.Mount { return spec.Mount{ Source: m.HostPath, Destination: m.ContainerPath, @@ -95,10 +98,15 @@ func toOCIMount(m *Mount) spec.Mount { } } -func toOCIDevice(d *DeviceNode) spec.Mount { - return spec.Mount{ - Source: d.HostPath, - Destination: d.ContainerPath, - Options: d.Permissions, +// ToOCI returns the opencontainers runtime Spec LinuxDevice for this DeviceNode. +func (d *DeviceNode) ToOCI() spec.LinuxDevice { + return spec.LinuxDevice{ + Path: d.Path, + Type: d.Type, + Major: d.Major, + Minor: d.Minor, + FileMode: d.FileMode, + UID: d.UID, + GID: d.GID, } } |