diff --git a/README.md b/README.md index 86b57b1c..df533d39 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ $ cat > /etc/cdi/vendor-annotations.json <", + // This field contains a list of alternative names for the device. + // These names must be unique for a given kind. + "aliases": [ (optional) + + ], + // This field contains a set of key-value pairs that may be used to provide // additional information to a consumer on the specific device. "annotations": { (optional) diff --git a/pkg/cdi/cache.go b/pkg/cdi/cache.go index cb495ebb..1247fb8a 100644 --- a/pkg/cdi/cache.go +++ b/pkg/cdi/cache.go @@ -176,14 +176,15 @@ func (c *Cache) refresh() error { 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 + for _, qualified := range dev.GetQualifiedNames() { + other, ok := devices[qualified] + if ok { + if resolveConflict(qualified, dev, other) { + continue + } } + devices[qualified] = dev } - devices[qualified] = dev } return nil diff --git a/pkg/cdi/cache_test.go b/pkg/cdi/cache_test.go index 75ea0c02..15bcfe79 100644 --- a/pkg/cdi/cache_test.go +++ b/pkg/cdi/cache_test.go @@ -903,6 +903,89 @@ devices: type: b major: 10 minor: 1 +`, + }, + }, + ociSpec: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + }, + }, + }, + devices: []string{ + "vendor1.com/device=dev1", + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + "VENDOR1_SPEC_VAR1=VAL1", + "VENDOR1_VAR1=VAL1", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + { + Path: "/dev/vendor1-dev1", + Type: "b", + Major: 10, + Minor: 1, + }, + }, + Resources: &oci.LinuxResources{ + Devices: []oci.LinuxDeviceCgroup{ + { + Allow: true, + Type: "b", + Major: int64ptr(10), + Minor: int64ptr(1), + Access: "rwm", + }, + }, + }, + }, + }, + }, + { + name: "non-empty OCI Spec, inject one device by alias", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.6.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "notdev1" + aliases: ["dev1"] + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" + type: b + major: 10 + minor: 1 `, }, }, diff --git a/pkg/cdi/device.go b/pkg/cdi/device.go index 7d20e176..466e5f8b 100644 --- a/pkg/cdi/device.go +++ b/pkg/cdi/device.go @@ -49,11 +49,27 @@ func (d *Device) GetSpec() *Spec { return d.spec } +// GetNames returns the possible names for the device including aliases +func (d *Device) GetNames() []string { + names := []string{d.Name} + return append(names, d.Aliases...) +} + // GetQualifiedName returns the qualified name for this device. func (d *Device) GetQualifiedName() string { return QualifiedName(d.spec.GetVendor(), d.spec.GetClass(), d.Name) } +// GetQualifiedNames returns the qualified names for this device and its aliases. +func (d *Device) GetQualifiedNames() []string { + var qualifiedNames []string + for _, name := range d.GetNames() { + qualified := QualifiedName(d.spec.GetVendor(), d.spec.GetClass(), name) + qualifiedNames = append(qualifiedNames, qualified) + } + return qualifiedNames +} + // ApplyEdits applies the device-speific container edits to an OCI Spec. func (d *Device) ApplyEdits(ociSpec *oci.Spec) error { return d.edits().Apply(ociSpec) @@ -66,8 +82,10 @@ func (d *Device) edits() *ContainerEdits { // Validate the device. func (d *Device) validate() error { - if err := ValidateDeviceName(d.Name); err != nil { - return err + for _, name := range d.GetNames() { + if err := ValidateDeviceName(name); err != nil { + return err + } } name := d.Name if d.spec != nil { diff --git a/pkg/cdi/spec.go b/pkg/cdi/spec.go index 29220639..ce2d8647 100644 --- a/pkg/cdi/spec.go +++ b/pkg/cdi/spec.go @@ -233,10 +233,16 @@ func (s *Spec) validate() (map[string]*Device, error) { if err != nil { return nil, fmt.Errorf("failed add device %q: %w", d.Name, err) } - if _, conflict := devices[d.Name]; conflict { - return nil, fmt.Errorf("invalid spec, multiple device %q", d.Name) + for _, name := range dev.GetNames() { + if _, conflict := devices[name]; conflict { + var additional string + if name != d.Name { + additional = fmt.Sprintf(" (alias of %q)", d.Name) + } + return nil, fmt.Errorf("invalid spec, multiple device name: %q%s", name, additional) + } + devices[name] = dev } - devices[d.Name] = dev } return devices, nil diff --git a/pkg/cdi/spec_test.go b/pkg/cdi/spec_test.go index f84eeafe..c23b4f4e 100644 --- a/pkg/cdi/spec_test.go +++ b/pkg/cdi/spec_test.go @@ -661,6 +661,23 @@ func TestRequiredVersion(t *testing.T) { }, expectedVersion: "0.6.0", }, + { + description: "device aliases require v0.6.0", + spec: &cdi.Spec{ + Devices: []cdi.Device{ + { + Name: "device0", + Aliases: []string{"0", "zero"}, + ContainerEdits: cdi.ContainerEdits{ + Env: []string{ + "FOO=bar", + }, + }, + }, + }, + }, + expectedVersion: "0.6.0", + }, } for _, tc := range testCases { diff --git a/pkg/cdi/version.go b/pkg/cdi/version.go index a4baaa7d..470a03ed 100644 --- a/pkg/cdi/version.go +++ b/pkg/cdi/version.go @@ -124,8 +124,12 @@ func requiresV060(spec *cdi.Spec) bool { return true } - // The v0.6.0 spec allows annotations to be specified at a device level for _, d := range spec.Devices { + // The v0.6.0 spec allows aliases for device names + for range d.Aliases { + return true + } + // The v0.6.0 spec allows annotations to be specified at a device level for range d.Annotations { return true } diff --git a/schema/schema.json b/schema/schema.json index a8e57cc0..98a8c35e 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -23,6 +23,13 @@ "description": "The name of the device", "type": "string" }, + "aliases": { + "description": "Alternative names for the device", + "type": "array", + "items": { + "type": "string" + } + }, "annotations": { "$ref": "defs.json#/definitions/annotations" }, diff --git a/specs-go/config.go b/specs-go/config.go index 4043b858..8e391931 100644 --- a/specs-go/config.go +++ b/specs-go/config.go @@ -18,6 +18,8 @@ type Spec struct { // Device is a "Device" a container runtime can add to a container type Device struct { Name string `json:"name"` + // Aliases provide alternative names for a device. The fully-qualified names for the devices must be globally unique. + Aliases []string `json:"aliases,omitempty"` // Annotations add meta information per device. Note these are CDI-specific and do not affect container metadata. Annotations map[string]string `json:"annotations,omitempty"` ContainerEdits ContainerEdits `json:"containerEdits"`