CDI: update go.mod and vendor deps
Signed-off-by: Ed Bartosh <eduard.bartosh@intel.com>
This commit is contained in:
201
vendor/github.com/container-orchestrated-devices/container-device-interface/LICENSE
generated
vendored
Normal file
201
vendor/github.com/container-orchestrated-devices/container-device-interface/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
139
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go
generated
vendored
Normal file
139
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
Copyright © 2021-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 (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// AnnotationPrefix is the prefix for CDI container annotation keys.
|
||||
AnnotationPrefix = "cdi.k8s.io/"
|
||||
)
|
||||
|
||||
// UpdateAnnotations updates annotations with a plugin-specific CDI device
|
||||
// injection request for the given devices. Upon any error a non-nil error
|
||||
// is returned and annotations are left intact. By convention plugin should
|
||||
// be in the format of "vendor.device-type".
|
||||
func UpdateAnnotations(annotations map[string]string, plugin string, deviceID string, devices []string) (map[string]string, error) {
|
||||
key, err := AnnotationKey(plugin, deviceID)
|
||||
if err != nil {
|
||||
return annotations, errors.Wrap(err, "CDI annotation failed")
|
||||
}
|
||||
if _, ok := annotations[key]; ok {
|
||||
return annotations, errors.Errorf("CDI annotation failed, key %q used", key)
|
||||
}
|
||||
value, err := AnnotationValue(devices)
|
||||
if err != nil {
|
||||
return annotations, errors.Wrap(err, "CDI annotation failed")
|
||||
}
|
||||
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations[key] = value
|
||||
|
||||
return annotations, nil
|
||||
}
|
||||
|
||||
// ParseAnnotations parses annotations for CDI device injection requests.
|
||||
// The keys and devices from all such requests are collected into slices
|
||||
// which are returned as the result. All devices are expected to be fully
|
||||
// qualified CDI device names. If any device fails this check empty slices
|
||||
// are returned along with a non-nil error. The annotations are expected
|
||||
// to be formatted by, or in a compatible fashion to UpdateAnnotations().
|
||||
func ParseAnnotations(annotations map[string]string) ([]string, []string, error) {
|
||||
var (
|
||||
keys []string
|
||||
devices []string
|
||||
)
|
||||
|
||||
for key, value := range annotations {
|
||||
if !strings.HasPrefix(key, AnnotationPrefix) {
|
||||
continue
|
||||
}
|
||||
for _, d := range strings.Split(value, ",") {
|
||||
if !IsQualifiedName(d) {
|
||||
return nil, nil, errors.Errorf("invalid CDI device name %q", d)
|
||||
}
|
||||
devices = append(devices, d)
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys, devices, nil
|
||||
}
|
||||
|
||||
// AnnotationKey returns a unique annotation key for an device allocation
|
||||
// by a K8s device plugin. pluginName should be in the format of
|
||||
// "vendor.device-type". deviceID is the ID of the device the plugin is
|
||||
// allocating. It is used to make sure that the generated key is unique
|
||||
// even if multiple allocations by a single plugin needs to be annotated.
|
||||
func AnnotationKey(pluginName, deviceID string) (string, error) {
|
||||
const maxNameLen = 63
|
||||
|
||||
if pluginName == "" {
|
||||
return "", errors.New("invalid plugin name, empty")
|
||||
}
|
||||
if deviceID == "" {
|
||||
return "", errors.New("invalid deviceID, empty")
|
||||
}
|
||||
|
||||
name := pluginName + "_" + strings.ReplaceAll(deviceID, "/", "_")
|
||||
|
||||
if len(name) > maxNameLen {
|
||||
return "", errors.Errorf("invalid plugin+deviceID %q, too long", name)
|
||||
}
|
||||
|
||||
if c := rune(name[0]); !isAlphaNumeric(c) {
|
||||
return "", errors.Errorf("invalid name %q, first '%c' should be alphanumeric",
|
||||
name, c)
|
||||
}
|
||||
if len(name) > 2 {
|
||||
for _, c := range name[1 : len(name)-1] {
|
||||
switch {
|
||||
case isAlphaNumeric(c):
|
||||
case c == '_' || c == '-' || c == '.':
|
||||
default:
|
||||
return "", errors.Errorf("invalid name %q, invalid charcter '%c'",
|
||||
name, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
if c := rune(name[len(name)-1]); !isAlphaNumeric(c) {
|
||||
return "", errors.Errorf("invalid name %q, last '%c' should be alphanumeric",
|
||||
name, c)
|
||||
}
|
||||
|
||||
return AnnotationPrefix + name, nil
|
||||
}
|
||||
|
||||
// AnnotationValue returns an annotation value for the given devices.
|
||||
func AnnotationValue(devices []string) (string, error) {
|
||||
value, sep := "", ""
|
||||
for _, d := range devices {
|
||||
if _, _, _, err := ParseQualifiedName(d); err != nil {
|
||||
return "", err
|
||||
}
|
||||
value += sep + d
|
||||
sep = ","
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
279
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
generated
vendored
Normal file
279
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
generated
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
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
|
||||
|
||||
if ociSpec == nil {
|
||||
return devices, errors.Errorf("can't inject devices, nil OCI Spec")
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
edits := &ContainerEdits{}
|
||||
specs := map[*Spec]struct{}{}
|
||||
|
||||
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{}{}
|
||||
edits.Append(d.GetSpec().edits())
|
||||
}
|
||||
edits.Append(d.edits())
|
||||
}
|
||||
|
||||
if unresolved != nil {
|
||||
return unresolved, errors.Errorf("unresolvable CDI devices %s",
|
||||
strings.Join(devices, ", "))
|
||||
}
|
||||
|
||||
if err := edits.Apply(ociSpec); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to inject devices")
|
||||
}
|
||||
|
||||
return nil, 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
|
||||
}
|
357
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go
generated
vendored
Normal file
357
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go
generated
vendored
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
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"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/container-orchestrated-devices/container-device-interface/specs-go"
|
||||
oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||
ocigen "github.com/opencontainers/runtime-tools/generate"
|
||||
|
||||
runc "github.com/opencontainers/runc/libcontainer/devices"
|
||||
)
|
||||
|
||||
const (
|
||||
// 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 {
|
||||
dev := d.ToOCI()
|
||||
if err := fillMissingInfo(&dev); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dev.UID == nil && spec.Process != nil {
|
||||
if uid := spec.Process.User.UID; uid > 0 {
|
||||
dev.UID = &uid
|
||||
}
|
||||
}
|
||||
if dev.GID == nil && spec.Process != nil {
|
||||
if gid := spec.Process.User.GID; gid > 0 {
|
||||
dev.GID = &gid
|
||||
}
|
||||
}
|
||||
|
||||
specgen.RemoveDevice(dev.Path)
|
||||
specgen.AddDevice(dev)
|
||||
|
||||
if dev.Type == "b" || dev.Type == "c" {
|
||||
access := d.Permissions
|
||||
if access == "" {
|
||||
access = "rwm"
|
||||
}
|
||||
specgen.AddLinuxResourcesDevice(true, dev.Type, &dev.Major, &dev.Minor, access)
|
||||
}
|
||||
}
|
||||
|
||||
if len(e.Mounts) > 0 {
|
||||
for _, m := range e.Mounts {
|
||||
specgen.RemoveMount(m.ContainerPath)
|
||||
specgen.AddMount(m.ToOCI())
|
||||
}
|
||||
sortMounts(&specgen)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Append other edits into this one. If called with a nil receiver,
|
||||
// allocates and returns newly allocated edits.
|
||||
func (e *ContainerEdits) Append(o *ContainerEdits) *ContainerEdits {
|
||||
if o == nil || o.ContainerEdits == nil {
|
||||
return e
|
||||
}
|
||||
if e == nil {
|
||||
e = &ContainerEdits{}
|
||||
}
|
||||
if e.ContainerEdits == nil {
|
||||
e.ContainerEdits = &specs.ContainerEdits{}
|
||||
}
|
||||
|
||||
e.Env = append(e.Env, o.Env...)
|
||||
e.DeviceNodes = append(e.DeviceNodes, o.DeviceNodes...)
|
||||
e.Hooks = append(e.Hooks, o.Hooks...)
|
||||
e.Mounts = append(e.Mounts, o.Mounts...)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// 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 {
|
||||
validTypes := map[string]struct{}{
|
||||
"": {},
|
||||
"b": {},
|
||||
"c": {},
|
||||
"u": {},
|
||||
"p": {},
|
||||
}
|
||||
|
||||
if d.Path == "" {
|
||||
return errors.New("invalid (empty) device path")
|
||||
}
|
||||
if _, ok := validTypes[d.Type]; !ok {
|
||||
return errors.Errorf("device %q: invalid type %q", d.Path, d.Type)
|
||||
}
|
||||
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{}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
if dev.Type == "" {
|
||||
dev.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 dev.Major == 0 && dev.Type != "p" {
|
||||
dev.Major = hostDev.Major
|
||||
dev.Minor = hostDev.Minor
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sortMounts sorts the mounts in the given OCI Spec.
|
||||
func sortMounts(specgen *ocigen.Generator) {
|
||||
mounts := specgen.Mounts()
|
||||
specgen.ClearMounts()
|
||||
sort.Sort(orderedMounts(mounts))
|
||||
specgen.Config.Mounts = mounts
|
||||
}
|
||||
|
||||
// orderedMounts defines how to sort an OCI Spec Mount slice.
|
||||
// This is the almost the same implementation sa used by CRI-O and Docker,
|
||||
// with a minor tweak for stable sorting order (easier to test):
|
||||
// https://github.com/moby/moby/blob/17.05.x/daemon/volumes.go#L26
|
||||
type orderedMounts []oci.Mount
|
||||
|
||||
// Len returns the number of mounts. Used in sorting.
|
||||
func (m orderedMounts) Len() int {
|
||||
return len(m)
|
||||
}
|
||||
|
||||
// Less returns true if the number of parts (a/b/c would be 3 parts) in the
|
||||
// mount indexed by parameter 1 is less than that of the mount indexed by
|
||||
// parameter 2. Used in sorting.
|
||||
func (m orderedMounts) Less(i, j int) bool {
|
||||
ip, jp := m.parts(i), m.parts(j)
|
||||
if ip < jp {
|
||||
return true
|
||||
}
|
||||
if jp < ip {
|
||||
return false
|
||||
}
|
||||
return m[i].Destination < m[j].Destination
|
||||
}
|
||||
|
||||
// Swap swaps two items in an array of mounts. Used in sorting
|
||||
func (m orderedMounts) Swap(i, j int) {
|
||||
m[i], m[j] = m[j], m[i]
|
||||
}
|
||||
|
||||
// parts returns the number of parts in the destination of a mount. Used in sorting.
|
||||
func (m orderedMounts) parts(i int) int {
|
||||
return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
|
||||
}
|
78
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go
generated
vendored
Normal file
78
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 {
|
||||
return d.edits().Apply(ociSpec)
|
||||
}
|
||||
|
||||
// edits returns the applicable container edits for this spec.
|
||||
func (d *Device) edits() *ContainerEdits {
|
||||
return &ContainerEdits{&d.ContainerEdits}
|
||||
}
|
||||
|
||||
// Validate the device.
|
||||
func (d *Device) validate() error {
|
||||
if err := ValidateDeviceName(d.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
edits := d.edits()
|
||||
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
|
||||
}
|
150
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go
generated
vendored
Normal file
150
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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'.
|
||||
//
|
||||
// CDI Spec Validation
|
||||
//
|
||||
// This package performs both syntactic and semantic validation of CDI
|
||||
// Spec file data when a Spec file is loaded via the registry or using
|
||||
// the ReadSpec API function. As part of the semantic verification, the
|
||||
// Spec file is verified against the CDI Spec JSON validation schema.
|
||||
//
|
||||
// If a valid externally provided JSON validation schema is found in
|
||||
// the filesystem at /etc/cdi/schema/schema.json it is loaded and used
|
||||
// as the default validation schema. If such a file is not found or
|
||||
// fails to load, an embedded no-op schema is used.
|
||||
//
|
||||
// The used validation schema can also be changed programmatically using
|
||||
// the SetSchema API convenience function. This function also accepts
|
||||
// the special "builtin" (BuiltinSchemaName) and "none" (NoneSchemaName)
|
||||
// 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.
|
||||
package cdi
|
203
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go
generated
vendored
Normal file
203
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
139
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go
generated
vendored
Normal file
139
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
46
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/schema.go
generated
vendored
Normal file
46
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/schema.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/container-orchestrated-devices/container-device-interface/schema"
|
||||
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultExternalSchema is the JSON schema to load if found.
|
||||
DefaultExternalSchema = "/etc/cdi/schema/schema.json"
|
||||
)
|
||||
|
||||
// SetSchema sets the Spec JSON validation schema to use.
|
||||
func SetSchema(name string) error {
|
||||
s, err := schema.Load(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schema.Set(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate CDI Spec against JSON Schema.
|
||||
func validateWithSchema(raw *cdi.Spec) error {
|
||||
return schema.ValidateType(raw)
|
||||
}
|
||||
|
||||
func init() {
|
||||
SetSchema(DefaultExternalSchema)
|
||||
}
|
110
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec-dirs.go
generated
vendored
Normal file
110
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec-dirs.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
190
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
generated
vendored
Normal file
190
vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
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": {},
|
||||
"0.3.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)
|
||||
}
|
||||
if raw == nil {
|
||||
return nil, errors.Errorf("failed to parse CDI Spec %q, no Spec data", path)
|
||||
}
|
||||
|
||||
spec, err := NewSpec(raw, path, priority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
err := validateWithSchema(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
return s.edits().Apply(ociSpec)
|
||||
}
|
||||
|
||||
// edits returns the applicable global container edits for this spec.
|
||||
func (s *Spec) edits() *ContainerEdits {
|
||||
return &ContainerEdits{&s.ContainerEdits}
|
||||
}
|
||||
|
||||
// Validate the Spec.
|
||||
func (s *Spec) validate() (map[string]*Device, error) {
|
||||
if err := validateVersion(s.Version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ValidateVendorName(s.vendor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ValidateClassName(s.class); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.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
|
||||
}
|
||||
|
||||
// validateVersion checks whether the specified spec version is supported.
|
||||
func validateVersion(version string) error {
|
||||
if _, ok := validSpecVersions[version]; !ok {
|
||||
return errors.Errorf("invalid version %q", version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse raw CDI Spec file data.
|
||||
func parseSpec(data []byte) (*cdi.Spec, error) {
|
||||
var raw *cdi.Spec
|
||||
err := yaml.UnmarshalStrict(data, &raw)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal CDI Spec")
|
||||
}
|
||||
return raw, nil
|
||||
}
|
27
vendor/github.com/container-orchestrated-devices/container-device-interface/schema/Makefile
generated
vendored
Normal file
27
vendor/github.com/container-orchestrated-devices/container-device-interface/schema/Makefile
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
VALIDATE ?= ../bin/validate
|
||||
SCHEMA ?= schema.json
|
||||
|
||||
test:
|
||||
@FMT_RED=$$(tput setaf 1); \
|
||||
FMT_BLUE=$$(tput setaf 12); \
|
||||
FMT_CLEAR=$$(tput sgr0); \
|
||||
echo "Running Good Tests"; \
|
||||
for FILE in $$(ls "testdata/good"); do \
|
||||
FILE_PATH="testdata/good/$${FILE}"; \
|
||||
if $(VALIDATE) --schema "$(SCHEMA)" "$${FILE_PATH}" > /dev/null ; then \
|
||||
printf '%s[OK]%s %s\n' "$${FMT_BLUE}" "$${FMT_CLEAR}" "$${FILE_PATH}"; \
|
||||
else \
|
||||
printf '%s[KO]%s %s\n' "$${FMT_RED}" "$${FMT_CLEAR}" "$${FILE_PATH}"; \
|
||||
exit 1; \
|
||||
fi \
|
||||
done; \
|
||||
echo "Running Bad Tests"; \
|
||||
for FILE in $$(ls "testdata/bad"); do \
|
||||
FILE_PATH="testdata/bad/$${FILE}"; \
|
||||
if $(VALIDATE) --schema "$(SCHEMA)" "$${FILE_PATH}" > /dev/null ; then \
|
||||
printf '%s[KO]%s %s\n' "$${FMT_RED}" "$${FMT_CLEAR}" "$${FILE_PATH}"; \
|
||||
exit 1; \
|
||||
else \
|
||||
printf '%s[OK]%s %s\n' "$${FMT_BLUE}" "$${FMT_CLEAR}" "$${FILE_PATH}"; \
|
||||
fi \
|
||||
done
|
127
vendor/github.com/container-orchestrated-devices/container-device-interface/schema/defs.json
generated
vendored
Normal file
127
vendor/github.com/container-orchestrated-devices/container-device-interface/schema/defs.json
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"description": "Definitions used throughout the Container Device Interface Specification",
|
||||
"definitions": {
|
||||
"uint32": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 4294967295
|
||||
},
|
||||
"int64": {
|
||||
"type": "integer",
|
||||
"minimum": -9223372036854775808,
|
||||
"maximum": 9223372036854775807
|
||||
},
|
||||
"ArrayOfStrings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"FilePath": {
|
||||
"type": "string"
|
||||
},
|
||||
"Env": {
|
||||
"$ref": "#/definitions/ArrayOfStrings"
|
||||
},
|
||||
"DeviceNode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/FilePath"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"major": {
|
||||
"$ref": "#/definitions/int64"
|
||||
},
|
||||
"minor": {
|
||||
"$ref": "#/definitions/int64"
|
||||
},
|
||||
"uid": {
|
||||
"$ref": "#/definitions/uint32"
|
||||
},
|
||||
"gid": {
|
||||
"$ref": "#/definitions/uint32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
]
|
||||
},
|
||||
"Mount": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hostPath": {
|
||||
"$ref": "#/definitions/FilePath"
|
||||
},
|
||||
"containerPath": {
|
||||
"$ref": "#/definitions/FilePath"
|
||||
},
|
||||
"options": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hostPath",
|
||||
"containerPath"
|
||||
]
|
||||
},
|
||||
"Hook": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hookName": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FilePath"
|
||||
},
|
||||
"args": {
|
||||
"$ref": "#/definitions/ArrayOfStrings"
|
||||
},
|
||||
"env": {
|
||||
"$ref": "#/definitions/ArrayOfStrings"
|
||||
},
|
||||
"timeout": {
|
||||
"$ref": "#/definitions/uint32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookName",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
"containerEdits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"env": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"ref": "#definitions/Env"
|
||||
}
|
||||
},
|
||||
"deviceNodes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/DeviceNode"
|
||||
}
|
||||
},
|
||||
"mounts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Mount"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Hook"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
253
vendor/github.com/container-orchestrated-devices/container-device-interface/schema/schema.go
generated
vendored
Normal file
253
vendor/github.com/container-orchestrated-devices/container-device-interface/schema/schema.go
generated
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
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 schema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
schema "github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
const (
|
||||
// BuiltinSchemaName references the builtin schema for Load()/Set().
|
||||
BuiltinSchemaName = "builtin"
|
||||
// NoneSchemaName references a disabled/NOP schema for Load()/Set().
|
||||
NoneSchemaName = "none"
|
||||
// DefaultSchemaName is the none schema.
|
||||
DefaultSchemaName = NoneSchemaName
|
||||
|
||||
// builtinSchemaFile is the builtin schema URI in our embedded FS.
|
||||
builtinSchemaFile = "file:///schema.json"
|
||||
)
|
||||
|
||||
// Schema is a JSON validation schema.
|
||||
type Schema struct {
|
||||
schema *schema.Schema
|
||||
}
|
||||
|
||||
// Error wraps a JSON validation result.
|
||||
type Error struct {
|
||||
Result *schema.Result
|
||||
}
|
||||
|
||||
// Set sets the default validating JSON schema.
|
||||
func Set(s *Schema) {
|
||||
current = s
|
||||
}
|
||||
|
||||
// Get returns the active validating JSON schema.
|
||||
func Get() *Schema {
|
||||
return current
|
||||
}
|
||||
|
||||
// BuiltinSchema returns the builtin validating JSON Schema.
|
||||
func BuiltinSchema() *Schema {
|
||||
return builtin
|
||||
}
|
||||
|
||||
// NopSchema returns an validating JSON Schema that does no real validation.
|
||||
func NopSchema() *Schema {
|
||||
return &Schema{}
|
||||
}
|
||||
|
||||
// ReadAndValidate all data from the given reader, using the active schema for validation.
|
||||
func ReadAndValidate(r io.Reader) ([]byte, error) {
|
||||
return current.ReadAndValidate(r)
|
||||
}
|
||||
|
||||
// Validate validates the data read from an io.Reader against the active schema.
|
||||
func Validate(r io.Reader) error {
|
||||
return current.Validate(r)
|
||||
}
|
||||
|
||||
// ValidateData validates the given JSON document against the active schema.
|
||||
func ValidateData(data []byte) error {
|
||||
return current.ValidateData(data)
|
||||
}
|
||||
|
||||
// ValidateFile validates the given JSON file against the active schema.
|
||||
func ValidateFile(path string) error {
|
||||
return current.ValidateFile(path)
|
||||
}
|
||||
|
||||
// ValidateType validates a go object against the schema.
|
||||
func ValidateType(obj interface{}) error {
|
||||
return current.ValidateType(obj)
|
||||
}
|
||||
|
||||
// Load the given JSON Schema.
|
||||
func Load(source string) (*Schema, error) {
|
||||
var (
|
||||
loader schema.JSONLoader
|
||||
err error
|
||||
s *schema.Schema
|
||||
)
|
||||
|
||||
source = strings.TrimSpace(source)
|
||||
|
||||
switch {
|
||||
case source == BuiltinSchemaName:
|
||||
return BuiltinSchema(), nil
|
||||
case source == NoneSchemaName, source == "":
|
||||
return NopSchema(), nil
|
||||
case strings.HasPrefix(source, "file://"):
|
||||
case strings.HasPrefix(source, "http://"):
|
||||
case strings.HasPrefix(source, "https://"):
|
||||
default:
|
||||
if strings.Index(source, "://") < 0 {
|
||||
source, err = filepath.Abs(source)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err,
|
||||
"failed to get JSON schema absolute path for %s", source)
|
||||
}
|
||||
source = "file://" + source
|
||||
}
|
||||
}
|
||||
|
||||
loader = schema.NewReferenceLoader(source)
|
||||
|
||||
s, err = schema.NewSchema(loader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load JSON schema")
|
||||
}
|
||||
|
||||
return &Schema{schema: s}, nil
|
||||
}
|
||||
|
||||
// ReadAndValidate all data from the given reader, using the schema for validation.
|
||||
func (s *Schema) ReadAndValidate(r io.Reader) ([]byte, error) {
|
||||
loader, reader := schema.NewReaderLoader(r)
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read data for validation")
|
||||
}
|
||||
return data, s.validate(loader)
|
||||
}
|
||||
|
||||
// Validate validates the data read from an io.Reader against the schema.
|
||||
func (s *Schema) Validate(r io.Reader) error {
|
||||
_, err := s.ReadAndValidate(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateData validates the given JSON data against the schema.
|
||||
func (s *Schema) ValidateData(data []byte) error {
|
||||
var (
|
||||
any interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
if !bytes.HasPrefix(bytes.TrimSpace(data), []byte{'{'}) {
|
||||
err = yaml.Unmarshal(data, &any)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to YAML unmarshal data for validation")
|
||||
}
|
||||
data, err = json.Marshal(any)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to JSON remarshal data for validation")
|
||||
}
|
||||
}
|
||||
|
||||
return s.validate(schema.NewBytesLoader(data))
|
||||
}
|
||||
|
||||
// ValidateFile validates the given JSON file against the schema.
|
||||
func (s *Schema) ValidateFile(path string) error {
|
||||
if filepath.Ext(path) == ".json" {
|
||||
return s.validate(schema.NewReferenceLoader("file://" + path))
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.ValidateData(data)
|
||||
}
|
||||
|
||||
// ValidateType validates a go object against the schema.
|
||||
func (s *Schema) ValidateType(obj interface{}) error {
|
||||
l := schema.NewGoLoader(obj)
|
||||
return s.validate(l)
|
||||
}
|
||||
|
||||
// Validate the (to be) loaded doc against the schema.
|
||||
func (s *Schema) validate(doc schema.JSONLoader) error {
|
||||
if s == nil || s.schema == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
docErr, jsonErr := s.schema.Validate(doc)
|
||||
if jsonErr != nil {
|
||||
return errors.Wrap(jsonErr, "failed to load JSON data for validation")
|
||||
}
|
||||
if docErr.Valid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Error{Result: docErr}
|
||||
}
|
||||
|
||||
// Error returns the given Result's error as a multierror(.Error()).
|
||||
func (e *Error) Error() string {
|
||||
if e == nil || e.Result == nil || e.Result.Valid() {
|
||||
return ""
|
||||
}
|
||||
|
||||
var multi error
|
||||
for _, err := range e.Result.Errors() {
|
||||
multi = multierror.Append(multi, errors.Errorf("%v", err))
|
||||
}
|
||||
return strings.TrimRight(multi.Error(), "\n")
|
||||
}
|
||||
|
||||
var (
|
||||
// our builtin schema
|
||||
builtin *Schema
|
||||
// currently loaded schema, builtin by default
|
||||
current *Schema
|
||||
)
|
||||
|
||||
//go:embed *.json
|
||||
var builtinFS embed.FS
|
||||
|
||||
func init() {
|
||||
s, err := schema.NewSchema(
|
||||
schema.NewReferenceLoaderFileSystem(
|
||||
builtinSchemaFile,
|
||||
http.FS(builtinFS),
|
||||
),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
builtin = NopSchema()
|
||||
} else {
|
||||
builtin = &Schema{schema: s}
|
||||
}
|
||||
|
||||
current = builtin
|
||||
}
|
39
vendor/github.com/container-orchestrated-devices/container-device-interface/schema/schema.json
generated
vendored
Normal file
39
vendor/github.com/container-orchestrated-devices/container-device-interface/schema/schema.json
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"description": "Configuration Schema for the Container Device Interface",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cdiVersion": {
|
||||
"description": "The version of the Container Device Interface Specification that the document complies with",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The kind of the device usually of the form 'vendor.com/device'",
|
||||
"type": "string"
|
||||
},
|
||||
"devices": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The name of the device",
|
||||
"type": "string"
|
||||
},
|
||||
"containerEdits": {
|
||||
"$ref": "defs.json#/definitions/containerEdits"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"containerEdits"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cdiVersion",
|
||||
"kind",
|
||||
"devices"
|
||||
]
|
||||
}
|
57
vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go
generated
vendored
Normal file
57
vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package specs
|
||||
|
||||
import "os"
|
||||
|
||||
// CurrentVersion is the current version of the Spec.
|
||||
const CurrentVersion = "0.3.0"
|
||||
|
||||
// Spec is the base configuration for CDI
|
||||
type Spec struct {
|
||||
Version string `json:"cdiVersion"`
|
||||
Kind string `json:"kind"`
|
||||
|
||||
Devices []Device `json:"devices"`
|
||||
ContainerEdits ContainerEdits `json:"containerEdits,omitempty"`
|
||||
}
|
||||
|
||||
// Device is a "Device" a container runtime can add to a container
|
||||
type Device struct {
|
||||
Name string `json:"name"`
|
||||
ContainerEdits ContainerEdits `json:"containerEdits"`
|
||||
}
|
||||
|
||||
// ContainerEdits are edits a container runtime must make to the OCI spec to expose the device.
|
||||
type ContainerEdits struct {
|
||||
Env []string `json:"env,omitempty"`
|
||||
DeviceNodes []*DeviceNode `json:"deviceNodes,omitempty"`
|
||||
Hooks []*Hook `json:"hooks,omitempty"`
|
||||
Mounts []*Mount `json:"mounts,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceNode represents a device node that needs to be added to the OCI spec.
|
||||
type DeviceNode struct {
|
||||
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.
|
||||
type Mount struct {
|
||||
HostPath string `json:"hostPath"`
|
||||
ContainerPath string `json:"containerPath"`
|
||||
Options []string `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
// Hook represents a hook that needs to be added to the OCI spec.
|
||||
type Hook struct {
|
||||
HookName string `json:"hookName"`
|
||||
Path string `json:"path"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
Env []string `json:"env,omitempty"`
|
||||
Timeout *int `json:"timeout,omitempty"`
|
||||
}
|
112
vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go
generated
vendored
Normal file
112
vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
package specs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// ApplyOCIEditsForDevice applies devices OCI edits, in other words
|
||||
// it finds the device in the CDI spec and applies the OCI patches that device
|
||||
// requires to the OCI specification.
|
||||
func ApplyOCIEditsForDevice(config *spec.Spec, cdi *Spec, dev string) error {
|
||||
for _, d := range cdi.Devices {
|
||||
if d.Name != dev {
|
||||
continue
|
||||
}
|
||||
|
||||
return ApplyEditsToOCISpec(config, &d.ContainerEdits)
|
||||
}
|
||||
|
||||
return fmt.Errorf("CDI: device %q not found for spec %q", dev, cdi.Kind)
|
||||
}
|
||||
|
||||
// ApplyOCIEdits applies the OCI edits the CDI spec declares globablly
|
||||
func ApplyOCIEdits(config *spec.Spec, cdi *Spec) error {
|
||||
return ApplyEditsToOCISpec(config, &cdi.ContainerEdits)
|
||||
}
|
||||
|
||||
// ApplyEditsToOCISpec applies the specified edits to the OCI spec.
|
||||
func ApplyEditsToOCISpec(config *spec.Spec, edits *ContainerEdits) error {
|
||||
if config == nil {
|
||||
return errors.New("spec is nil")
|
||||
}
|
||||
if edits == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
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, m.ToOCI())
|
||||
}
|
||||
|
||||
for _, h := range edits.Hooks {
|
||||
if config.Hooks == nil {
|
||||
config.Hooks = &spec.Hooks{}
|
||||
}
|
||||
switch h.HookName {
|
||||
case "prestart":
|
||||
config.Hooks.Prestart = append(config.Hooks.Prestart, h.ToOCI())
|
||||
case "createRuntime":
|
||||
config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, h.ToOCI())
|
||||
case "createContainer":
|
||||
config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, h.ToOCI())
|
||||
case "startContainer":
|
||||
config.Hooks.StartContainer = append(config.Hooks.StartContainer, h.ToOCI())
|
||||
case "poststart":
|
||||
config.Hooks.Poststart = append(config.Hooks.Poststart, h.ToOCI())
|
||||
case "poststop":
|
||||
config.Hooks.Poststop = append(config.Hooks.Poststop, h.ToOCI())
|
||||
default:
|
||||
fmt.Printf("CDI: Unknown hook %q\n", h.HookName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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,
|
||||
Env: h.Env,
|
||||
Timeout: h.Timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
Options: m.Options,
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user