Update container-device-interface to v0.6.2
This includes migrating from the github.com/container-orchestrated-devices repo to tags.cncf.io. Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
		
							
								
								
									
										201
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										201
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,201 +0,0 @@
 | 
			
		||||
                                 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.
 | 
			
		||||
@@ -1,82 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 multierror
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// New combines several errors into a single error. Parameters that are nil are
 | 
			
		||||
// ignored. If no errors are passed in or all parameters are nil, then the
 | 
			
		||||
// result is also nil.
 | 
			
		||||
func New(errors ...error) error {
 | 
			
		||||
	// Filter out nil entries.
 | 
			
		||||
	numErrors := 0
 | 
			
		||||
	for _, err := range errors {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errors[numErrors] = err
 | 
			
		||||
			numErrors++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if numErrors == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return multiError(errors[0:numErrors])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// multiError is the underlying implementation used by New.
 | 
			
		||||
//
 | 
			
		||||
// Beware that a null multiError is not the same as a nil error.
 | 
			
		||||
type multiError []error
 | 
			
		||||
 | 
			
		||||
// multiError returns all individual error strings concatenated with "\n"
 | 
			
		||||
func (e multiError) Error() string {
 | 
			
		||||
	var builder strings.Builder
 | 
			
		||||
	for i, err := range e {
 | 
			
		||||
		if i > 0 {
 | 
			
		||||
			_, _ = builder.WriteString("\n")
 | 
			
		||||
		}
 | 
			
		||||
		_, _ = builder.WriteString(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return builder.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Append returns a new multi error all errors concatenated. Errors that are
 | 
			
		||||
// multi errors get flattened, nil is ignored.
 | 
			
		||||
func Append(err error, errors ...error) error {
 | 
			
		||||
	var result multiError
 | 
			
		||||
	if m, ok := err.(multiError); ok {
 | 
			
		||||
		result = m
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		result = append(result, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, e := range errors {
 | 
			
		||||
		if e == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if m, ok := e.(multiError); ok {
 | 
			
		||||
			result = append(result, m...)
 | 
			
		||||
		} else {
 | 
			
		||||
			result = append(result, e)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(result) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
@@ -1,57 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2014 The Kubernetes 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.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// Adapted from k8s.io/apimachinery/pkg/api/validation:
 | 
			
		||||
// https://github.com/kubernetes/apimachinery/blob/7687996c715ee7d5c8cf1e3215e607eb065a4221/pkg/api/validation/objectmeta.go
 | 
			
		||||
 | 
			
		||||
package k8s
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/internal/multierror"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TotalAnnotationSizeLimitB defines the maximum size of all annotations in characters.
 | 
			
		||||
const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
 | 
			
		||||
 | 
			
		||||
// ValidateAnnotations validates that a set of annotations are correctly defined.
 | 
			
		||||
func ValidateAnnotations(annotations map[string]string, path string) error {
 | 
			
		||||
	errors := multierror.New()
 | 
			
		||||
	for k := range annotations {
 | 
			
		||||
		// The rule is QualifiedName except that case doesn't matter, so convert to lowercase before checking.
 | 
			
		||||
		for _, msg := range IsQualifiedName(strings.ToLower(k)) {
 | 
			
		||||
			errors = multierror.Append(errors, fmt.Errorf("%v.%v is invalid: %v", path, k, msg))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := ValidateAnnotationsSize(annotations); err != nil {
 | 
			
		||||
		errors = multierror.Append(errors, fmt.Errorf("%v is too long: %v", path, err))
 | 
			
		||||
	}
 | 
			
		||||
	return errors
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateAnnotationsSize validates that a set of annotations is not too large.
 | 
			
		||||
func ValidateAnnotationsSize(annotations map[string]string) error {
 | 
			
		||||
	var totalSize int64
 | 
			
		||||
	for k, v := range annotations {
 | 
			
		||||
		totalSize += (int64)(len(k)) + (int64)(len(v))
 | 
			
		||||
	}
 | 
			
		||||
	if totalSize > (int64)(TotalAnnotationSizeLimitB) {
 | 
			
		||||
		return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,217 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2014 The Kubernetes 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.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// Adapted from k8s.io/apimachinery/pkg/util/validation:
 | 
			
		||||
// https://github.com/kubernetes/apimachinery/blob/7687996c715ee7d5c8cf1e3215e607eb065a4221/pkg/util/validation/validation.go
 | 
			
		||||
 | 
			
		||||
package k8s
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const qnameCharFmt string = "[A-Za-z0-9]"
 | 
			
		||||
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
 | 
			
		||||
const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
 | 
			
		||||
const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
 | 
			
		||||
const qualifiedNameMaxLength int = 63
 | 
			
		||||
 | 
			
		||||
var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
 | 
			
		||||
 | 
			
		||||
// IsQualifiedName tests whether the value passed is what Kubernetes calls a
 | 
			
		||||
// "qualified name".  This is a format used in various places throughout the
 | 
			
		||||
// system.  If the value is not valid, a list of error strings is returned.
 | 
			
		||||
// Otherwise an empty list (or nil) is returned.
 | 
			
		||||
func IsQualifiedName(value string) []string {
 | 
			
		||||
	var errs []string
 | 
			
		||||
	parts := strings.Split(value, "/")
 | 
			
		||||
	var name string
 | 
			
		||||
	switch len(parts) {
 | 
			
		||||
	case 1:
 | 
			
		||||
		name = parts[0]
 | 
			
		||||
	case 2:
 | 
			
		||||
		var prefix string
 | 
			
		||||
		prefix, name = parts[0], parts[1]
 | 
			
		||||
		if len(prefix) == 0 {
 | 
			
		||||
			errs = append(errs, "prefix part "+EmptyError())
 | 
			
		||||
		} else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
 | 
			
		||||
			errs = append(errs, prefixEach(msgs, "prefix part ")...)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
 | 
			
		||||
			" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(name) == 0 {
 | 
			
		||||
		errs = append(errs, "name part "+EmptyError())
 | 
			
		||||
	} else if len(name) > qualifiedNameMaxLength {
 | 
			
		||||
		errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength))
 | 
			
		||||
	}
 | 
			
		||||
	if !qualifiedNameRegexp.MatchString(name) {
 | 
			
		||||
		errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"))
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const labelValueFmt string = "(" + qualifiedNameFmt + ")?"
 | 
			
		||||
const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
 | 
			
		||||
 | 
			
		||||
// LabelValueMaxLength is a label's max length
 | 
			
		||||
const LabelValueMaxLength int = 63
 | 
			
		||||
 | 
			
		||||
var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
 | 
			
		||||
 | 
			
		||||
// IsValidLabelValue tests whether the value passed is a valid label value.  If
 | 
			
		||||
// the value is not valid, a list of error strings is returned.  Otherwise an
 | 
			
		||||
// empty list (or nil) is returned.
 | 
			
		||||
func IsValidLabelValue(value string) []string {
 | 
			
		||||
	var errs []string
 | 
			
		||||
	if len(value) > LabelValueMaxLength {
 | 
			
		||||
		errs = append(errs, MaxLenError(LabelValueMaxLength))
 | 
			
		||||
	}
 | 
			
		||||
	if !labelValueRegexp.MatchString(value) {
 | 
			
		||||
		errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
 | 
			
		||||
const dns1123LabelErrMsg string = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
 | 
			
		||||
 | 
			
		||||
// DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123)
 | 
			
		||||
const DNS1123LabelMaxLength int = 63
 | 
			
		||||
 | 
			
		||||
var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
 | 
			
		||||
 | 
			
		||||
// IsDNS1123Label tests for a string that conforms to the definition of a label in
 | 
			
		||||
// DNS (RFC 1123).
 | 
			
		||||
func IsDNS1123Label(value string) []string {
 | 
			
		||||
	var errs []string
 | 
			
		||||
	if len(value) > DNS1123LabelMaxLength {
 | 
			
		||||
		errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
 | 
			
		||||
	}
 | 
			
		||||
	if !dns1123LabelRegexp.MatchString(value) {
 | 
			
		||||
		errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
 | 
			
		||||
const dns1123SubdomainErrorMsg string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
 | 
			
		||||
 | 
			
		||||
// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
 | 
			
		||||
const DNS1123SubdomainMaxLength int = 253
 | 
			
		||||
 | 
			
		||||
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
 | 
			
		||||
 | 
			
		||||
// IsDNS1123Subdomain tests for a string that conforms to the definition of a
 | 
			
		||||
// subdomain in DNS (RFC 1123).
 | 
			
		||||
func IsDNS1123Subdomain(value string) []string {
 | 
			
		||||
	var errs []string
 | 
			
		||||
	if len(value) > DNS1123SubdomainMaxLength {
 | 
			
		||||
		errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
 | 
			
		||||
	}
 | 
			
		||||
	if !dns1123SubdomainRegexp.MatchString(value) {
 | 
			
		||||
		errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
 | 
			
		||||
const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"
 | 
			
		||||
 | 
			
		||||
// DNS1035LabelMaxLength is a label's max length in DNS (RFC 1035)
 | 
			
		||||
const DNS1035LabelMaxLength int = 63
 | 
			
		||||
 | 
			
		||||
var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
 | 
			
		||||
 | 
			
		||||
// IsDNS1035Label tests for a string that conforms to the definition of a label in
 | 
			
		||||
// DNS (RFC 1035).
 | 
			
		||||
func IsDNS1035Label(value string) []string {
 | 
			
		||||
	var errs []string
 | 
			
		||||
	if len(value) > DNS1035LabelMaxLength {
 | 
			
		||||
		errs = append(errs, MaxLenError(DNS1035LabelMaxLength))
 | 
			
		||||
	}
 | 
			
		||||
	if !dns1035LabelRegexp.MatchString(value) {
 | 
			
		||||
		errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// wildcard definition - RFC 1034 section 4.3.3.
 | 
			
		||||
// examples:
 | 
			
		||||
// - valid: *.bar.com, *.foo.bar.com
 | 
			
		||||
// - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, *
 | 
			
		||||
const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt
 | 
			
		||||
const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character"
 | 
			
		||||
 | 
			
		||||
// IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a
 | 
			
		||||
// wildcard subdomain in DNS (RFC 1034 section 4.3.3).
 | 
			
		||||
func IsWildcardDNS1123Subdomain(value string) []string {
 | 
			
		||||
	wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$")
 | 
			
		||||
 | 
			
		||||
	var errs []string
 | 
			
		||||
	if len(value) > DNS1123SubdomainMaxLength {
 | 
			
		||||
		errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
 | 
			
		||||
	}
 | 
			
		||||
	if !wildcardDNS1123SubdomainRegexp.MatchString(value) {
 | 
			
		||||
		errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com"))
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MaxLenError returns a string explanation of a "string too long" validation
 | 
			
		||||
// failure.
 | 
			
		||||
func MaxLenError(length int) string {
 | 
			
		||||
	return fmt.Sprintf("must be no more than %d characters", length)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegexError returns a string explanation of a regex validation failure.
 | 
			
		||||
func RegexError(msg string, fmt string, examples ...string) string {
 | 
			
		||||
	if len(examples) == 0 {
 | 
			
		||||
		return msg + " (regex used for validation is '" + fmt + "')"
 | 
			
		||||
	}
 | 
			
		||||
	msg += " (e.g. "
 | 
			
		||||
	for i := range examples {
 | 
			
		||||
		if i > 0 {
 | 
			
		||||
			msg += " or "
 | 
			
		||||
		}
 | 
			
		||||
		msg += "'" + examples[i] + "', "
 | 
			
		||||
	}
 | 
			
		||||
	msg += "regex used for validation is '" + fmt + "')"
 | 
			
		||||
	return msg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EmptyError returns a string explanation of a "must not be empty" validation
 | 
			
		||||
// failure.
 | 
			
		||||
func EmptyError() string {
 | 
			
		||||
	return "must be non-empty"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func prefixEach(msgs []string, prefix string) []string {
 | 
			
		||||
	for i := range msgs {
 | 
			
		||||
		msgs[i] = prefix + msgs[i]
 | 
			
		||||
	}
 | 
			
		||||
	return msgs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InclusiveRangeError returns a string explanation of a numeric "must be
 | 
			
		||||
// between" validation failure.
 | 
			
		||||
func InclusiveRangeError(lo, hi int) string {
 | 
			
		||||
	return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright © 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 validation
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/internal/validation/k8s"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ValidateSpecAnnotations checks whether spec annotations are valid.
 | 
			
		||||
func ValidateSpecAnnotations(name string, any interface{}) error {
 | 
			
		||||
	if any == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch v := any.(type) {
 | 
			
		||||
	case map[string]interface{}:
 | 
			
		||||
		annotations := make(map[string]string)
 | 
			
		||||
		for k, v := range v {
 | 
			
		||||
			if s, ok := v.(string); ok {
 | 
			
		||||
				annotations[k] = s
 | 
			
		||||
			} else {
 | 
			
		||||
				return fmt.Errorf("invalid annotation %v.%v; %v is not a string", name, k, any)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return validateSpecAnnotations(name, annotations)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateSpecAnnotations checks whether spec annotations are valid.
 | 
			
		||||
func validateSpecAnnotations(name string, annotations map[string]string) error {
 | 
			
		||||
	path := "annotations"
 | 
			
		||||
	if name != "" {
 | 
			
		||||
		path = strings.Join([]string{name, path}, ".")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return k8s.ValidateAnnotations(annotations, path)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,141 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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, fmt.Errorf("CDI annotation failed: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := annotations[key]; ok {
 | 
			
		||||
		return annotations, fmt.Errorf("CDI annotation failed, key %q used", key)
 | 
			
		||||
	}
 | 
			
		||||
	value, err := AnnotationValue(devices)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return annotations, fmt.Errorf("CDI annotation failed: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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, fmt.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 "", fmt.Errorf("invalid plugin+deviceID %q, too long", name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c := rune(name[0]); !parser.IsAlphaNumeric(c) {
 | 
			
		||||
		return "", fmt.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 parser.IsAlphaNumeric(c):
 | 
			
		||||
			case c == '_' || c == '-' || c == '.':
 | 
			
		||||
			default:
 | 
			
		||||
				return "", fmt.Errorf("invalid name %q, invalid character '%c'",
 | 
			
		||||
					name, c)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if c := rune(name[len(name)-1]); !parser.IsAlphaNumeric(c) {
 | 
			
		||||
		return "", fmt.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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										581
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										581
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,581 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/internal/multierror"
 | 
			
		||||
	cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
 | 
			
		||||
	"github.com/fsnotify/fsnotify"
 | 
			
		||||
	oci "github.com/opencontainers/runtime-spec/specs-go"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	dirErrors map[string]error
 | 
			
		||||
 | 
			
		||||
	autoRefresh bool
 | 
			
		||||
	watch       *watch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithAutoRefresh returns an option to control automatic Cache refresh.
 | 
			
		||||
// By default, auto-refresh is enabled, the list of Spec directories are
 | 
			
		||||
// monitored and the Cache is automatically refreshed whenever a change
 | 
			
		||||
// is detected. This option can be used to disable this behavior when a
 | 
			
		||||
// manually refreshed mode is preferable.
 | 
			
		||||
func WithAutoRefresh(autoRefresh bool) Option {
 | 
			
		||||
	return func(c *Cache) error {
 | 
			
		||||
		c.autoRefresh = autoRefresh
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCache creates a new CDI Cache. The cache is populated from a set
 | 
			
		||||
// of CDI Spec directories. These can be specified using a WithSpecDirs
 | 
			
		||||
// option. The default set of directories is exposed in DefaultSpecDirs.
 | 
			
		||||
func NewCache(options ...Option) (*Cache, error) {
 | 
			
		||||
	c := &Cache{
 | 
			
		||||
		autoRefresh: true,
 | 
			
		||||
		watch:       &watch{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	WithSpecDirs(DefaultSpecDirs...)(c)
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	return c, c.configure(options...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Configure applies options to the Cache. Updates and refreshes the
 | 
			
		||||
// Cache if options have changed.
 | 
			
		||||
func (c *Cache) Configure(options ...Option) error {
 | 
			
		||||
	if len(options) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	return c.configure(options...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Configure the Cache. Start/stop CDI Spec directory watch, refresh
 | 
			
		||||
// the Cache if necessary.
 | 
			
		||||
func (c *Cache) configure(options ...Option) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	for _, o := range options {
 | 
			
		||||
		if err = o(c); err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to apply cache options: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.dirErrors = make(map[string]error)
 | 
			
		||||
 | 
			
		||||
	c.watch.stop()
 | 
			
		||||
	if c.autoRefresh {
 | 
			
		||||
		c.watch.setup(c.specDirs, c.dirErrors)
 | 
			
		||||
		c.watch.start(&c.Mutex, c.refresh, c.dirErrors)
 | 
			
		||||
	}
 | 
			
		||||
	c.refresh()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Refresh rescans the CDI Spec directories and refreshes the Cache.
 | 
			
		||||
// In manual refresh mode the cache is always refreshed. In auto-
 | 
			
		||||
// refresh mode the cache is only refreshed if it is out of date.
 | 
			
		||||
func (c *Cache) Refresh() error {
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	// force a refresh in manual mode
 | 
			
		||||
	if refreshed, err := c.refreshIfRequired(!c.autoRefresh); refreshed {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// collect and return cached errors, much like refresh() does it
 | 
			
		||||
	var result error
 | 
			
		||||
	for _, errors := range c.errors {
 | 
			
		||||
		result = multierror.Append(result, errors...)
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Refresh the Cache by rescanning CDI Spec directories and files.
 | 
			
		||||
func (c *Cache) refresh() error {
 | 
			
		||||
	var (
 | 
			
		||||
		specs      = map[string][]*Spec{}
 | 
			
		||||
		devices    = map[string]*Device{}
 | 
			
		||||
		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(fmt.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(fmt.Errorf("failed to load CDI Spec %w", err), 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.specs = specs
 | 
			
		||||
	c.devices = devices
 | 
			
		||||
	c.errors = specErrors
 | 
			
		||||
 | 
			
		||||
	return multierror.New(result...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RefreshIfRequired triggers a refresh if necessary.
 | 
			
		||||
func (c *Cache) refreshIfRequired(force bool) (bool, error) {
 | 
			
		||||
	// We need to refresh if
 | 
			
		||||
	// - it's forced by an explicit call to Refresh() in manual mode
 | 
			
		||||
	// - a missing Spec dir appears (added to watch) in auto-refresh mode
 | 
			
		||||
	if force || (c.autoRefresh && c.watch.update(c.dirErrors)) {
 | 
			
		||||
		return true, c.refresh()
 | 
			
		||||
	}
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InjectDevices injects the given qualified devices to an OCI Spec. It
 | 
			
		||||
// returns any unresolvable devices and an error if injection fails for
 | 
			
		||||
// any of the devices.
 | 
			
		||||
func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, error) {
 | 
			
		||||
	var unresolved []string
 | 
			
		||||
 | 
			
		||||
	if ociSpec == nil {
 | 
			
		||||
		return devices, fmt.Errorf("can't inject devices, nil OCI Spec")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	c.refreshIfRequired(false)
 | 
			
		||||
 | 
			
		||||
	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, fmt.Errorf("unresolvable CDI devices %s",
 | 
			
		||||
			strings.Join(unresolved, ", "))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := edits.Apply(ociSpec); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to inject devices: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// highestPrioritySpecDir returns the Spec directory with highest priority
 | 
			
		||||
// and its priority.
 | 
			
		||||
func (c *Cache) highestPrioritySpecDir() (string, int) {
 | 
			
		||||
	if len(c.specDirs) == 0 {
 | 
			
		||||
		return "", -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prio := len(c.specDirs) - 1
 | 
			
		||||
	dir := c.specDirs[prio]
 | 
			
		||||
 | 
			
		||||
	return dir, prio
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WriteSpec writes a Spec file with the given content into the highest
 | 
			
		||||
// priority Spec directory. If name has a "json" or "yaml" extension it
 | 
			
		||||
// choses the encoding. Otherwise the default YAML encoding is used.
 | 
			
		||||
func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error {
 | 
			
		||||
	var (
 | 
			
		||||
		specDir string
 | 
			
		||||
		path    string
 | 
			
		||||
		prio    int
 | 
			
		||||
		spec    *Spec
 | 
			
		||||
		err     error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	specDir, prio = c.highestPrioritySpecDir()
 | 
			
		||||
	if specDir == "" {
 | 
			
		||||
		return errors.New("no Spec directories to write to")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path = filepath.Join(specDir, name)
 | 
			
		||||
	if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" {
 | 
			
		||||
		path += defaultSpecExt
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	spec, err = newSpec(raw, path, prio)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return spec.write(true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveSpec removes a Spec with the given name from the highest
 | 
			
		||||
// priority Spec directory. This function can be used to remove a
 | 
			
		||||
// Spec previously written by WriteSpec(). If the file exists and
 | 
			
		||||
// its removal fails RemoveSpec returns an error.
 | 
			
		||||
func (c *Cache) RemoveSpec(name string) error {
 | 
			
		||||
	var (
 | 
			
		||||
		specDir string
 | 
			
		||||
		path    string
 | 
			
		||||
		err     error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	specDir, _ = c.highestPrioritySpecDir()
 | 
			
		||||
	if specDir == "" {
 | 
			
		||||
		return errors.New("no Spec directories to remove from")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path = filepath.Join(specDir, name)
 | 
			
		||||
	if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" {
 | 
			
		||||
		path += defaultSpecExt
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = os.Remove(path)
 | 
			
		||||
	if err != nil && errors.Is(err, fs.ErrNotExist) {
 | 
			
		||||
		err = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDevice returns the cached device for the given qualified name.
 | 
			
		||||
func (c *Cache) GetDevice(device string) *Device {
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	c.refreshIfRequired(false)
 | 
			
		||||
 | 
			
		||||
	return c.devices[device]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListDevices lists all cached devices by qualified name.
 | 
			
		||||
func (c *Cache) ListDevices() []string {
 | 
			
		||||
	var devices []string
 | 
			
		||||
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	c.refreshIfRequired(false)
 | 
			
		||||
 | 
			
		||||
	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()
 | 
			
		||||
 | 
			
		||||
	c.refreshIfRequired(false)
 | 
			
		||||
 | 
			
		||||
	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()
 | 
			
		||||
 | 
			
		||||
	c.refreshIfRequired(false)
 | 
			
		||||
 | 
			
		||||
	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()
 | 
			
		||||
 | 
			
		||||
	c.refreshIfRequired(false)
 | 
			
		||||
 | 
			
		||||
	return c.specs[vendor]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSpecErrors returns all errors encountered for the spec during the
 | 
			
		||||
// last cache refresh.
 | 
			
		||||
func (c *Cache) GetSpecErrors(spec *Spec) []error {
 | 
			
		||||
	var errors []error
 | 
			
		||||
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	if errs, ok := c.errors[spec.GetPath()]; ok {
 | 
			
		||||
		errors = make([]error, len(errs))
 | 
			
		||||
		copy(errors, errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return errors
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetErrors returns all errors encountered during the last
 | 
			
		||||
// cache refresh.
 | 
			
		||||
func (c *Cache) GetErrors() map[string][]error {
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	errors := map[string][]error{}
 | 
			
		||||
	for path, errs := range c.errors {
 | 
			
		||||
		errors[path] = errs
 | 
			
		||||
	}
 | 
			
		||||
	for path, err := range c.dirErrors {
 | 
			
		||||
		errors[path] = []error{err}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return errors
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSpecDirectories returns the CDI Spec directories currently in use.
 | 
			
		||||
func (c *Cache) GetSpecDirectories() []string {
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	dirs := make([]string, len(c.specDirs))
 | 
			
		||||
	copy(dirs, c.specDirs)
 | 
			
		||||
	return dirs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSpecDirErrors returns any errors related to configured Spec directories.
 | 
			
		||||
func (c *Cache) GetSpecDirErrors() map[string]error {
 | 
			
		||||
	if c.dirErrors == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	errors := make(map[string]error)
 | 
			
		||||
	for dir, err := range c.dirErrors {
 | 
			
		||||
		errors[dir] = err
 | 
			
		||||
	}
 | 
			
		||||
	return errors
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Our fsnotify helper wrapper.
 | 
			
		||||
type watch struct {
 | 
			
		||||
	watcher *fsnotify.Watcher
 | 
			
		||||
	tracked map[string]bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Setup monitoring for the given Spec directories.
 | 
			
		||||
func (w *watch) setup(dirs []string, dirErrors map[string]error) {
 | 
			
		||||
	var (
 | 
			
		||||
		dir string
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
	w.tracked = make(map[string]bool)
 | 
			
		||||
	for _, dir = range dirs {
 | 
			
		||||
		w.tracked[dir] = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.watcher, err = fsnotify.NewWatcher()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		for _, dir := range dirs {
 | 
			
		||||
			dirErrors[dir] = fmt.Errorf("failed to create watcher: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.update(dirErrors)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Start watching Spec directories for relevant changes.
 | 
			
		||||
func (w *watch) start(m *sync.Mutex, refresh func() error, dirErrors map[string]error) {
 | 
			
		||||
	go w.watch(w.watcher, m, refresh, dirErrors)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Stop watching directories.
 | 
			
		||||
func (w *watch) stop() {
 | 
			
		||||
	if w.watcher == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.watcher.Close()
 | 
			
		||||
	w.tracked = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Watch Spec directory changes, triggering a refresh if necessary.
 | 
			
		||||
func (w *watch) watch(fsw *fsnotify.Watcher, m *sync.Mutex, refresh func() error, dirErrors map[string]error) {
 | 
			
		||||
	watch := fsw
 | 
			
		||||
	if watch == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case event, ok := <-watch.Events:
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (event.Op & (fsnotify.Rename | fsnotify.Remove | fsnotify.Write)) == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if event.Op == fsnotify.Write {
 | 
			
		||||
				if ext := filepath.Ext(event.Name); ext != ".json" && ext != ".yaml" {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			m.Lock()
 | 
			
		||||
			if event.Op == fsnotify.Remove && w.tracked[event.Name] {
 | 
			
		||||
				w.update(dirErrors, event.Name)
 | 
			
		||||
			} else {
 | 
			
		||||
				w.update(dirErrors)
 | 
			
		||||
			}
 | 
			
		||||
			refresh()
 | 
			
		||||
			m.Unlock()
 | 
			
		||||
 | 
			
		||||
		case _, ok := <-watch.Errors:
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update watch with pending/missing or removed directories.
 | 
			
		||||
func (w *watch) update(dirErrors map[string]error, removed ...string) bool {
 | 
			
		||||
	var (
 | 
			
		||||
		dir    string
 | 
			
		||||
		ok     bool
 | 
			
		||||
		err    error
 | 
			
		||||
		update bool
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	for dir, ok = range w.tracked {
 | 
			
		||||
		if ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = w.watcher.Add(dir)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			w.tracked[dir] = true
 | 
			
		||||
			delete(dirErrors, dir)
 | 
			
		||||
			update = true
 | 
			
		||||
		} else {
 | 
			
		||||
			w.tracked[dir] = false
 | 
			
		||||
			dirErrors[dir] = fmt.Errorf("failed to monitor for changes: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, dir = range removed {
 | 
			
		||||
		w.tracked[dir] = false
 | 
			
		||||
		dirErrors[dir] = errors.New("directory removed")
 | 
			
		||||
		update = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return update
 | 
			
		||||
}
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   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 "syscall"
 | 
			
		||||
 | 
			
		||||
func osSync() {
 | 
			
		||||
	syscall.Sync()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
//go:build windows
 | 
			
		||||
// +build windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   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
 | 
			
		||||
 | 
			
		||||
func osSync() {}
 | 
			
		||||
@@ -1,332 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/specs-go"
 | 
			
		||||
	oci "github.com/opencontainers/runtime-spec/specs-go"
 | 
			
		||||
	ocigen "github.com/opencontainers/runtime-tools/generate"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// PrestartHook is the name of the OCI "prestart" hook.
 | 
			
		||||
	PrestartHook = "prestart"
 | 
			
		||||
	// CreateRuntimeHook is the name of the OCI "createRuntime" hook.
 | 
			
		||||
	CreateRuntimeHook = "createRuntime"
 | 
			
		||||
	// CreateContainerHook is the name of the OCI "createContainer" hook.
 | 
			
		||||
	CreateContainerHook = "createContainer"
 | 
			
		||||
	// StartContainerHook is the name of the OCI "startContainer" hook.
 | 
			
		||||
	StartContainerHook = "startContainer"
 | 
			
		||||
	// PoststartHook is the name of the OCI "poststart" hook.
 | 
			
		||||
	PoststartHook = "poststart"
 | 
			
		||||
	// PoststopHook is the name of the OCI "poststop" hook.
 | 
			
		||||
	PoststopHook = "poststop"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// Names of recognized hooks.
 | 
			
		||||
	validHookNames = map[string]struct{}{
 | 
			
		||||
		PrestartHook:        {},
 | 
			
		||||
		CreateRuntimeHook:   {},
 | 
			
		||||
		CreateContainerHook: {},
 | 
			
		||||
		StartContainerHook:  {},
 | 
			
		||||
		PoststartHook:       {},
 | 
			
		||||
		PoststopHook:        {},
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ContainerEdits represent updates to be applied to an OCI Spec.
 | 
			
		||||
// These updates can be specific to a CDI device, or they can be
 | 
			
		||||
// specific to a CDI Spec. In the former case these edits should
 | 
			
		||||
// be applied to all OCI Specs where the corresponding CDI device
 | 
			
		||||
// is injected. In the latter case, these edits should be applied
 | 
			
		||||
// to all OCI Specs where at least one devices from the CDI Spec
 | 
			
		||||
// is injected.
 | 
			
		||||
type ContainerEdits struct {
 | 
			
		||||
	*specs.ContainerEdits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Apply edits to the given OCI Spec. Updates the OCI Spec in place.
 | 
			
		||||
// Returns an error if the update fails.
 | 
			
		||||
func (e *ContainerEdits) Apply(spec *oci.Spec) error {
 | 
			
		||||
	if spec == nil {
 | 
			
		||||
		return errors.New("can't edit nil OCI Spec")
 | 
			
		||||
	}
 | 
			
		||||
	if e == nil || e.ContainerEdits == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	specgen := ocigen.NewFromSpec(spec)
 | 
			
		||||
	if len(e.Env) > 0 {
 | 
			
		||||
		specgen.AddMultipleProcessEnv(e.Env)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range e.DeviceNodes {
 | 
			
		||||
		dn := DeviceNode{d}
 | 
			
		||||
 | 
			
		||||
		err := dn.fillMissingInfo()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		dev := d.ToOCI()
 | 
			
		||||
		if dev.UID == nil && spec.Process != nil {
 | 
			
		||||
			if uid := spec.Process.User.UID; uid > 0 {
 | 
			
		||||
				dev.UID = &uid
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		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 fmt.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 fmt.Errorf("invalid container edits: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	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 fmt.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 fmt.Errorf("device %q: invalid type %q", d.Path, d.Type)
 | 
			
		||||
	}
 | 
			
		||||
	for _, bit := range d.Permissions {
 | 
			
		||||
		if bit != 'r' && bit != 'w' && bit != 'm' {
 | 
			
		||||
			return fmt.Errorf("device %q: invalid permissions %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 fmt.Errorf("invalid hook name %q", h.HookName)
 | 
			
		||||
	}
 | 
			
		||||
	if h.Path == "" {
 | 
			
		||||
		return fmt.Errorf("invalid hook %q with empty path", h.HookName)
 | 
			
		||||
	}
 | 
			
		||||
	if err := ValidateEnv(h.Env); err != nil {
 | 
			
		||||
		return fmt.Errorf("invalid hook %q: %w", h.HookName, err)
 | 
			
		||||
	}
 | 
			
		||||
	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{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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))
 | 
			
		||||
}
 | 
			
		||||
@@ -1,88 +0,0 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	blockDevice = "b"
 | 
			
		||||
	charDevice  = "c" // or "u"
 | 
			
		||||
	fifoDevice  = "p"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// deviceInfoFromPath takes the path to a device and returns its type,
 | 
			
		||||
// major and minor device numbers.
 | 
			
		||||
//
 | 
			
		||||
// It was adapted from https://github.com/opencontainers/runc/blob/v1.1.9/libcontainer/devices/device_unix.go#L30-L69
 | 
			
		||||
func deviceInfoFromPath(path string) (devType string, major, minor int64, _ error) {
 | 
			
		||||
	var stat unix.Stat_t
 | 
			
		||||
	err := unix.Lstat(path, &stat)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", 0, 0, err
 | 
			
		||||
	}
 | 
			
		||||
	switch stat.Mode & unix.S_IFMT {
 | 
			
		||||
	case unix.S_IFBLK:
 | 
			
		||||
		devType = blockDevice
 | 
			
		||||
	case unix.S_IFCHR:
 | 
			
		||||
		devType = charDevice
 | 
			
		||||
	case unix.S_IFIFO:
 | 
			
		||||
		devType = fifoDevice
 | 
			
		||||
	default:
 | 
			
		||||
		return "", 0, 0, errors.New("not a device node")
 | 
			
		||||
	}
 | 
			
		||||
	devNumber := uint64(stat.Rdev) //nolint:unconvert // Rdev is uint32 on e.g. MIPS.
 | 
			
		||||
	return devType, int64(unix.Major(devNumber)), int64(unix.Minor(devNumber)), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fillMissingInfo fills in missing mandatory attributes from the host device.
 | 
			
		||||
func (d *DeviceNode) fillMissingInfo() error {
 | 
			
		||||
	if d.HostPath == "" {
 | 
			
		||||
		d.HostPath = d.Path
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if d.Type != "" && (d.Major != 0 || d.Type == "p") {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deviceType, major, minor, err := deviceInfoFromPath(d.HostPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to stat CDI host device %q: %w", d.HostPath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if d.Type == "" {
 | 
			
		||||
		d.Type = deviceType
 | 
			
		||||
	} else {
 | 
			
		||||
		if d.Type != deviceType {
 | 
			
		||||
			return fmt.Errorf("CDI device (%q, %q), host type mismatch (%s, %s)",
 | 
			
		||||
				d.Path, d.HostPath, d.Type, deviceType)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if d.Major == 0 && d.Type != "p" {
 | 
			
		||||
		d.Major = major
 | 
			
		||||
		d.Minor = minor
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
//go:build windows
 | 
			
		||||
// +build windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   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 "fmt"
 | 
			
		||||
 | 
			
		||||
// fillMissingInfo fills in missing mandatory attributes from the host device.
 | 
			
		||||
func (d *DeviceNode) fillMissingInfo() error {
 | 
			
		||||
	return fmt.Errorf("unimplemented")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,88 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/internal/validation"
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
 | 
			
		||||
	cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
 | 
			
		||||
	oci "github.com/opencontainers/runtime-spec/specs-go"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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 parser.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
 | 
			
		||||
	}
 | 
			
		||||
	name := d.Name
 | 
			
		||||
	if d.spec != nil {
 | 
			
		||||
		name = d.GetQualifiedName()
 | 
			
		||||
	}
 | 
			
		||||
	if err := validation.ValidateSpecAnnotations(name, d.Annotations); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	edits := d.edits()
 | 
			
		||||
	if edits.isEmpty() {
 | 
			
		||||
		return fmt.Errorf("invalid device, empty device edits")
 | 
			
		||||
	}
 | 
			
		||||
	if err := edits.Validate(); err != nil {
 | 
			
		||||
		return fmt.Errorf("invalid device %q: %w", d.Name, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										272
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										272
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,272 +0,0 @@
 | 
			
		||||
// 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"
 | 
			
		||||
//
 | 
			
		||||
//      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 fmt.Errorf("CDI device injection failed: %w", err)
 | 
			
		||||
//      }
 | 
			
		||||
//
 | 
			
		||||
//      log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec))
 | 
			
		||||
//      return nil
 | 
			
		||||
//  }
 | 
			
		||||
//
 | 
			
		||||
// Cache Refresh
 | 
			
		||||
//
 | 
			
		||||
// By default the CDI Spec cache monitors the configured Spec directories
 | 
			
		||||
// and automatically refreshes itself when necessary. This behavior can be
 | 
			
		||||
// disabled using the WithAutoRefresh(false) option.
 | 
			
		||||
//
 | 
			
		||||
// Failure to set up monitoring for a Spec directory causes the directory to
 | 
			
		||||
// get ignored and an error to be recorded among the Spec directory errors.
 | 
			
		||||
// These errors can be queried using the GetSpecDirErrors() function. If the
 | 
			
		||||
// error condition is transient, for instance a missing directory which later
 | 
			
		||||
// gets created, the corresponding error will be removed once the condition
 | 
			
		||||
// is over.
 | 
			
		||||
//
 | 
			
		||||
// With auto-refresh enabled injecting any CDI devices can be done without
 | 
			
		||||
// an explicit call to Refresh(), using a code snippet similar to the
 | 
			
		||||
// following:
 | 
			
		||||
//
 | 
			
		||||
// In a runtime implementation one typically wants to make sure the
 | 
			
		||||
// CDI Spec cache is up to date before performing device injection.
 | 
			
		||||
// A code snippet similar to the following accmplishes that:
 | 
			
		||||
//
 | 
			
		||||
//  import (
 | 
			
		||||
//      "fmt"
 | 
			
		||||
//      "strings"
 | 
			
		||||
//
 | 
			
		||||
//      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 fmt.Errorf("CDI device injection failed: %w", err)
 | 
			
		||||
//      }
 | 
			
		||||
//
 | 
			
		||||
//      log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec))
 | 
			
		||||
//      return nil
 | 
			
		||||
//  }
 | 
			
		||||
//
 | 
			
		||||
// Generated Spec Files, Multiple Directories, Device Precedence
 | 
			
		||||
//
 | 
			
		||||
// It is often necessary to generate Spec files dynamically. On some
 | 
			
		||||
// systems the available or usable set of CDI devices might change
 | 
			
		||||
// dynamically which then needs to be reflected in CDI Specs. For
 | 
			
		||||
// some device classes it makes sense to enumerate the available
 | 
			
		||||
// devices at every boot and generate Spec file entries for each
 | 
			
		||||
// device found. Some CDI devices might need special client- or
 | 
			
		||||
// request-specific configuration which can only be fulfilled by
 | 
			
		||||
// dynamically generated client-specific entries in transient Spec
 | 
			
		||||
// files.
 | 
			
		||||
//
 | 
			
		||||
// 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 choosing 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'. With this scheme, static
 | 
			
		||||
// Spec files, typically installed by distro-specific packages, go into
 | 
			
		||||
// '/etc/cdi' while all the dynamically generated Spec files, transient
 | 
			
		||||
// or other, go into '/var/run/cdi'.
 | 
			
		||||
//
 | 
			
		||||
// Spec File Generation
 | 
			
		||||
//
 | 
			
		||||
// CDI offers two functions for writing and removing dynamically generated
 | 
			
		||||
// Specs from CDI Spec directories. These functions, WriteSpec() and
 | 
			
		||||
// RemoveSpec() implicitly follow the principle of separating dynamic Specs
 | 
			
		||||
// from the rest and therefore always write to and remove Specs from the
 | 
			
		||||
// last configured directory.
 | 
			
		||||
//
 | 
			
		||||
// Corresponding functions are also provided for generating names for Spec
 | 
			
		||||
// files. These functions follow a simple naming convention to ensure that
 | 
			
		||||
// multiple entities generating Spec files simultaneously on the same host
 | 
			
		||||
// do not end up using conflicting Spec file names. GenerateSpecName(),
 | 
			
		||||
// GenerateNameForSpec(), GenerateTransientSpecName(), and
 | 
			
		||||
// GenerateTransientNameForSpec() all generate names which can be passed
 | 
			
		||||
// as such to WriteSpec() and subsequently to RemoveSpec().
 | 
			
		||||
//
 | 
			
		||||
// Generating a Spec file for a vendor/device class can be done with a
 | 
			
		||||
// code snippet similar to the following:
 | 
			
		||||
//
 | 
			
		||||
// import (
 | 
			
		||||
//     "fmt"
 | 
			
		||||
//     ...
 | 
			
		||||
//     "github.com/container-orchestrated-devices/container-device-interface/specs-go"
 | 
			
		||||
//     "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
 | 
			
		||||
// )
 | 
			
		||||
//
 | 
			
		||||
// func generateDeviceSpecs() error {
 | 
			
		||||
//     registry := cdi.GetRegistry()
 | 
			
		||||
//     spec := &specs.Spec{
 | 
			
		||||
//         Version: specs.CurrentVersion,
 | 
			
		||||
//         Kind:    vendor+"/"+class,
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     for _, dev := range enumerateDevices() {
 | 
			
		||||
//         spec.Devices = append(spec.Devices, specs.Device{
 | 
			
		||||
//             Name: dev.Name,
 | 
			
		||||
//             ContainerEdits: getContainerEditsForDevice(dev),
 | 
			
		||||
//         })
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     specName, err := cdi.GenerateNameForSpec(spec)
 | 
			
		||||
//     if err != nil {
 | 
			
		||||
//         return fmt.Errorf("failed to generate Spec name: %w", err)
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     return registry.SpecDB().WriteSpec(spec, specName)
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// Similarly, generating and later cleaning up transient Spec files can be
 | 
			
		||||
// done with code fragments similar to the following. These transient Spec
 | 
			
		||||
// files are temporary Spec files with container-specific parametrization.
 | 
			
		||||
// They are typically created before the associated container is created
 | 
			
		||||
// and removed once that container is removed.
 | 
			
		||||
//
 | 
			
		||||
// import (
 | 
			
		||||
//     "fmt"
 | 
			
		||||
//     ...
 | 
			
		||||
//     "github.com/container-orchestrated-devices/container-device-interface/specs-go"
 | 
			
		||||
//     "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
 | 
			
		||||
// )
 | 
			
		||||
//
 | 
			
		||||
// func generateTransientSpec(ctr Container) error {
 | 
			
		||||
//     registry := cdi.GetRegistry()
 | 
			
		||||
//     devices := getContainerDevs(ctr, vendor, class)
 | 
			
		||||
//     spec := &specs.Spec{
 | 
			
		||||
//         Version: specs.CurrentVersion,
 | 
			
		||||
//         Kind:    vendor+"/"+class,
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     for _, dev := range devices {
 | 
			
		||||
//         spec.Devices = append(spec.Devices, specs.Device{
 | 
			
		||||
//             // the generated name needs to be unique within the
 | 
			
		||||
//             // vendor/class domain on the host/node.
 | 
			
		||||
//             Name: generateUniqueDevName(dev, ctr),
 | 
			
		||||
//             ContainerEdits: getEditsForContainer(dev),
 | 
			
		||||
//         })
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     // transientID is expected to guarantee that the Spec file name
 | 
			
		||||
//     // generated using <vendor, class, transientID> is unique within
 | 
			
		||||
//     // the host/node. If more than one device is allocated with the
 | 
			
		||||
//     // same vendor/class domain, either all generated Spec entries
 | 
			
		||||
//     // should go to a single Spec file (like in this sample snippet),
 | 
			
		||||
//     // or transientID should be unique for each generated Spec file.
 | 
			
		||||
//     transientID := getSomeSufficientlyUniqueIDForContainer(ctr)
 | 
			
		||||
//     specName, err := cdi.GenerateNameForTransientSpec(vendor, class, transientID)
 | 
			
		||||
//     if err != nil {
 | 
			
		||||
//         return fmt.Errorf("failed to generate Spec name: %w", err)
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     return registry.SpecDB().WriteSpec(spec, specName)
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// func removeTransientSpec(ctr Container) error {
 | 
			
		||||
//     registry := cdi.GetRegistry()
 | 
			
		||||
//     transientID := getSomeSufficientlyUniqueIDForContainer(ctr)
 | 
			
		||||
//     specName := cdi.GenerateNameForTransientSpec(vendor, class, transientID)
 | 
			
		||||
//
 | 
			
		||||
//     return registry.SpecDB().RemoveSpec(specName)
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// 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
 | 
			
		||||
@@ -1,113 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// QualifiedName returns the qualified name for a device.
 | 
			
		||||
// The syntax for a qualified device names is
 | 
			
		||||
//
 | 
			
		||||
//	"<vendor>/<class>=<name>".
 | 
			
		||||
//
 | 
			
		||||
// A valid vendor and class name may contain the following runes:
 | 
			
		||||
//
 | 
			
		||||
//	'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
 | 
			
		||||
//
 | 
			
		||||
// A valid device name may contain the following runes:
 | 
			
		||||
//
 | 
			
		||||
//	'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use parser.QualifiedName instead
 | 
			
		||||
func QualifiedName(vendor, class, name string) string {
 | 
			
		||||
	return parser.QualifiedName(vendor, class, name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsQualifiedName tests if a device name is qualified.
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use parser.IsQualifiedName instead
 | 
			
		||||
func IsQualifiedName(device string) bool {
 | 
			
		||||
	return parser.IsQualifiedName(device)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use parser.ParseQualifiedName instead
 | 
			
		||||
func ParseQualifiedName(device string) (string, string, string, error) {
 | 
			
		||||
	return parser.ParseQualifiedName(device)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use parser.ParseDevice instead
 | 
			
		||||
func ParseDevice(device string) (string, string, string) {
 | 
			
		||||
	return parser.ParseDevice(device)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use parser.ParseQualifier instead
 | 
			
		||||
func ParseQualifier(kind string) (string, string) {
 | 
			
		||||
	return parser.ParseQualifier(kind)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 '.')
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use parser.ValidateVendorName instead
 | 
			
		||||
func ValidateVendorName(vendor string) error {
 | 
			
		||||
	return parser.ValidateVendorName(vendor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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, dash, and dot ('_', '-', and '.')
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use parser.ValidateClassName instead
 | 
			
		||||
func ValidateClassName(class string) error {
 | 
			
		||||
	return parser.ValidateClassName(class)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 ('_', '-', '.', ':')
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use parser.ValidateDeviceName instead
 | 
			
		||||
func ValidateDeviceName(name string) error {
 | 
			
		||||
	return parser.ValidateDeviceName(name)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,150 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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"
 | 
			
		||||
 | 
			
		||||
	cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
 | 
			
		||||
	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.
 | 
			
		||||
//
 | 
			
		||||
// Configure reconfigures the registry with the given options.
 | 
			
		||||
//
 | 
			
		||||
// Refresh rescans all CDI Spec directories and updates the
 | 
			
		||||
// state of the cache to reflect any changes. It returns any
 | 
			
		||||
// errors encountered during the refresh.
 | 
			
		||||
//
 | 
			
		||||
// 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().
 | 
			
		||||
//
 | 
			
		||||
// GetSpecDirErrors returns any errors related to the configured
 | 
			
		||||
// Spec directories.
 | 
			
		||||
type RegistryRefresher interface {
 | 
			
		||||
	Configure(...Option) error
 | 
			
		||||
	Refresh() error
 | 
			
		||||
	GetErrors() map[string][]error
 | 
			
		||||
	GetSpecDirectories() []string
 | 
			
		||||
	GetSpecDirErrors() map[string]error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistryResolver is the registry interface for injecting CDI
 | 
			
		||||
// 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.
 | 
			
		||||
//
 | 
			
		||||
// WriteSpec writes the Spec with the given content and name to the
 | 
			
		||||
// last Spec directory.
 | 
			
		||||
type RegistrySpecDB interface {
 | 
			
		||||
	ListVendors() []string
 | 
			
		||||
	ListClasses() []string
 | 
			
		||||
	GetVendorSpecs(vendor string) []*Spec
 | 
			
		||||
	GetSpecErrors(*Spec) []error
 | 
			
		||||
	WriteSpec(raw *cdi.Spec, name string) error
 | 
			
		||||
	RemoveSpec(name string) 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
 | 
			
		||||
}
 | 
			
		||||
@@ -1,114 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
		specDirs := make([]string, len(dirs))
 | 
			
		||||
		for i, dir := range dirs {
 | 
			
		||||
			specDirs[i] = filepath.Clean(dir)
 | 
			
		||||
		}
 | 
			
		||||
		c.specDirs = specDirs
 | 
			
		||||
		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 {
 | 
			
		||||
				if errors.Is(err, fs.ErrNotExist) {
 | 
			
		||||
					return 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										352
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										352
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,352 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	oci "github.com/opencontainers/runtime-spec/specs-go"
 | 
			
		||||
	"sigs.k8s.io/yaml"
 | 
			
		||||
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/internal/validation"
 | 
			
		||||
	cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// defaultSpecExt is the file extension for the default encoding.
 | 
			
		||||
	defaultSpecExt = ".yaml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// Externally set CDI Spec validation function.
 | 
			
		||||
	specValidator func(*cdi.Spec) error
 | 
			
		||||
	validatorLock sync.RWMutex
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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, fmt.Errorf("failed to read CDI Spec %q: %w", path, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	raw, err := ParseSpec(data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to parse CDI Spec %q: %w", path, err)
 | 
			
		||||
	}
 | 
			
		||||
	if raw == nil {
 | 
			
		||||
		return nil, fmt.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 := validateSpec(raw)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	spec := &Spec{
 | 
			
		||||
		Spec:     raw,
 | 
			
		||||
		path:     filepath.Clean(path),
 | 
			
		||||
		priority: priority,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ext := filepath.Ext(spec.path); ext != ".yaml" && ext != ".json" {
 | 
			
		||||
		spec.path += defaultSpecExt
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	spec.vendor, spec.class = ParseQualifier(spec.Kind)
 | 
			
		||||
 | 
			
		||||
	if spec.devices, err = spec.validate(); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("invalid CDI Spec: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return spec, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write the CDI Spec to the file associated with it during instantiation
 | 
			
		||||
// by newSpec() or ReadSpec().
 | 
			
		||||
func (s *Spec) write(overwrite bool) error {
 | 
			
		||||
	var (
 | 
			
		||||
		data []byte
 | 
			
		||||
		dir  string
 | 
			
		||||
		tmp  *os.File
 | 
			
		||||
		err  error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	err = validateSpec(s.Spec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if filepath.Ext(s.path) == ".yaml" {
 | 
			
		||||
		data, err = yaml.Marshal(s.Spec)
 | 
			
		||||
		data = append([]byte("---\n"), data...)
 | 
			
		||||
	} else {
 | 
			
		||||
		data, err = json.Marshal(s.Spec)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to marshal Spec file: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dir = filepath.Dir(s.path)
 | 
			
		||||
	err = os.MkdirAll(dir, 0o755)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to create Spec dir: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tmp, err = os.CreateTemp(dir, "spec.*.tmp")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to create Spec file: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	_, err = tmp.Write(data)
 | 
			
		||||
	tmp.Close()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to write Spec file: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = renameIn(dir, filepath.Base(tmp.Name()), filepath.Base(s.path), overwrite)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		os.Remove(tmp.Name())
 | 
			
		||||
		err = fmt.Errorf("failed to write Spec file: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	minVersion, err := MinimumRequiredVersion(s.Spec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("could not determine minimum required version: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if newVersion(minVersion).IsGreaterThan(newVersion(s.Version)) {
 | 
			
		||||
		return nil, fmt.Errorf("the spec version must be at least v%v", minVersion)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ValidateVendorName(s.vendor); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := ValidateClassName(s.class); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := validation.ValidateSpecAnnotations(s.Kind, s.Annotations); 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, fmt.Errorf("failed add device %q: %w", d.Name, err)
 | 
			
		||||
		}
 | 
			
		||||
		if _, conflict := devices[d.Name]; conflict {
 | 
			
		||||
			return nil, fmt.Errorf("invalid spec, multiple device %q", d.Name)
 | 
			
		||||
		}
 | 
			
		||||
		devices[d.Name] = dev
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return devices, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateVersion checks whether the specified spec version is supported.
 | 
			
		||||
func validateVersion(version string) error {
 | 
			
		||||
	if !validSpecVersions.isValidVersion(version) {
 | 
			
		||||
		return fmt.Errorf("invalid version %q", version)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseSpec parses CDI Spec data into a raw CDI Spec.
 | 
			
		||||
func ParseSpec(data []byte) (*cdi.Spec, error) {
 | 
			
		||||
	var raw *cdi.Spec
 | 
			
		||||
	err := yaml.UnmarshalStrict(data, &raw)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to unmarshal CDI Spec: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return raw, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetSpecValidator sets a CDI Spec validator function. This function
 | 
			
		||||
// is used for extra CDI Spec content validation whenever a Spec file
 | 
			
		||||
// loaded (using ReadSpec() or written (using WriteSpec()).
 | 
			
		||||
func SetSpecValidator(fn func(*cdi.Spec) error) {
 | 
			
		||||
	validatorLock.Lock()
 | 
			
		||||
	defer validatorLock.Unlock()
 | 
			
		||||
	specValidator = fn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateSpec validates the Spec using the extneral validator.
 | 
			
		||||
func validateSpec(raw *cdi.Spec) error {
 | 
			
		||||
	validatorLock.RLock()
 | 
			
		||||
	defer validatorLock.RUnlock()
 | 
			
		||||
 | 
			
		||||
	if specValidator == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	err := specValidator(raw)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Spec validation failed: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateSpecName generates a vendor+class scoped Spec file name. The
 | 
			
		||||
// name can be passed to WriteSpec() to write a Spec file to the file
 | 
			
		||||
// system.
 | 
			
		||||
//
 | 
			
		||||
// vendor and class should match the vendor and class of the CDI Spec.
 | 
			
		||||
// The file name is generated without a ".json" or ".yaml" extension.
 | 
			
		||||
// The caller can append the desired extension to choose a particular
 | 
			
		||||
// encoding. Otherwise WriteSpec() will use its default encoding.
 | 
			
		||||
//
 | 
			
		||||
// This function always returns the same name for the same vendor/class
 | 
			
		||||
// combination. Therefore it cannot be used as such to generate multiple
 | 
			
		||||
// Spec file names for a single vendor and class.
 | 
			
		||||
func GenerateSpecName(vendor, class string) string {
 | 
			
		||||
	return vendor + "-" + class
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateTransientSpecName generates a vendor+class scoped transient
 | 
			
		||||
// Spec file name. The name can be passed to WriteSpec() to write a Spec
 | 
			
		||||
// file to the file system.
 | 
			
		||||
//
 | 
			
		||||
// Transient Specs are those whose lifecycle is tied to that of some
 | 
			
		||||
// external entity, for instance a container. vendor and class should
 | 
			
		||||
// match the vendor and class of the CDI Spec. transientID should be
 | 
			
		||||
// unique among all CDI users on the same host that might generate
 | 
			
		||||
// transient Spec files using the same vendor/class combination. If
 | 
			
		||||
// the external entity to which the lifecycle of the transient Spec
 | 
			
		||||
// is tied to has a unique ID of its own, then this is usually a
 | 
			
		||||
// good choice for transientID.
 | 
			
		||||
//
 | 
			
		||||
// The file name is generated without a ".json" or ".yaml" extension.
 | 
			
		||||
// The caller can append the desired extension to choose a particular
 | 
			
		||||
// encoding. Otherwise WriteSpec() will use its default encoding.
 | 
			
		||||
func GenerateTransientSpecName(vendor, class, transientID string) string {
 | 
			
		||||
	transientID = strings.ReplaceAll(transientID, "/", "_")
 | 
			
		||||
	return GenerateSpecName(vendor, class) + "_" + transientID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateNameForSpec generates a name for the given Spec using
 | 
			
		||||
// GenerateSpecName with the vendor and class taken from the Spec.
 | 
			
		||||
// On success it returns the generated name and a nil error. If
 | 
			
		||||
// the Spec does not contain a valid vendor or class, it returns
 | 
			
		||||
// an empty name and a non-nil error.
 | 
			
		||||
func GenerateNameForSpec(raw *cdi.Spec) (string, error) {
 | 
			
		||||
	vendor, class := ParseQualifier(raw.Kind)
 | 
			
		||||
	if vendor == "" {
 | 
			
		||||
		return "", fmt.Errorf("invalid vendor/class %q in Spec", raw.Kind)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return GenerateSpecName(vendor, class), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenerateNameForTransientSpec generates a name for the given transient
 | 
			
		||||
// Spec using GenerateTransientSpecName with the vendor and class taken
 | 
			
		||||
// from the Spec. On success it returns the generated name and a nil error.
 | 
			
		||||
// If the Spec does not contain a valid vendor or class, it returns an
 | 
			
		||||
// an empty name and a non-nil error.
 | 
			
		||||
func GenerateNameForTransientSpec(raw *cdi.Spec, transientID string) (string, error) {
 | 
			
		||||
	vendor, class := ParseQualifier(raw.Kind)
 | 
			
		||||
	if vendor == "" {
 | 
			
		||||
		return "", fmt.Errorf("invalid vendor/class %q in Spec", raw.Kind)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return GenerateTransientSpecName(vendor, class, transientID), nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   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 (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Rename src to dst, both relative to the directory dir. If dst already exists
 | 
			
		||||
// refuse renaming with an error unless overwrite is explicitly asked for.
 | 
			
		||||
func renameIn(dir, src, dst string, overwrite bool) error {
 | 
			
		||||
	var flags uint
 | 
			
		||||
 | 
			
		||||
	dirf, err := os.Open(dir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("rename failed: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer dirf.Close()
 | 
			
		||||
 | 
			
		||||
	if !overwrite {
 | 
			
		||||
		flags = unix.RENAME_NOREPLACE
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dirFd := int(dirf.Fd())
 | 
			
		||||
	err = unix.Renameat2(dirFd, src, dirFd, dst, flags)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("rename failed: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
//go:build !linux
 | 
			
		||||
// +build !linux
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Copyright © 2022 The CDI Authors
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cdi
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Rename src to dst, both relative to the directory dir. If dst already exists
 | 
			
		||||
// refuse renaming with an error unless overwrite is explicitly asked for.
 | 
			
		||||
func renameIn(dir, src, dst string, overwrite bool) error {
 | 
			
		||||
	src = filepath.Join(dir, src)
 | 
			
		||||
	dst = filepath.Join(dir, dst)
 | 
			
		||||
 | 
			
		||||
	_, err := os.Stat(dst)
 | 
			
		||||
	if err == nil && !overwrite {
 | 
			
		||||
		return os.ErrExist
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return os.Rename(src, dst)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,188 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright © 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"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/mod/semver"
 | 
			
		||||
 | 
			
		||||
	"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
 | 
			
		||||
	cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// CurrentVersion is the current version of the CDI Spec.
 | 
			
		||||
	CurrentVersion = cdi.CurrentVersion
 | 
			
		||||
 | 
			
		||||
	// vCurrent is the current version as a semver-comparable type
 | 
			
		||||
	vCurrent version = "v" + CurrentVersion
 | 
			
		||||
 | 
			
		||||
	// These represent the released versions of the CDI specification
 | 
			
		||||
	v010 version = "v0.1.0"
 | 
			
		||||
	v020 version = "v0.2.0"
 | 
			
		||||
	v030 version = "v0.3.0"
 | 
			
		||||
	v040 version = "v0.4.0"
 | 
			
		||||
	v050 version = "v0.5.0"
 | 
			
		||||
	v060 version = "v0.6.0"
 | 
			
		||||
 | 
			
		||||
	// vEarliest is the earliest supported version of the CDI specification
 | 
			
		||||
	vEarliest version = v030
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// validSpecVersions stores a map of spec versions to functions to check the required versions.
 | 
			
		||||
// Adding new fields / spec versions requires that a `requiredFunc` be implemented and
 | 
			
		||||
// this map be updated.
 | 
			
		||||
var validSpecVersions = requiredVersionMap{
 | 
			
		||||
	v010: nil,
 | 
			
		||||
	v020: nil,
 | 
			
		||||
	v030: nil,
 | 
			
		||||
	v040: requiresV040,
 | 
			
		||||
	v050: requiresV050,
 | 
			
		||||
	v060: requiresV060,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MinimumRequiredVersion determines the minimum spec version for the input spec.
 | 
			
		||||
func MinimumRequiredVersion(spec *cdi.Spec) (string, error) {
 | 
			
		||||
	minVersion := validSpecVersions.requiredVersion(spec)
 | 
			
		||||
	return minVersion.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// version represents a semantic version string
 | 
			
		||||
type version string
 | 
			
		||||
 | 
			
		||||
// newVersion creates a version that can be used for semantic version comparisons.
 | 
			
		||||
func newVersion(v string) version {
 | 
			
		||||
	return version("v" + strings.TrimPrefix(v, "v"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String returns the string representation of the version.
 | 
			
		||||
// This trims a leading v if present.
 | 
			
		||||
func (v version) String() string {
 | 
			
		||||
	return strings.TrimPrefix(string(v), "v")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsGreaterThan checks with a version is greater than the specified version.
 | 
			
		||||
func (v version) IsGreaterThan(o version) bool {
 | 
			
		||||
	return semver.Compare(string(v), string(o)) > 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsLatest checks whether the version is the latest supported version
 | 
			
		||||
func (v version) IsLatest() bool {
 | 
			
		||||
	return v == vCurrent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type requiredFunc func(*cdi.Spec) bool
 | 
			
		||||
 | 
			
		||||
type requiredVersionMap map[version]requiredFunc
 | 
			
		||||
 | 
			
		||||
// isValidVersion checks whether the specified version is valid.
 | 
			
		||||
// A version is valid if it is contained in the required version map.
 | 
			
		||||
func (r requiredVersionMap) isValidVersion(specVersion string) bool {
 | 
			
		||||
	_, ok := validSpecVersions[newVersion(specVersion)]
 | 
			
		||||
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// requiredVersion returns the minimum version required for the given spec
 | 
			
		||||
func (r requiredVersionMap) requiredVersion(spec *cdi.Spec) version {
 | 
			
		||||
	minVersion := vEarliest
 | 
			
		||||
 | 
			
		||||
	for v, isRequired := range validSpecVersions {
 | 
			
		||||
		if isRequired == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if isRequired(spec) && v.IsGreaterThan(minVersion) {
 | 
			
		||||
			minVersion = v
 | 
			
		||||
		}
 | 
			
		||||
		// If we have already detected the latest version then no later version could be detected
 | 
			
		||||
		if minVersion.IsLatest() {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return minVersion
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// requiresV060 returns true if the spec uses v0.6.0 features
 | 
			
		||||
func requiresV060(spec *cdi.Spec) bool {
 | 
			
		||||
	// The v0.6.0 spec allows annotations to be specified at a spec level
 | 
			
		||||
	for range spec.Annotations {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The v0.6.0 spec allows annotations to be specified at a device level
 | 
			
		||||
	for _, d := range spec.Devices {
 | 
			
		||||
		for range d.Annotations {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The v0.6.0 spec allows dots "." in Kind name label (class)
 | 
			
		||||
	vendor, class := parser.ParseQualifier(spec.Kind)
 | 
			
		||||
	if vendor != "" {
 | 
			
		||||
		if strings.ContainsRune(class, '.') {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// requiresV050 returns true if the spec uses v0.5.0 features
 | 
			
		||||
func requiresV050(spec *cdi.Spec) bool {
 | 
			
		||||
	var edits []*cdi.ContainerEdits
 | 
			
		||||
 | 
			
		||||
	for _, d := range spec.Devices {
 | 
			
		||||
		// The v0.5.0 spec allowed device names to start with a digit instead of requiring a letter
 | 
			
		||||
		if len(d.Name) > 0 && !parser.IsLetter(rune(d.Name[0])) {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		edits = append(edits, &d.ContainerEdits)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	edits = append(edits, &spec.ContainerEdits)
 | 
			
		||||
	for _, e := range edits {
 | 
			
		||||
		for _, dn := range e.DeviceNodes {
 | 
			
		||||
			// The HostPath field was added in v0.5.0
 | 
			
		||||
			if dn.HostPath != "" {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// requiresV040 returns true if the spec uses v0.4.0 features
 | 
			
		||||
func requiresV040(spec *cdi.Spec) bool {
 | 
			
		||||
	var edits []*cdi.ContainerEdits
 | 
			
		||||
 | 
			
		||||
	for _, d := range spec.Devices {
 | 
			
		||||
		edits = append(edits, &d.ContainerEdits)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	edits = append(edits, &spec.ContainerEdits)
 | 
			
		||||
	for _, e := range edits {
 | 
			
		||||
		for _, m := range e.Mounts {
 | 
			
		||||
			// The Type field was added in v0.4.0
 | 
			
		||||
			if m.Type != "" {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
@@ -1,212 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
   Copyright © 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 parser
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// QualifiedName returns the qualified name for a device.
 | 
			
		||||
// The syntax for a qualified device names is
 | 
			
		||||
//
 | 
			
		||||
//	"<vendor>/<class>=<name>".
 | 
			
		||||
//
 | 
			
		||||
// A valid vendor and class name may contain the following runes:
 | 
			
		||||
//
 | 
			
		||||
//	'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
 | 
			
		||||
//
 | 
			
		||||
// A valid device name may contain 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, fmt.Errorf("unqualified device %q, missing vendor", device)
 | 
			
		||||
	}
 | 
			
		||||
	if class == "" {
 | 
			
		||||
		return "", "", device, fmt.Errorf("unqualified device %q, missing class", device)
 | 
			
		||||
	}
 | 
			
		||||
	if name == "" {
 | 
			
		||||
		return "", "", device, fmt.Errorf("unqualified device %q, missing device name", device)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ValidateVendorName(vendor); err != nil {
 | 
			
		||||
		return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := ValidateClassName(class); err != nil {
 | 
			
		||||
		return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := ValidateDeviceName(name); err != nil {
 | 
			
		||||
		return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
	err := validateVendorOrClassName(vendor)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("invalid vendor. %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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, dash, and dot ('_', '-', and '.')
 | 
			
		||||
func ValidateClassName(class string) error {
 | 
			
		||||
	err := validateVendorOrClassName(class)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("invalid class. %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateVendorOrClassName checks the validity of vendor or class name.
 | 
			
		||||
// A name may contain the following ASCII characters:
 | 
			
		||||
//   - upper- and lowercase letters ('A'-'Z', 'a'-'z')
 | 
			
		||||
//   - digits ('0'-'9')
 | 
			
		||||
//   - underscore, dash, and dot ('_', '-', and '.')
 | 
			
		||||
func validateVendorOrClassName(name string) error {
 | 
			
		||||
	if name == "" {
 | 
			
		||||
		return fmt.Errorf("empty name")
 | 
			
		||||
	}
 | 
			
		||||
	if !IsLetter(rune(name[0])) {
 | 
			
		||||
		return fmt.Errorf("%q, should start with letter", name)
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range string(name[1 : len(name)-1]) {
 | 
			
		||||
		switch {
 | 
			
		||||
		case IsAlphaNumeric(c):
 | 
			
		||||
		case c == '_' || c == '-' || c == '.':
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("invalid character '%c' in name %q",
 | 
			
		||||
				c, name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !IsAlphaNumeric(rune(name[len(name)-1])) {
 | 
			
		||||
		return fmt.Errorf("%q, should end with a letter or digit", name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 fmt.Errorf("invalid (empty) device name")
 | 
			
		||||
	}
 | 
			
		||||
	if !IsAlphaNumeric(rune(name[0])) {
 | 
			
		||||
		return fmt.Errorf("invalid class %q, should start with a letter or digit", name)
 | 
			
		||||
	}
 | 
			
		||||
	if len(name) == 1 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range string(name[1 : len(name)-1]) {
 | 
			
		||||
		switch {
 | 
			
		||||
		case IsAlphaNumeric(c):
 | 
			
		||||
		case c == '_' || c == '-' || c == '.' || c == ':':
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("invalid character '%c' in device name %q",
 | 
			
		||||
				c, name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !IsAlphaNumeric(rune(name[len(name)-1])) {
 | 
			
		||||
		return fmt.Errorf("invalid name %q, should end with a letter or digit", name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsLetter reports whether the rune is a letter.
 | 
			
		||||
func IsLetter(c rune) bool {
 | 
			
		||||
	return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsDigit reports whether the rune is a digit.
 | 
			
		||||
func IsDigit(c rune) bool {
 | 
			
		||||
	return '0' <= c && c <= '9'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsAlphaNumeric reports whether the rune is a letter or digit.
 | 
			
		||||
func IsAlphaNumeric(c rune) bool {
 | 
			
		||||
	return IsLetter(c) || IsDigit(c)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
package specs
 | 
			
		||||
 | 
			
		||||
import "os"
 | 
			
		||||
 | 
			
		||||
// CurrentVersion is the current version of the Spec.
 | 
			
		||||
const CurrentVersion = "0.6.0"
 | 
			
		||||
 | 
			
		||||
// Spec is the base configuration for CDI
 | 
			
		||||
type Spec struct {
 | 
			
		||||
	Version string `json:"cdiVersion"`
 | 
			
		||||
	Kind    string `json:"kind"`
 | 
			
		||||
	// Annotations add meta information per CDI spec. Note these are CDI-specific and do not affect container metadata.
 | 
			
		||||
	Annotations    map[string]string `json:"annotations,omitempty"`
 | 
			
		||||
	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"`
 | 
			
		||||
	// Annotations add meta information per device. Note these are CDI-specific and do not affect container metadata.
 | 
			
		||||
	Annotations    map[string]string `json:"annotations,omitempty"`
 | 
			
		||||
	ContainerEdits ContainerEdits    `json:"containerEdits"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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"`
 | 
			
		||||
	HostPath    string       `json:"hostPath,omitempty"`
 | 
			
		||||
	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"`
 | 
			
		||||
	Type          string   `json:"type,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"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										113
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										113
									
								
								vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,113 +0,0 @@
 | 
			
		||||
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 globally
 | 
			
		||||
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,
 | 
			
		||||
		Type:        m.Type,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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