1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
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)
}
// UpdateOCISpecForDevicesWithSpec updates the given OCI spec based on the requested CDI devices using the given CDI specs.
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
_, device := extractVendor(d)
err = cdispec.ApplyOCIEditsForDevice(ociconfig, spec, device)
if err != nil {
return err
}
}
for _, spec := range edits {
if err := cdispec.ApplyOCIEdits(ociconfig, spec); err != nil {
return err
}
}
return nil
}
|