Merge pull request #1239 from giuseppe/validate-cdi-devices
validate: ignore validation of CDI devices
This commit is contained in:
commit
32d0d9fc59
|
|
@ -4,6 +4,7 @@ go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1
|
github.com/BurntSushi/toml v1.2.1
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface v0.5.3
|
||||||
github.com/containerd/containerd v1.6.10
|
github.com/containerd/containerd v1.6.10
|
||||||
github.com/containernetworking/cni v1.1.2
|
github.com/containernetworking/cni v1.1.2
|
||||||
github.com/containernetworking/plugins v1.1.1
|
github.com/containernetworking/plugins v1.1.1
|
||||||
|
|
@ -112,6 +113,7 @@ require (
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
retract (
|
retract (
|
||||||
|
|
|
||||||
1091
common/go.sum
1091
common/go.sum
File diff suppressed because it is too large
Load Diff
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -57,6 +58,9 @@ func (c *EngineConfig) validatePaths() error {
|
||||||
|
|
||||||
func (c *ContainersConfig) validateDevices() error {
|
func (c *ContainersConfig) validateDevices() error {
|
||||||
for _, d := range c.Devices {
|
for _, d := range c.Devices {
|
||||||
|
if cdi.IsQualifiedName(d) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_, _, _, err := Device(d)
|
_, _, _, err := Device(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
201
common/vendor/github.com/container-orchestrated-devices/container-device-interface/LICENSE
generated
vendored
Normal file
201
common/vendor/github.com/container-orchestrated-devices/container-device-interface/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
82
common/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/multierror/multierror.go
generated
vendored
Normal file
82
common/vendor/github.com/container-orchestrated-devices/container-device-interface/internal/multierror/multierror.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
||||||
139
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go
generated
vendored
Normal file
139
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go
generated
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2021-2022 The CDI Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cdi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AnnotationPrefix is the prefix for CDI container annotation keys.
|
||||||
|
AnnotationPrefix = "cdi.k8s.io/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateAnnotations updates annotations with a plugin-specific CDI device
|
||||||
|
// injection request for the given devices. Upon any error a non-nil error
|
||||||
|
// is returned and annotations are left intact. By convention plugin should
|
||||||
|
// be in the format of "vendor.device-type".
|
||||||
|
func UpdateAnnotations(annotations map[string]string, plugin string, deviceID string, devices []string) (map[string]string, error) {
|
||||||
|
key, err := AnnotationKey(plugin, deviceID)
|
||||||
|
if err != nil {
|
||||||
|
return annotations, errors.Wrap(err, "CDI annotation failed")
|
||||||
|
}
|
||||||
|
if _, ok := annotations[key]; ok {
|
||||||
|
return annotations, errors.Errorf("CDI annotation failed, key %q used", key)
|
||||||
|
}
|
||||||
|
value, err := AnnotationValue(devices)
|
||||||
|
if err != nil {
|
||||||
|
return annotations, errors.Wrap(err, "CDI annotation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if annotations == nil {
|
||||||
|
annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
annotations[key] = value
|
||||||
|
|
||||||
|
return annotations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnnotations parses annotations for CDI device injection requests.
|
||||||
|
// The keys and devices from all such requests are collected into slices
|
||||||
|
// which are returned as the result. All devices are expected to be fully
|
||||||
|
// qualified CDI device names. If any device fails this check empty slices
|
||||||
|
// are returned along with a non-nil error. The annotations are expected
|
||||||
|
// to be formatted by, or in a compatible fashion to UpdateAnnotations().
|
||||||
|
func ParseAnnotations(annotations map[string]string) ([]string, []string, error) {
|
||||||
|
var (
|
||||||
|
keys []string
|
||||||
|
devices []string
|
||||||
|
)
|
||||||
|
|
||||||
|
for key, value := range annotations {
|
||||||
|
if !strings.HasPrefix(key, AnnotationPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, d := range strings.Split(value, ",") {
|
||||||
|
if !IsQualifiedName(d) {
|
||||||
|
return nil, nil, errors.Errorf("invalid CDI device name %q", d)
|
||||||
|
}
|
||||||
|
devices = append(devices, d)
|
||||||
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, devices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnotationKey returns a unique annotation key for an device allocation
|
||||||
|
// by a K8s device plugin. pluginName should be in the format of
|
||||||
|
// "vendor.device-type". deviceID is the ID of the device the plugin is
|
||||||
|
// allocating. It is used to make sure that the generated key is unique
|
||||||
|
// even if multiple allocations by a single plugin needs to be annotated.
|
||||||
|
func AnnotationKey(pluginName, deviceID string) (string, error) {
|
||||||
|
const maxNameLen = 63
|
||||||
|
|
||||||
|
if pluginName == "" {
|
||||||
|
return "", errors.New("invalid plugin name, empty")
|
||||||
|
}
|
||||||
|
if deviceID == "" {
|
||||||
|
return "", errors.New("invalid deviceID, empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := pluginName + "_" + strings.ReplaceAll(deviceID, "/", "_")
|
||||||
|
|
||||||
|
if len(name) > maxNameLen {
|
||||||
|
return "", errors.Errorf("invalid plugin+deviceID %q, too long", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := rune(name[0]); !isAlphaNumeric(c) {
|
||||||
|
return "", errors.Errorf("invalid name %q, first '%c' should be alphanumeric",
|
||||||
|
name, c)
|
||||||
|
}
|
||||||
|
if len(name) > 2 {
|
||||||
|
for _, c := range name[1 : len(name)-1] {
|
||||||
|
switch {
|
||||||
|
case isAlphaNumeric(c):
|
||||||
|
case c == '_' || c == '-' || c == '.':
|
||||||
|
default:
|
||||||
|
return "", errors.Errorf("invalid name %q, invalid charcter '%c'",
|
||||||
|
name, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c := rune(name[len(name)-1]); !isAlphaNumeric(c) {
|
||||||
|
return "", errors.Errorf("invalid name %q, last '%c' should be alphanumeric",
|
||||||
|
name, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnnotationPrefix + name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnotationValue returns an annotation value for the given devices.
|
||||||
|
func AnnotationValue(devices []string) (string, error) {
|
||||||
|
value, sep := "", ""
|
||||||
|
for _, d := range devices {
|
||||||
|
if _, _, _, err := ParseQualifiedName(d); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
value += sep + d
|
||||||
|
sep = ","
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
572
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
generated
vendored
Normal file
572
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
generated
vendored
Normal file
|
|
@ -0,0 +1,572 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2021 The CDI Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cdi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
stderr "errors"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option is an option to change some aspect of default CDI behavior.
|
||||||
|
type Option func(*Cache) error
|
||||||
|
|
||||||
|
// Cache stores CDI Specs loaded from Spec directories.
|
||||||
|
type Cache struct {
|
||||||
|
sync.Mutex
|
||||||
|
specDirs []string
|
||||||
|
specs map[string][]*Spec
|
||||||
|
devices map[string]*Device
|
||||||
|
errors map[string][]error
|
||||||
|
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 errors.Wrapf(err, "failed to apply cache options")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(errors.Errorf("conflicting device %q (specs %q, %q)",
|
||||||
|
name, devPath, oldPath), devPath, oldPath)
|
||||||
|
conflicts[name] = struct{}{}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = scanSpecDirs(c.specDirs, func(path string, priority int, spec *Spec, err error) error {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
if err != nil {
|
||||||
|
collectError(errors.Wrapf(err, "failed to load CDI Spec"), path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vendor := spec.GetVendor()
|
||||||
|
specs[vendor] = append(specs[vendor], spec)
|
||||||
|
|
||||||
|
for _, dev := range spec.devices {
|
||||||
|
qualified := dev.GetQualifiedName()
|
||||||
|
other, ok := devices[qualified]
|
||||||
|
if ok {
|
||||||
|
if resolveConflict(qualified, dev, other) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
devices[qualified] = dev
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for conflict := range conflicts {
|
||||||
|
delete(devices, conflict)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.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 explicitly 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, errors.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, errors.Errorf("unresolvable CDI devices %s",
|
||||||
|
strings.Join(devices, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := edits.Apply(ociSpec); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to inject devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 && stderr.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 {
|
||||||
|
return c.errors[spec.GetPath()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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] = errors.Wrap(err, "failed to create watcher")
|
||||||
|
}
|
||||||
|
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] = errors.Wrap(err, "failed to monitor for changes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dir = range removed {
|
||||||
|
w.tracked[dir] = false
|
||||||
|
dirErrors[dir] = errors.New("directory removed")
|
||||||
|
update = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return update
|
||||||
|
}
|
||||||
26
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache_test_unix.go
generated
vendored
Normal file
26
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache_test_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
//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()
|
||||||
|
}
|
||||||
22
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache_test_windows.go
generated
vendored
Normal file
22
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache_test_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
//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() {}
|
||||||
331
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go
generated
vendored
Normal file
331
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go
generated
vendored
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2021 The CDI Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cdi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/container-orchestrated-devices/container-device-interface/specs-go"
|
||||||
|
oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
ocigen "github.com/opencontainers/runtime-tools/generate"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 errors.Errorf("unknown hook name %q", h.HookName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate container edits.
|
||||||
|
func (e *ContainerEdits) Validate() error {
|
||||||
|
if e == nil || e.ContainerEdits == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ValidateEnv(e.Env); err != nil {
|
||||||
|
return errors.Wrap(err, "invalid container edits")
|
||||||
|
}
|
||||||
|
for _, d := range e.DeviceNodes {
|
||||||
|
if err := (&DeviceNode{d}).Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, h := range e.Hooks {
|
||||||
|
if err := (&Hook{h}).Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range e.Mounts {
|
||||||
|
if err := (&Mount{m}).Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append other edits into this one. If called with a nil receiver,
|
||||||
|
// allocates and returns newly allocated edits.
|
||||||
|
func (e *ContainerEdits) Append(o *ContainerEdits) *ContainerEdits {
|
||||||
|
if o == nil || o.ContainerEdits == nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
if e == nil {
|
||||||
|
e = &ContainerEdits{}
|
||||||
|
}
|
||||||
|
if e.ContainerEdits == nil {
|
||||||
|
e.ContainerEdits = &specs.ContainerEdits{}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Env = append(e.Env, o.Env...)
|
||||||
|
e.DeviceNodes = append(e.DeviceNodes, o.DeviceNodes...)
|
||||||
|
e.Hooks = append(e.Hooks, o.Hooks...)
|
||||||
|
e.Mounts = append(e.Mounts, o.Mounts...)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEmpty returns true if these edits are empty. This is valid in a
|
||||||
|
// global Spec context but invalid in a Device context.
|
||||||
|
func (e *ContainerEdits) isEmpty() bool {
|
||||||
|
if e == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(e.Env)+len(e.DeviceNodes)+len(e.Hooks)+len(e.Mounts) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateEnv validates the given environment variables.
|
||||||
|
func ValidateEnv(env []string) error {
|
||||||
|
for _, v := range env {
|
||||||
|
if strings.IndexByte(v, byte('=')) <= 0 {
|
||||||
|
return errors.Errorf("invalid environment variable %q", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceNode is a CDI Spec DeviceNode wrapper, used for validating DeviceNodes.
|
||||||
|
type DeviceNode struct {
|
||||||
|
*specs.DeviceNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate a CDI Spec DeviceNode.
|
||||||
|
func (d *DeviceNode) Validate() error {
|
||||||
|
validTypes := map[string]struct{}{
|
||||||
|
"": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
"u": {},
|
||||||
|
"p": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Path == "" {
|
||||||
|
return errors.New("invalid (empty) device path")
|
||||||
|
}
|
||||||
|
if _, ok := validTypes[d.Type]; !ok {
|
||||||
|
return errors.Errorf("device %q: invalid type %q", d.Path, d.Type)
|
||||||
|
}
|
||||||
|
for _, bit := range d.Permissions {
|
||||||
|
if bit != 'r' && bit != 'w' && bit != 'm' {
|
||||||
|
return errors.Errorf("device %q: invalid persmissions %q",
|
||||||
|
d.Path, d.Permissions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook is a CDI Spec Hook wrapper, used for validating hooks.
|
||||||
|
type Hook struct {
|
||||||
|
*specs.Hook
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate a hook.
|
||||||
|
func (h *Hook) Validate() error {
|
||||||
|
if _, ok := validHookNames[h.HookName]; !ok {
|
||||||
|
return errors.Errorf("invalid hook name %q", h.HookName)
|
||||||
|
}
|
||||||
|
if h.Path == "" {
|
||||||
|
return errors.Errorf("invalid hook %q with empty path", h.HookName)
|
||||||
|
}
|
||||||
|
if err := ValidateEnv(h.Env); err != nil {
|
||||||
|
return errors.Wrapf(err, "invalid hook %q", h.HookName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount is a CDI Mount wrapper, used for validating mounts.
|
||||||
|
type Mount struct {
|
||||||
|
*specs.Mount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate a mount.
|
||||||
|
func (m *Mount) Validate() error {
|
||||||
|
if m.HostPath == "" {
|
||||||
|
return errors.New("invalid mount, empty host path")
|
||||||
|
}
|
||||||
|
if m.ContainerPath == "" {
|
||||||
|
return errors.New("invalid mount, empty container path")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure OCI Spec hooks are not nil so we can add hooks.
|
||||||
|
func ensureOCIHooks(spec *oci.Spec) {
|
||||||
|
if spec.Hooks == nil {
|
||||||
|
spec.Hooks = &oci.Hooks{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
56
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_unix.go
generated
vendored
Normal file
56
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
//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 (
|
||||||
|
runc "github.com/opencontainers/runc/libcontainer/devices"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
hostDev, err := runc.DeviceFromPath(d.HostPath, "rwm")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to stat CDI host device %q", d.HostPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Type == "" {
|
||||||
|
d.Type = string(hostDev.Type)
|
||||||
|
} else {
|
||||||
|
if d.Type != string(hostDev.Type) {
|
||||||
|
return errors.Errorf("CDI device (%q, %q), host type mismatch (%s, %s)",
|
||||||
|
d.Path, d.HostPath, d.Type, string(hostDev.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d.Major == 0 && d.Type != "p" {
|
||||||
|
d.Major = hostDev.Major
|
||||||
|
d.Minor = hostDev.Minor
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
27
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_windows.go
generated
vendored
Normal file
27
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
//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")
|
||||||
|
}
|
||||||
78
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go
generated
vendored
Normal file
78
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go
generated
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2021 The CDI Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cdi
|
||||||
|
|
||||||
|
import (
|
||||||
|
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
|
||||||
|
oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device represents a CDI device of a Spec.
|
||||||
|
type Device struct {
|
||||||
|
*cdi.Device
|
||||||
|
spec *Spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Device, associate it with the given Spec.
|
||||||
|
func newDevice(spec *Spec, d cdi.Device) (*Device, error) {
|
||||||
|
dev := &Device{
|
||||||
|
Device: &d,
|
||||||
|
spec: spec,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dev.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSpec returns the Spec this device is defined in.
|
||||||
|
func (d *Device) GetSpec() *Spec {
|
||||||
|
return d.spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQualifiedName returns the qualified name for this device.
|
||||||
|
func (d *Device) GetQualifiedName() string {
|
||||||
|
return QualifiedName(d.spec.GetVendor(), d.spec.GetClass(), d.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyEdits applies the device-speific container edits to an OCI Spec.
|
||||||
|
func (d *Device) ApplyEdits(ociSpec *oci.Spec) error {
|
||||||
|
return d.edits().Apply(ociSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// edits returns the applicable container edits for this spec.
|
||||||
|
func (d *Device) edits() *ContainerEdits {
|
||||||
|
return &ContainerEdits{&d.ContainerEdits}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the device.
|
||||||
|
func (d *Device) validate() error {
|
||||||
|
if err := ValidateDeviceName(d.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
edits := d.edits()
|
||||||
|
if edits.isEmpty() {
|
||||||
|
return errors.Errorf("invalid device, empty device edits")
|
||||||
|
}
|
||||||
|
if err := edits.Validate(); err != nil {
|
||||||
|
return errors.Wrapf(err, "invalid device %q", d.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
274
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go
generated
vendored
Normal file
274
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
// Package cdi has the primary purpose of providing an API for
|
||||||
|
// interacting with CDI and consuming CDI devices.
|
||||||
|
//
|
||||||
|
// For more information about Container Device Interface, please refer to
|
||||||
|
// https://github.com/container-orchestrated-devices/container-device-interface
|
||||||
|
//
|
||||||
|
// Container Device Interface
|
||||||
|
//
|
||||||
|
// Container Device Interface, or CDI for short, provides comprehensive
|
||||||
|
// third party device support for container runtimes. CDI uses vendor
|
||||||
|
// provided specification files, CDI Specs for short, to describe how a
|
||||||
|
// container's runtime environment should be modified when one or more
|
||||||
|
// of the vendor-specific devices is injected into the container. Beyond
|
||||||
|
// describing the low level platform-specific details of how to gain
|
||||||
|
// basic access to a device, CDI Specs allow more fine-grained device
|
||||||
|
// initialization, and the automatic injection of any necessary vendor-
|
||||||
|
// or device-specific software that might be required for a container
|
||||||
|
// to use a device or take full advantage of it.
|
||||||
|
//
|
||||||
|
// In the CDI device model containers request access to a device using
|
||||||
|
// fully qualified device names, qualified names for short, consisting of
|
||||||
|
// a vendor identifier, a device class and a device name or identifier.
|
||||||
|
// These pieces of information together uniquely identify a device among
|
||||||
|
// all device vendors, classes and device instances.
|
||||||
|
//
|
||||||
|
// This package implements an API for easy consumption of CDI. The API
|
||||||
|
// implements discovery, loading and caching of CDI Specs and injection
|
||||||
|
// of CDI devices into containers. This is the most common functionality
|
||||||
|
// the vast majority of CDI consumers need. The API should be usable both
|
||||||
|
// by OCI runtime clients and runtime implementations.
|
||||||
|
//
|
||||||
|
// CDI Registry
|
||||||
|
//
|
||||||
|
// The primary interface to interact with CDI devices is the Registry. It
|
||||||
|
// is essentially a cache of all Specs and devices discovered in standard
|
||||||
|
// CDI directories on the host. The registry has two main functionality,
|
||||||
|
// injecting devices into an OCI Spec and refreshing the cache of CDI
|
||||||
|
// Specs and devices.
|
||||||
|
//
|
||||||
|
// Device Injection
|
||||||
|
//
|
||||||
|
// Using the Registry one can inject CDI devices into a container with code
|
||||||
|
// similar to the following snippet:
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
// "strings"
|
||||||
|
//
|
||||||
|
// "github.com/pkg/errors"
|
||||||
|
// log "github.com/sirupsen/logrus"
|
||||||
|
//
|
||||||
|
// "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
|
||||||
|
// oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func injectCDIDevices(spec *oci.Spec, devices []string) error {
|
||||||
|
// log.Debug("pristine OCI Spec: %s", dumpSpec(spec))
|
||||||
|
//
|
||||||
|
// unresolved, err := cdi.GetRegistry().InjectDevices(spec, devices)
|
||||||
|
// if err != nil {
|
||||||
|
// return errors.Wrap(err, "CDI device injection failed")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec))
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Cache Refresh
|
||||||
|
//
|
||||||
|
// 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"
|
||||||
|
//
|
||||||
|
// "github.com/pkg/errors"
|
||||||
|
// log "github.com/sirupsen/logrus"
|
||||||
|
//
|
||||||
|
// "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
|
||||||
|
// oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func injectCDIDevices(spec *oci.Spec, devices []string) error {
|
||||||
|
// registry := cdi.GetRegistry()
|
||||||
|
//
|
||||||
|
// if err := registry.Refresh(); err != nil {
|
||||||
|
// // Note:
|
||||||
|
// // It is up to the implementation to decide whether
|
||||||
|
// // to abort injection on errors. A failed Refresh()
|
||||||
|
// // does not necessarily render the registry unusable.
|
||||||
|
// // For instance, a parse error in a Spec file for
|
||||||
|
// // vendor A does not have any effect on devices of
|
||||||
|
// // vendor B...
|
||||||
|
// log.Warnf("pre-injection Refresh() failed: %v", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// log.Debug("pristine OCI Spec: %s", dumpSpec(spec))
|
||||||
|
//
|
||||||
|
// unresolved, err := registry.InjectDevices(spec, devices)
|
||||||
|
// if err != nil {
|
||||||
|
// return errors.Wrap(err, "CDI device injection failed")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec))
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Generated Spec Files, Multiple Directories, Device Precedence
|
||||||
|
//
|
||||||
|
// 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 chosing the definition from the
|
||||||
|
// Spec file with the highest priority.
|
||||||
|
//
|
||||||
|
// The default CDI directory configuration is chosen to encourage
|
||||||
|
// separating dynamically generated CDI Spec files from static ones.
|
||||||
|
// The default directories are '/etc/cdi' and '/var/run/cdi'. By putting
|
||||||
|
// dynamically generated Spec files under '/var/run/cdi', those take
|
||||||
|
// precedence over static ones in '/etc/cdi'. 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.WriteSpec(spec, specName)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Similary, 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.WriteSpec(spec, specName)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func removeTransientSpec(ctr Container) error {
|
||||||
|
// registry := cdi.GetRegistry()
|
||||||
|
// transientID := getSomeSufficientlyUniqueIDForContainer(ctr)
|
||||||
|
// specName := cdi.GenerateNameForTransientSpec(vendor, class, transientID)
|
||||||
|
//
|
||||||
|
// return registry.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
|
||||||
206
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go
generated
vendored
Normal file
206
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/qualified-device.go
generated
vendored
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2021 The CDI Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cdi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QualifiedName returns the qualified name for a device.
|
||||||
|
// The syntax for a qualified device names is
|
||||||
|
// "<vendor>/<class>=<name>".
|
||||||
|
// A valid vendor name may contain the following runes:
|
||||||
|
// 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
|
||||||
|
// A valid class name may contain the following runes:
|
||||||
|
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_'.
|
||||||
|
// A valid device name may containe the following runes:
|
||||||
|
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'
|
||||||
|
func QualifiedName(vendor, class, name string) string {
|
||||||
|
return vendor + "/" + class + "=" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsQualifiedName tests if a device name is qualified.
|
||||||
|
func IsQualifiedName(device string) bool {
|
||||||
|
_, _, _, err := ParseQualifiedName(device)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseQualifiedName splits a qualified name into device vendor, class,
|
||||||
|
// and name. If the device fails to parse as a qualified name, or if any
|
||||||
|
// of the split components fail to pass syntax validation, vendor and
|
||||||
|
// class are returned as empty, together with the verbatim input as the
|
||||||
|
// name and an error describing the reason for failure.
|
||||||
|
func ParseQualifiedName(device string) (string, string, string, error) {
|
||||||
|
vendor, class, name := ParseDevice(device)
|
||||||
|
|
||||||
|
if vendor == "" {
|
||||||
|
return "", "", device, errors.Errorf("unqualified device %q, missing vendor", device)
|
||||||
|
}
|
||||||
|
if class == "" {
|
||||||
|
return "", "", device, errors.Errorf("unqualified device %q, missing class", device)
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
return "", "", device, errors.Errorf("unqualified device %q, missing device name", device)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ValidateVendorName(vendor); err != nil {
|
||||||
|
return "", "", device, errors.Wrapf(err, "invalid device %q", device)
|
||||||
|
}
|
||||||
|
if err := ValidateClassName(class); err != nil {
|
||||||
|
return "", "", device, errors.Wrapf(err, "invalid device %q", device)
|
||||||
|
}
|
||||||
|
if err := ValidateDeviceName(name); err != nil {
|
||||||
|
return "", "", device, errors.Wrapf(err, "invalid device %q", device)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vendor, class, name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDevice tries to split a device name into vendor, class, and name.
|
||||||
|
// If this fails, for instance in the case of unqualified device names,
|
||||||
|
// ParseDevice returns an empty vendor and class together with name set
|
||||||
|
// to the verbatim input.
|
||||||
|
func ParseDevice(device string) (string, string, string) {
|
||||||
|
if device == "" || device[0] == '/' {
|
||||||
|
return "", "", device
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(device, "=", 2)
|
||||||
|
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||||
|
return "", "", device
|
||||||
|
}
|
||||||
|
|
||||||
|
name := parts[1]
|
||||||
|
vendor, class := ParseQualifier(parts[0])
|
||||||
|
if vendor == "" {
|
||||||
|
return "", "", device
|
||||||
|
}
|
||||||
|
|
||||||
|
return vendor, class, name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseQualifier splits a device qualifier into vendor and class.
|
||||||
|
// The syntax for a device qualifier is
|
||||||
|
// "<vendor>/<class>"
|
||||||
|
// If parsing fails, an empty vendor and the class set to the
|
||||||
|
// verbatim input is returned.
|
||||||
|
func ParseQualifier(kind string) (string, string) {
|
||||||
|
parts := strings.SplitN(kind, "/", 2)
|
||||||
|
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||||
|
return "", kind
|
||||||
|
}
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateVendorName checks the validity of a vendor name.
|
||||||
|
// A vendor name may contain the following ASCII characters:
|
||||||
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
|
// - digits ('0'-'9')
|
||||||
|
// - underscore, dash, and dot ('_', '-', and '.')
|
||||||
|
func ValidateVendorName(vendor string) error {
|
||||||
|
if vendor == "" {
|
||||||
|
return errors.Errorf("invalid (empty) vendor name")
|
||||||
|
}
|
||||||
|
if !isLetter(rune(vendor[0])) {
|
||||||
|
return errors.Errorf("invalid vendor %q, should start with letter", vendor)
|
||||||
|
}
|
||||||
|
for _, c := range string(vendor[1 : len(vendor)-1]) {
|
||||||
|
switch {
|
||||||
|
case isAlphaNumeric(c):
|
||||||
|
case c == '_' || c == '-' || c == '.':
|
||||||
|
default:
|
||||||
|
return errors.Errorf("invalid character '%c' in vendor name %q",
|
||||||
|
c, vendor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isAlphaNumeric(rune(vendor[len(vendor)-1])) {
|
||||||
|
return errors.Errorf("invalid vendor %q, should end with a letter or digit", vendor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateClassName checks the validity of class name.
|
||||||
|
// A class name may contain the following ASCII characters:
|
||||||
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
|
// - digits ('0'-'9')
|
||||||
|
// - underscore and dash ('_', '-')
|
||||||
|
func ValidateClassName(class string) error {
|
||||||
|
if class == "" {
|
||||||
|
return errors.Errorf("invalid (empty) device class")
|
||||||
|
}
|
||||||
|
if !isLetter(rune(class[0])) {
|
||||||
|
return errors.Errorf("invalid class %q, should start with letter", class)
|
||||||
|
}
|
||||||
|
for _, c := range string(class[1 : len(class)-1]) {
|
||||||
|
switch {
|
||||||
|
case isAlphaNumeric(c):
|
||||||
|
case c == '_' || c == '-':
|
||||||
|
default:
|
||||||
|
return errors.Errorf("invalid character '%c' in device class %q",
|
||||||
|
c, class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isAlphaNumeric(rune(class[len(class)-1])) {
|
||||||
|
return errors.Errorf("invalid class %q, should end with a letter or digit", class)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateDeviceName checks the validity of a device name.
|
||||||
|
// A device name may contain the following ASCII characters:
|
||||||
|
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
|
||||||
|
// - digits ('0'-'9')
|
||||||
|
// - underscore, dash, dot, colon ('_', '-', '.', ':')
|
||||||
|
func ValidateDeviceName(name string) error {
|
||||||
|
if name == "" {
|
||||||
|
return errors.Errorf("invalid (empty) device name")
|
||||||
|
}
|
||||||
|
if !isAlphaNumeric(rune(name[0])) {
|
||||||
|
return errors.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 errors.Errorf("invalid character '%c' in device name %q",
|
||||||
|
c, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isAlphaNumeric(rune(name[len(name)-1])) {
|
||||||
|
return errors.Errorf("invalid name %q, should end with a letter or digit", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLetter(c rune) bool {
|
||||||
|
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigit(c rune) bool {
|
||||||
|
return '0' <= c && c <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAlphaNumeric(c rune) bool {
|
||||||
|
return isLetter(c) || isDigit(c)
|
||||||
|
}
|
||||||
152
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go
generated
vendored
Normal file
152
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/registry.go
generated
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
||||||
114
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec-dirs.go
generated
vendored
Normal file
114
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec-dirs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
||||||
350
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
generated
vendored
Normal file
350
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
generated
vendored
Normal file
|
|
@ -0,0 +1,350 @@
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CurrentVersion is the current vesion of the CDI Spec.
|
||||||
|
CurrentVersion = cdi.CurrentVersion
|
||||||
|
|
||||||
|
// defaultSpecExt is the file extension for the default encoding.
|
||||||
|
defaultSpecExt = ".yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Valid CDI Spec versions.
|
||||||
|
validSpecVersions = map[string]struct{}{
|
||||||
|
"0.1.0": {},
|
||||||
|
"0.2.0": {},
|
||||||
|
"0.3.0": {},
|
||||||
|
"0.4.0": {},
|
||||||
|
"0.5.0": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, errors.Wrapf(err, "failed to read CDI Spec %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := ParseSpec(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to parse CDI Spec %q", path)
|
||||||
|
}
|
||||||
|
if raw == nil {
|
||||||
|
return nil, errors.Errorf("failed to parse CDI Spec %q, no Spec data", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
spec, err := newSpec(raw, path, priority)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSpec creates a new Spec from the given CDI Spec data. The
|
||||||
|
// Spec is marked as loaded from the given path with the given
|
||||||
|
// priority. If Spec data validation fails newSpec returns a nil
|
||||||
|
// Spec and an error.
|
||||||
|
func newSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) {
|
||||||
|
err := 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, errors.Wrap(err, "invalid CDI Spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
data, err = json.Marshal(s.Spec)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to marshal Spec file")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir = filepath.Dir(s.path)
|
||||||
|
err = os.MkdirAll(dir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create Spec dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp, err = os.CreateTemp(dir, "spec.*.tmp")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create Spec file")
|
||||||
|
}
|
||||||
|
_, err = tmp.Write(data)
|
||||||
|
tmp.Close()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to write Spec file")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = renameIn(dir, filepath.Base(tmp.Name()), filepath.Base(s.path), overwrite)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(tmp.Name())
|
||||||
|
err = errors.Wrap(err, "failed to write Spec file")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if err := ValidateVendorName(s.vendor); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ValidateClassName(s.class); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.edits().Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
devices := make(map[string]*Device)
|
||||||
|
for _, d := range s.Devices {
|
||||||
|
dev, err := newDevice(s, d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed add device %q", d.Name)
|
||||||
|
}
|
||||||
|
if _, conflict := devices[d.Name]; conflict {
|
||||||
|
return nil, errors.Errorf("invalid spec, multiple device %q", d.Name)
|
||||||
|
}
|
||||||
|
devices[d.Name] = dev
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateVersion checks whether the specified spec version is supported.
|
||||||
|
func validateVersion(version string) error {
|
||||||
|
if _, ok := validSpecVersions[version]; !ok {
|
||||||
|
return errors.Errorf("invalid version %q", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, errors.Wrap(err, "failed to unmarshal CDI Spec")
|
||||||
|
}
|
||||||
|
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 errors.Wrap(err, "Spec validation failed")
|
||||||
|
}
|
||||||
|
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 tranient 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 "", errors.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 "", errors.Errorf("invalid vendor/class %q in Spec", raw.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenerateTransientSpecName(vendor, class, transientID), nil
|
||||||
|
}
|
||||||
48
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_linux.go
generated
vendored
Normal file
48
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"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 errors.Wrap(err, "rename failed")
|
||||||
|
}
|
||||||
|
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 errors.Wrap(err, "rename failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
39
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_other.go
generated
vendored
Normal file
39
common/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec_other.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
//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)
|
||||||
|
}
|
||||||
59
common/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go
generated
vendored
Normal file
59
common/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
package specs
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// CurrentVersion is the current version of the Spec.
|
||||||
|
const CurrentVersion = "0.5.0"
|
||||||
|
|
||||||
|
// Spec is the base configuration for CDI
|
||||||
|
type Spec struct {
|
||||||
|
Version string `json:"cdiVersion"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
|
||||||
|
Devices []Device `json:"devices"`
|
||||||
|
ContainerEdits ContainerEdits `json:"containerEdits,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device is a "Device" a container runtime can add to a container
|
||||||
|
type Device struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ContainerEdits ContainerEdits `json:"containerEdits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerEdits are edits a container runtime must make to the OCI spec to expose the device.
|
||||||
|
type ContainerEdits struct {
|
||||||
|
Env []string `json:"env,omitempty"`
|
||||||
|
DeviceNodes []*DeviceNode `json:"deviceNodes,omitempty"`
|
||||||
|
Hooks []*Hook `json:"hooks,omitempty"`
|
||||||
|
Mounts []*Mount `json:"mounts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceNode represents a device node that needs to be added to the OCI spec.
|
||||||
|
type DeviceNode struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
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
common/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go
generated
vendored
Normal file
113
common/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go
generated
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
package specs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyOCIEditsForDevice applies devices OCI edits, in other words
|
||||||
|
// it finds the device in the CDI spec and applies the OCI patches that device
|
||||||
|
// requires to the OCI specification.
|
||||||
|
func ApplyOCIEditsForDevice(config *spec.Spec, cdi *Spec, dev string) error {
|
||||||
|
for _, d := range cdi.Devices {
|
||||||
|
if d.Name != dev {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplyEditsToOCISpec(config, &d.ContainerEdits)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("CDI: device %q not found for spec %q", dev, cdi.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyOCIEdits applies the OCI edits the CDI spec declares globablly
|
||||||
|
func ApplyOCIEdits(config *spec.Spec, cdi *Spec) error {
|
||||||
|
return ApplyEditsToOCISpec(config, &cdi.ContainerEdits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyEditsToOCISpec applies the specified edits to the OCI spec.
|
||||||
|
func ApplyEditsToOCISpec(config *spec.Spec, edits *ContainerEdits) error {
|
||||||
|
if config == nil {
|
||||||
|
return errors.New("spec is nil")
|
||||||
|
}
|
||||||
|
if edits == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(edits.Env) > 0 {
|
||||||
|
if config.Process == nil {
|
||||||
|
config.Process = &spec.Process{}
|
||||||
|
}
|
||||||
|
config.Process.Env = append(config.Process.Env, edits.Env...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range edits.DeviceNodes {
|
||||||
|
if config.Linux == nil {
|
||||||
|
config.Linux = &spec.Linux{}
|
||||||
|
}
|
||||||
|
config.Linux.Devices = append(config.Linux.Devices, d.ToOCI())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range edits.Mounts {
|
||||||
|
config.Mounts = append(config.Mounts, m.ToOCI())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range edits.Hooks {
|
||||||
|
if config.Hooks == nil {
|
||||||
|
config.Hooks = &spec.Hooks{}
|
||||||
|
}
|
||||||
|
switch h.HookName {
|
||||||
|
case "prestart":
|
||||||
|
config.Hooks.Prestart = append(config.Hooks.Prestart, h.ToOCI())
|
||||||
|
case "createRuntime":
|
||||||
|
config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, h.ToOCI())
|
||||||
|
case "createContainer":
|
||||||
|
config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, h.ToOCI())
|
||||||
|
case "startContainer":
|
||||||
|
config.Hooks.StartContainer = append(config.Hooks.StartContainer, h.ToOCI())
|
||||||
|
case "poststart":
|
||||||
|
config.Hooks.Poststart = append(config.Hooks.Poststart, h.ToOCI())
|
||||||
|
case "poststop":
|
||||||
|
config.Hooks.Poststop = append(config.Hooks.Poststop, h.ToOCI())
|
||||||
|
default:
|
||||||
|
fmt.Printf("CDI: Unknown hook %q\n", h.HookName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOCI returns the opencontainers runtime Spec Hook for this Hook.
|
||||||
|
func (h *Hook) ToOCI() spec.Hook {
|
||||||
|
return spec.Hook{
|
||||||
|
Path: h.Path,
|
||||||
|
Args: h.Args,
|
||||||
|
Env: h.Env,
|
||||||
|
Timeout: h.Timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOCI returns the opencontainers runtime Spec Mount for this Mount.
|
||||||
|
func (m *Mount) ToOCI() spec.Mount {
|
||||||
|
return spec.Mount{
|
||||||
|
Source: m.HostPath,
|
||||||
|
Destination: m.ContainerPath,
|
||||||
|
Options: m.Options,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,6 +46,11 @@ github.com/blang/semver/v4
|
||||||
# github.com/chzyer/readline v1.5.1
|
# github.com/chzyer/readline v1.5.1
|
||||||
## explicit; go 1.15
|
## explicit; go 1.15
|
||||||
github.com/chzyer/readline
|
github.com/chzyer/readline
|
||||||
|
# github.com/container-orchestrated-devices/container-device-interface v0.5.3
|
||||||
|
## explicit; go 1.17
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface/internal/multierror
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface/pkg/cdi
|
||||||
|
github.com/container-orchestrated-devices/container-device-interface/specs-go
|
||||||
# github.com/containerd/cgroups v1.0.4
|
# github.com/containerd/cgroups v1.0.4
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/containerd/cgroups/stats/v1
|
github.com/containerd/cgroups/stats/v1
|
||||||
|
|
@ -730,4 +735,7 @@ gopkg.in/yaml.v2
|
||||||
# gopkg.in/yaml.v3 v3.0.1
|
# gopkg.in/yaml.v3 v3.0.1
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/yaml.v3
|
gopkg.in/yaml.v3
|
||||||
|
# sigs.k8s.io/yaml v1.3.0
|
||||||
|
## explicit; go 1.12
|
||||||
|
sigs.k8s.io/yaml
|
||||||
# github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc
|
# github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.1-0.20220617142545-8b9452f75cbc
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# OSX leaves these everywhere on SMB shares
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Eclipse files
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.settings/**
|
||||||
|
|
||||||
|
# Idea files
|
||||||
|
.idea/**
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Emacs save files
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Vim-related files
|
||||||
|
[._]*.s[a-w][a-z]
|
||||||
|
[._]s[a-w][a-z]
|
||||||
|
*.un~
|
||||||
|
Session.vim
|
||||||
|
.netrwhist
|
||||||
|
|
||||||
|
# Go test binaries
|
||||||
|
*.test
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
language: go
|
||||||
|
arch: arm64
|
||||||
|
dist: focal
|
||||||
|
go: 1.15.x
|
||||||
|
script:
|
||||||
|
- diff -u <(echo -n) <(gofmt -d *.go)
|
||||||
|
- diff -u <(echo -n) <(golint $(go list -e ./...) | grep -v YAMLToJSON)
|
||||||
|
- GO111MODULE=on go vet .
|
||||||
|
- GO111MODULE=on go test -v -race ./...
|
||||||
|
- git diff --exit-code
|
||||||
|
install:
|
||||||
|
- GO111MODULE=off go get golang.org/x/lint/golint
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
|
||||||
|
|
||||||
|
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
We have full documentation on how to get started contributing here:
|
||||||
|
|
||||||
|
<!---
|
||||||
|
If your repo has certain guidelines for contribution, put them here ahead of the general k8s resources
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
||||||
|
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
||||||
|
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
|
||||||
|
|
||||||
|
## Mentorship
|
||||||
|
|
||||||
|
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Custom Information - if you're copying this template for the first time you can add custom content here, for example:
|
||||||
|
|
||||||
|
## Contact Information
|
||||||
|
|
||||||
|
- [Slack channel](https://kubernetes.slack.com/messages/kubernetes-users) - Replace `kubernetes-users` with your slack channel string, this will send users directly to your channel.
|
||||||
|
- [Mailing list](URL)
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Sam Ghods
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
approvers:
|
||||||
|
- dims
|
||||||
|
- lavalamp
|
||||||
|
- smarterclayton
|
||||||
|
- deads2k
|
||||||
|
- sttts
|
||||||
|
- liggitt
|
||||||
|
- caesarxuchao
|
||||||
|
reviewers:
|
||||||
|
- dims
|
||||||
|
- thockin
|
||||||
|
- lavalamp
|
||||||
|
- smarterclayton
|
||||||
|
- wojtek-t
|
||||||
|
- deads2k
|
||||||
|
- derekwaynecarr
|
||||||
|
- caesarxuchao
|
||||||
|
- mikedanese
|
||||||
|
- liggitt
|
||||||
|
- gmarek
|
||||||
|
- sttts
|
||||||
|
- ncdc
|
||||||
|
- tallclair
|
||||||
|
labels:
|
||||||
|
- sig/api-machinery
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
# YAML marshaling and unmarshaling support for Go
|
||||||
|
|
||||||
|
[](https://travis-ci.org/kubernetes-sigs/yaml)
|
||||||
|
|
||||||
|
kubernetes-sigs/yaml is a permanent fork of [ghodss/yaml](https://github.com/ghodss/yaml).
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs.
|
||||||
|
|
||||||
|
In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://web.archive.org/web/20190603050330/http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/).
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility).
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example:
|
||||||
|
|
||||||
|
```
|
||||||
|
BAD:
|
||||||
|
exampleKey: !!binary gIGC
|
||||||
|
|
||||||
|
GOOD:
|
||||||
|
exampleKey: gIGC
|
||||||
|
... and decode the base64 data in your code.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys.
|
||||||
|
|
||||||
|
## Installation and usage
|
||||||
|
|
||||||
|
To install, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get sigs.k8s.io/yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
And import using:
|
||||||
|
|
||||||
|
```
|
||||||
|
import "sigs.k8s.io/yaml"
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage is very similar to the JSON library:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Name string `json:"name"` // Affects YAML field names too.
|
||||||
|
Age int `json:"age"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Marshal a Person struct to YAML.
|
||||||
|
p := Person{"John", 30}
|
||||||
|
y, err := yaml.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(y))
|
||||||
|
/* Output:
|
||||||
|
age: 30
|
||||||
|
name: John
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Unmarshal the YAML back into a Person struct.
|
||||||
|
var p2 Person
|
||||||
|
err = yaml.Unmarshal(y, &p2)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(p2)
|
||||||
|
/* Output:
|
||||||
|
{John 30}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
j := []byte(`{"name": "John", "age": 30}`)
|
||||||
|
y, err := yaml.JSONToYAML(j)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(y))
|
||||||
|
/* Output:
|
||||||
|
age: 30
|
||||||
|
name: John
|
||||||
|
*/
|
||||||
|
j2, err := yaml.YAMLToJSON(y)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(string(j2))
|
||||||
|
/* Output:
|
||||||
|
{"age":30,"name":"John"}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Release Process
|
||||||
|
|
||||||
|
The `yaml` Project is released on an as-needed basis. The process is as follows:
|
||||||
|
|
||||||
|
1. An issue is proposing a new release with a changelog since the last release
|
||||||
|
1. All [OWNERS](OWNERS) must LGTM this release
|
||||||
|
1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION`
|
||||||
|
1. The release issue is closed
|
||||||
|
1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-template-project $VERSION is released`
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Defined below are the security contacts for this repo.
|
||||||
|
#
|
||||||
|
# They are the contact point for the Product Security Team to reach out
|
||||||
|
# to for triaging and handling of incoming issues.
|
||||||
|
#
|
||||||
|
# The below names agree to abide by the
|
||||||
|
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
|
||||||
|
# and will be removed and replaced if they violate that agreement.
|
||||||
|
#
|
||||||
|
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
|
||||||
|
# INSTRUCTIONS AT https://kubernetes.io/security/
|
||||||
|
|
||||||
|
cjcullen
|
||||||
|
jessfraz
|
||||||
|
liggitt
|
||||||
|
philips
|
||||||
|
tallclair
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Kubernetes Community Code of Conduct
|
||||||
|
|
||||||
|
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
|
||||||
|
|
@ -0,0 +1,502 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// indirect walks down v allocating pointers as needed,
|
||||||
|
// until it gets to a non-pointer.
|
||||||
|
// if it encounters an Unmarshaler, indirect stops and returns that.
|
||||||
|
// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
|
||||||
|
func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
|
||||||
|
// If v is a named type and is addressable,
|
||||||
|
// start with its address, so that if the type has pointer methods,
|
||||||
|
// we find them.
|
||||||
|
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
// Load value from interface, but only if the result will be
|
||||||
|
// usefully addressable.
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
e := v.Elem()
|
||||||
|
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
|
||||||
|
v = e
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if v.IsNil() {
|
||||||
|
if v.CanSet() {
|
||||||
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
|
} else {
|
||||||
|
v = reflect.New(v.Type().Elem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.Type().NumMethod() > 0 {
|
||||||
|
if u, ok := v.Interface().(json.Unmarshaler); ok {
|
||||||
|
return u, nil, reflect.Value{}
|
||||||
|
}
|
||||||
|
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
|
||||||
|
return nil, u, reflect.Value{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return nil, nil, v
|
||||||
|
}
|
||||||
|
|
||||||
|
// A field represents a single field found in a struct.
|
||||||
|
type field struct {
|
||||||
|
name string
|
||||||
|
nameBytes []byte // []byte(name)
|
||||||
|
equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent
|
||||||
|
|
||||||
|
tag bool
|
||||||
|
index []int
|
||||||
|
typ reflect.Type
|
||||||
|
omitEmpty bool
|
||||||
|
quoted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillField(f field) field {
|
||||||
|
f.nameBytes = []byte(f.name)
|
||||||
|
f.equalFold = foldFunc(f.nameBytes)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// byName sorts field by name, breaking ties with depth,
|
||||||
|
// then breaking ties with "name came from json tag", then
|
||||||
|
// breaking ties with index sequence.
|
||||||
|
type byName []field
|
||||||
|
|
||||||
|
func (x byName) Len() int { return len(x) }
|
||||||
|
|
||||||
|
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
func (x byName) Less(i, j int) bool {
|
||||||
|
if x[i].name != x[j].name {
|
||||||
|
return x[i].name < x[j].name
|
||||||
|
}
|
||||||
|
if len(x[i].index) != len(x[j].index) {
|
||||||
|
return len(x[i].index) < len(x[j].index)
|
||||||
|
}
|
||||||
|
if x[i].tag != x[j].tag {
|
||||||
|
return x[i].tag
|
||||||
|
}
|
||||||
|
return byIndex(x).Less(i, j)
|
||||||
|
}
|
||||||
|
|
||||||
|
// byIndex sorts field by index sequence.
|
||||||
|
type byIndex []field
|
||||||
|
|
||||||
|
func (x byIndex) Len() int { return len(x) }
|
||||||
|
|
||||||
|
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
func (x byIndex) Less(i, j int) bool {
|
||||||
|
for k, xik := range x[i].index {
|
||||||
|
if k >= len(x[j].index) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if xik != x[j].index[k] {
|
||||||
|
return xik < x[j].index[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(x[i].index) < len(x[j].index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeFields returns a list of fields that JSON should recognize for the given type.
|
||||||
|
// The algorithm is breadth-first search over the set of structs to include - the top struct
|
||||||
|
// and then any reachable anonymous structs.
|
||||||
|
func typeFields(t reflect.Type) []field {
|
||||||
|
// Anonymous fields to explore at the current level and the next.
|
||||||
|
current := []field{}
|
||||||
|
next := []field{{typ: t}}
|
||||||
|
|
||||||
|
// Count of queued names for current level and the next.
|
||||||
|
count := map[reflect.Type]int{}
|
||||||
|
nextCount := map[reflect.Type]int{}
|
||||||
|
|
||||||
|
// Types already visited at an earlier level.
|
||||||
|
visited := map[reflect.Type]bool{}
|
||||||
|
|
||||||
|
// Fields found.
|
||||||
|
var fields []field
|
||||||
|
|
||||||
|
for len(next) > 0 {
|
||||||
|
current, next = next, current[:0]
|
||||||
|
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||||
|
|
||||||
|
for _, f := range current {
|
||||||
|
if visited[f.typ] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
visited[f.typ] = true
|
||||||
|
|
||||||
|
// Scan f.typ for fields to include.
|
||||||
|
for i := 0; i < f.typ.NumField(); i++ {
|
||||||
|
sf := f.typ.Field(i)
|
||||||
|
if sf.PkgPath != "" { // unexported
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tag := sf.Tag.Get("json")
|
||||||
|
if tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, opts := parseTag(tag)
|
||||||
|
if !isValidTag(name) {
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
index := make([]int, len(f.index)+1)
|
||||||
|
copy(index, f.index)
|
||||||
|
index[len(f.index)] = i
|
||||||
|
|
||||||
|
ft := sf.Type
|
||||||
|
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||||
|
// Follow pointer.
|
||||||
|
ft = ft.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record found field and index sequence.
|
||||||
|
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||||
|
tagged := name != ""
|
||||||
|
if name == "" {
|
||||||
|
name = sf.Name
|
||||||
|
}
|
||||||
|
fields = append(fields, fillField(field{
|
||||||
|
name: name,
|
||||||
|
tag: tagged,
|
||||||
|
index: index,
|
||||||
|
typ: ft,
|
||||||
|
omitEmpty: opts.Contains("omitempty"),
|
||||||
|
quoted: opts.Contains("string"),
|
||||||
|
}))
|
||||||
|
if count[f.typ] > 1 {
|
||||||
|
// If there were multiple instances, add a second,
|
||||||
|
// so that the annihilation code will see a duplicate.
|
||||||
|
// It only cares about the distinction between 1 or 2,
|
||||||
|
// so don't bother generating any more copies.
|
||||||
|
fields = append(fields, fields[len(fields)-1])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record new anonymous struct to explore in next round.
|
||||||
|
nextCount[ft]++
|
||||||
|
if nextCount[ft] == 1 {
|
||||||
|
next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byName(fields))
|
||||||
|
|
||||||
|
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||||
|
// except that fields with JSON tags are promoted.
|
||||||
|
|
||||||
|
// The fields are sorted in primary order of name, secondary order
|
||||||
|
// of field index length. Loop over names; for each name, delete
|
||||||
|
// hidden fields by choosing the one dominant field that survives.
|
||||||
|
out := fields[:0]
|
||||||
|
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||||
|
// One iteration per name.
|
||||||
|
// Find the sequence of fields with the name of this first field.
|
||||||
|
fi := fields[i]
|
||||||
|
name := fi.name
|
||||||
|
for advance = 1; i+advance < len(fields); advance++ {
|
||||||
|
fj := fields[i+advance]
|
||||||
|
if fj.name != name {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if advance == 1 { // Only one field with this name
|
||||||
|
out = append(out, fi)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dominant, ok := dominantField(fields[i : i+advance])
|
||||||
|
if ok {
|
||||||
|
out = append(out, dominant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = out
|
||||||
|
sort.Sort(byIndex(fields))
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// dominantField looks through the fields, all of which are known to
|
||||||
|
// have the same name, to find the single field that dominates the
|
||||||
|
// others using Go's embedding rules, modified by the presence of
|
||||||
|
// JSON tags. If there are multiple top-level fields, the boolean
|
||||||
|
// will be false: This condition is an error in Go and we skip all
|
||||||
|
// the fields.
|
||||||
|
func dominantField(fields []field) (field, bool) {
|
||||||
|
// The fields are sorted in increasing index-length order. The winner
|
||||||
|
// must therefore be one with the shortest index length. Drop all
|
||||||
|
// longer entries, which is easy: just truncate the slice.
|
||||||
|
length := len(fields[0].index)
|
||||||
|
tagged := -1 // Index of first tagged field.
|
||||||
|
for i, f := range fields {
|
||||||
|
if len(f.index) > length {
|
||||||
|
fields = fields[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if f.tag {
|
||||||
|
if tagged >= 0 {
|
||||||
|
// Multiple tagged fields at the same level: conflict.
|
||||||
|
// Return no field.
|
||||||
|
return field{}, false
|
||||||
|
}
|
||||||
|
tagged = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tagged >= 0 {
|
||||||
|
return fields[tagged], true
|
||||||
|
}
|
||||||
|
// All remaining fields have the same length. If there's more than one,
|
||||||
|
// we have a conflict (two fields named "X" at the same level) and we
|
||||||
|
// return no field.
|
||||||
|
if len(fields) > 1 {
|
||||||
|
return field{}, false
|
||||||
|
}
|
||||||
|
return fields[0], true
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
m map[reflect.Type][]field
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||||
|
func cachedTypeFields(t reflect.Type) []field {
|
||||||
|
fieldCache.RLock()
|
||||||
|
f := fieldCache.m[t]
|
||||||
|
fieldCache.RUnlock()
|
||||||
|
if f != nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fields without lock.
|
||||||
|
// Might duplicate effort but won't hold other computations back.
|
||||||
|
f = typeFields(t)
|
||||||
|
if f == nil {
|
||||||
|
f = []field{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldCache.Lock()
|
||||||
|
if fieldCache.m == nil {
|
||||||
|
fieldCache.m = map[reflect.Type][]field{}
|
||||||
|
}
|
||||||
|
fieldCache.m[t] = f
|
||||||
|
fieldCache.Unlock()
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidTag(s string) bool {
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range s {
|
||||||
|
switch {
|
||||||
|
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
|
||||||
|
// Backslash and quote chars are reserved, but
|
||||||
|
// otherwise any punctuation chars are allowed
|
||||||
|
// in a tag name.
|
||||||
|
default:
|
||||||
|
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
||||||
|
kelvin = '\u212a'
|
||||||
|
smallLongEss = '\u017f'
|
||||||
|
)
|
||||||
|
|
||||||
|
// foldFunc returns one of four different case folding equivalence
|
||||||
|
// functions, from most general (and slow) to fastest:
|
||||||
|
//
|
||||||
|
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
|
||||||
|
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
|
||||||
|
// 3) asciiEqualFold, no special, but includes non-letters (including _)
|
||||||
|
// 4) simpleLetterEqualFold, no specials, no non-letters.
|
||||||
|
//
|
||||||
|
// The letters S and K are special because they map to 3 runes, not just 2:
|
||||||
|
// * S maps to s and to U+017F 'ſ' Latin small letter long s
|
||||||
|
// * k maps to K and to U+212A 'K' Kelvin sign
|
||||||
|
// See http://play.golang.org/p/tTxjOc0OGo
|
||||||
|
//
|
||||||
|
// The returned function is specialized for matching against s and
|
||||||
|
// should only be given s. It's not curried for performance reasons.
|
||||||
|
func foldFunc(s []byte) func(s, t []byte) bool {
|
||||||
|
nonLetter := false
|
||||||
|
special := false // special letter
|
||||||
|
for _, b := range s {
|
||||||
|
if b >= utf8.RuneSelf {
|
||||||
|
return bytes.EqualFold
|
||||||
|
}
|
||||||
|
upper := b & caseMask
|
||||||
|
if upper < 'A' || upper > 'Z' {
|
||||||
|
nonLetter = true
|
||||||
|
} else if upper == 'K' || upper == 'S' {
|
||||||
|
// See above for why these letters are special.
|
||||||
|
special = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if special {
|
||||||
|
return equalFoldRight
|
||||||
|
}
|
||||||
|
if nonLetter {
|
||||||
|
return asciiEqualFold
|
||||||
|
}
|
||||||
|
return simpleLetterEqualFold
|
||||||
|
}
|
||||||
|
|
||||||
|
// equalFoldRight is a specialization of bytes.EqualFold when s is
|
||||||
|
// known to be all ASCII (including punctuation), but contains an 's',
|
||||||
|
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func equalFoldRight(s, t []byte) bool {
|
||||||
|
for _, sb := range s {
|
||||||
|
if len(t) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tb := t[0]
|
||||||
|
if tb < utf8.RuneSelf {
|
||||||
|
if sb != tb {
|
||||||
|
sbUpper := sb & caseMask
|
||||||
|
if 'A' <= sbUpper && sbUpper <= 'Z' {
|
||||||
|
if sbUpper != tb&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = t[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// sb is ASCII and t is not. t must be either kelvin
|
||||||
|
// sign or long s; sb must be s, S, k, or K.
|
||||||
|
tr, size := utf8.DecodeRune(t)
|
||||||
|
switch sb {
|
||||||
|
case 's', 'S':
|
||||||
|
if tr != smallLongEss {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case 'k', 'K':
|
||||||
|
if tr != kelvin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t = t[size:]
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(t) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
||||||
|
// s is all ASCII (but may contain non-letters) and contains no
|
||||||
|
// special-folding letters.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func asciiEqualFold(s, t []byte) bool {
|
||||||
|
if len(s) != len(t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, sb := range s {
|
||||||
|
tb := t[i]
|
||||||
|
if sb == tb {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
|
||||||
|
if sb&caseMask != tb&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
|
||||||
|
// use when s is all ASCII letters (no underscores, etc) and also
|
||||||
|
// doesn't contain 'k', 'K', 's', or 'S'.
|
||||||
|
// See comments on foldFunc.
|
||||||
|
func simpleLetterEqualFold(s, t []byte) bool {
|
||||||
|
if len(s) != len(t) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, b := range s {
|
||||||
|
if b&caseMask != t[i]&caseMask {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// tagOptions is the string following a comma in a struct field's "json"
|
||||||
|
// tag, or the empty string. It does not include the leading comma.
|
||||||
|
type tagOptions string
|
||||||
|
|
||||||
|
// parseTag splits a struct field's json tag into its name and
|
||||||
|
// comma-separated options.
|
||||||
|
func parseTag(tag string) (string, tagOptions) {
|
||||||
|
if idx := strings.Index(tag, ","); idx != -1 {
|
||||||
|
return tag[:idx], tagOptions(tag[idx+1:])
|
||||||
|
}
|
||||||
|
return tag, tagOptions("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether a comma-separated list of options
|
||||||
|
// contains a particular substr flag. substr must be surrounded by a
|
||||||
|
// string boundary or commas.
|
||||||
|
func (o tagOptions) Contains(optionName string) bool {
|
||||||
|
if len(o) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s := string(o)
|
||||||
|
for s != "" {
|
||||||
|
var next string
|
||||||
|
i := strings.Index(s, ",")
|
||||||
|
if i >= 0 {
|
||||||
|
s, next = s[:i], s[i+1:]
|
||||||
|
}
|
||||||
|
if s == optionName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s = next
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,380 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Marshal marshals the object into JSON then converts JSON to YAML and returns the
|
||||||
|
// YAML.
|
||||||
|
func Marshal(o interface{}) ([]byte, error) {
|
||||||
|
j, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshaling into JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
y, err := JSONToYAML(j)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error converting JSON to YAML: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return y, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONOpt is a decoding option for decoding from JSON format.
|
||||||
|
type JSONOpt func(*json.Decoder) *json.Decoder
|
||||||
|
|
||||||
|
// Unmarshal converts YAML to JSON then uses JSON to unmarshal into an object,
|
||||||
|
// optionally configuring the behavior of the JSON unmarshal.
|
||||||
|
func Unmarshal(y []byte, o interface{}, opts ...JSONOpt) error {
|
||||||
|
return yamlUnmarshal(y, o, false, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalStrict strictly converts YAML to JSON then uses JSON to unmarshal
|
||||||
|
// into an object, optionally configuring the behavior of the JSON unmarshal.
|
||||||
|
func UnmarshalStrict(y []byte, o interface{}, opts ...JSONOpt) error {
|
||||||
|
return yamlUnmarshal(y, o, true, append(opts, DisallowUnknownFields)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// yamlUnmarshal unmarshals the given YAML byte stream into the given interface,
|
||||||
|
// optionally performing the unmarshalling strictly
|
||||||
|
func yamlUnmarshal(y []byte, o interface{}, strict bool, opts ...JSONOpt) error {
|
||||||
|
vo := reflect.ValueOf(o)
|
||||||
|
unmarshalFn := yaml.Unmarshal
|
||||||
|
if strict {
|
||||||
|
unmarshalFn = yaml.UnmarshalStrict
|
||||||
|
}
|
||||||
|
j, err := yamlToJSON(y, &vo, unmarshalFn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting YAML to JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jsonUnmarshal(bytes.NewReader(j), o, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error unmarshaling JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonUnmarshal unmarshals the JSON byte stream from the given reader into the
|
||||||
|
// object, optionally applying decoder options prior to decoding. We are not
|
||||||
|
// using json.Unmarshal directly as we want the chance to pass in non-default
|
||||||
|
// options.
|
||||||
|
func jsonUnmarshal(r io.Reader, o interface{}, opts ...JSONOpt) error {
|
||||||
|
d := json.NewDecoder(r)
|
||||||
|
for _, opt := range opts {
|
||||||
|
d = opt(d)
|
||||||
|
}
|
||||||
|
if err := d.Decode(&o); err != nil {
|
||||||
|
return fmt.Errorf("while decoding JSON: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONToYAML Converts JSON to YAML.
|
||||||
|
func JSONToYAML(j []byte) ([]byte, error) {
|
||||||
|
// Convert the JSON to an object.
|
||||||
|
var jsonObj interface{}
|
||||||
|
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
|
||||||
|
// Go JSON library doesn't try to pick the right number type (int, float,
|
||||||
|
// etc.) when unmarshalling to interface{}, it just picks float64
|
||||||
|
// universally. go-yaml does go through the effort of picking the right
|
||||||
|
// number type, so we can preserve number type throughout this process.
|
||||||
|
err := yaml.Unmarshal(j, &jsonObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal this object into YAML.
|
||||||
|
return yaml.Marshal(jsonObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLToJSON converts YAML to JSON. Since JSON is a subset of YAML,
|
||||||
|
// passing JSON through this method should be a no-op.
|
||||||
|
//
|
||||||
|
// Things YAML can do that are not supported by JSON:
|
||||||
|
// * In YAML you can have binary and null keys in your maps. These are invalid
|
||||||
|
// in JSON. (int and float keys are converted to strings.)
|
||||||
|
// * Binary data in YAML with the !!binary tag is not supported. If you want to
|
||||||
|
// use binary data with this library, encode the data as base64 as usual but do
|
||||||
|
// not use the !!binary tag in your YAML. This will ensure the original base64
|
||||||
|
// encoded data makes it all the way through to the JSON.
|
||||||
|
//
|
||||||
|
// For strict decoding of YAML, use YAMLToJSONStrict.
|
||||||
|
func YAMLToJSON(y []byte) ([]byte, error) {
|
||||||
|
return yamlToJSON(y, nil, yaml.Unmarshal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLToJSONStrict is like YAMLToJSON but enables strict YAML decoding,
|
||||||
|
// returning an error on any duplicate field names.
|
||||||
|
func YAMLToJSONStrict(y []byte) ([]byte, error) {
|
||||||
|
return yamlToJSON(y, nil, yaml.UnmarshalStrict)
|
||||||
|
}
|
||||||
|
|
||||||
|
func yamlToJSON(y []byte, jsonTarget *reflect.Value, yamlUnmarshal func([]byte, interface{}) error) ([]byte, error) {
|
||||||
|
// Convert the YAML to an object.
|
||||||
|
var yamlObj interface{}
|
||||||
|
err := yamlUnmarshal(y, &yamlObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAML objects are not completely compatible with JSON objects (e.g. you
|
||||||
|
// can have non-string keys in YAML). So, convert the YAML-compatible object
|
||||||
|
// to a JSON-compatible object, failing with an error if irrecoverable
|
||||||
|
// incompatibilties happen along the way.
|
||||||
|
jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert this object to JSON and return the data.
|
||||||
|
return json.Marshal(jsonObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Resolve jsonTarget to a concrete value (i.e. not a pointer or an
|
||||||
|
// interface). We pass decodingNull as false because we're not actually
|
||||||
|
// decoding into the value, we're just checking if the ultimate target is a
|
||||||
|
// string.
|
||||||
|
if jsonTarget != nil {
|
||||||
|
ju, tu, pv := indirect(*jsonTarget, false)
|
||||||
|
// We have a JSON or Text Umarshaler at this level, so we can't be trying
|
||||||
|
// to decode into a string.
|
||||||
|
if ju != nil || tu != nil {
|
||||||
|
jsonTarget = nil
|
||||||
|
} else {
|
||||||
|
jsonTarget = &pv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If yamlObj is a number or a boolean, check if jsonTarget is a string -
|
||||||
|
// if so, coerce. Else return normal.
|
||||||
|
// If yamlObj is a map or array, find the field that each key is
|
||||||
|
// unmarshaling to, and when you recurse pass the reflect.Value for that
|
||||||
|
// field back into this function.
|
||||||
|
switch typedYAMLObj := yamlObj.(type) {
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
// JSON does not support arbitrary keys in a map, so we must convert
|
||||||
|
// these keys to strings.
|
||||||
|
//
|
||||||
|
// From my reading of go-yaml v2 (specifically the resolve function),
|
||||||
|
// keys can only have the types string, int, int64, float64, binary
|
||||||
|
// (unsupported), or null (unsupported).
|
||||||
|
strMap := make(map[string]interface{})
|
||||||
|
for k, v := range typedYAMLObj {
|
||||||
|
// Resolve the key to a string first.
|
||||||
|
var keyString string
|
||||||
|
switch typedKey := k.(type) {
|
||||||
|
case string:
|
||||||
|
keyString = typedKey
|
||||||
|
case int:
|
||||||
|
keyString = strconv.Itoa(typedKey)
|
||||||
|
case int64:
|
||||||
|
// go-yaml will only return an int64 as a key if the system
|
||||||
|
// architecture is 32-bit and the key's value is between 32-bit
|
||||||
|
// and 64-bit. Otherwise the key type will simply be int.
|
||||||
|
keyString = strconv.FormatInt(typedKey, 10)
|
||||||
|
case float64:
|
||||||
|
// Stolen from go-yaml to use the same conversion to string as
|
||||||
|
// the go-yaml library uses to convert float to string when
|
||||||
|
// Marshaling.
|
||||||
|
s := strconv.FormatFloat(typedKey, 'g', -1, 32)
|
||||||
|
switch s {
|
||||||
|
case "+Inf":
|
||||||
|
s = ".inf"
|
||||||
|
case "-Inf":
|
||||||
|
s = "-.inf"
|
||||||
|
case "NaN":
|
||||||
|
s = ".nan"
|
||||||
|
}
|
||||||
|
keyString = s
|
||||||
|
case bool:
|
||||||
|
if typedKey {
|
||||||
|
keyString = "true"
|
||||||
|
} else {
|
||||||
|
keyString = "false"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v",
|
||||||
|
reflect.TypeOf(k), k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonTarget should be a struct or a map. If it's a struct, find
|
||||||
|
// the field it's going to map to and pass its reflect.Value. If
|
||||||
|
// it's a map, find the element type of the map and pass the
|
||||||
|
// reflect.Value created from that type. If it's neither, just pass
|
||||||
|
// nil - JSON conversion will error for us if it's a real issue.
|
||||||
|
if jsonTarget != nil {
|
||||||
|
t := *jsonTarget
|
||||||
|
if t.Kind() == reflect.Struct {
|
||||||
|
keyBytes := []byte(keyString)
|
||||||
|
// Find the field that the JSON library would use.
|
||||||
|
var f *field
|
||||||
|
fields := cachedTypeFields(t.Type())
|
||||||
|
for i := range fields {
|
||||||
|
ff := &fields[i]
|
||||||
|
if bytes.Equal(ff.nameBytes, keyBytes) {
|
||||||
|
f = ff
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Do case-insensitive comparison.
|
||||||
|
if f == nil && ff.equalFold(ff.nameBytes, keyBytes) {
|
||||||
|
f = ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f != nil {
|
||||||
|
// Find the reflect.Value of the most preferential
|
||||||
|
// struct field.
|
||||||
|
jtf := t.Field(f.index[0])
|
||||||
|
strMap[keyString], err = convertToJSONableObject(v, &jtf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if t.Kind() == reflect.Map {
|
||||||
|
// Create a zero value of the map's element type to use as
|
||||||
|
// the JSON target.
|
||||||
|
jtv := reflect.Zero(t.Type().Elem())
|
||||||
|
strMap[keyString], err = convertToJSONableObject(v, &jtv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strMap[keyString], err = convertToJSONableObject(v, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strMap, nil
|
||||||
|
case []interface{}:
|
||||||
|
// We need to recurse into arrays in case there are any
|
||||||
|
// map[interface{}]interface{}'s inside and to convert any
|
||||||
|
// numbers to strings.
|
||||||
|
|
||||||
|
// If jsonTarget is a slice (which it really should be), find the
|
||||||
|
// thing it's going to map to. If it's not a slice, just pass nil
|
||||||
|
// - JSON conversion will error for us if it's a real issue.
|
||||||
|
var jsonSliceElemValue *reflect.Value
|
||||||
|
if jsonTarget != nil {
|
||||||
|
t := *jsonTarget
|
||||||
|
if t.Kind() == reflect.Slice {
|
||||||
|
// By default slices point to nil, but we need a reflect.Value
|
||||||
|
// pointing to a value of the slice type, so we create one here.
|
||||||
|
ev := reflect.Indirect(reflect.New(t.Type().Elem()))
|
||||||
|
jsonSliceElemValue = &ev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make and use a new array.
|
||||||
|
arr := make([]interface{}, len(typedYAMLObj))
|
||||||
|
for i, v := range typedYAMLObj {
|
||||||
|
arr[i], err = convertToJSONableObject(v, jsonSliceElemValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr, nil
|
||||||
|
default:
|
||||||
|
// If the target type is a string and the YAML type is a number,
|
||||||
|
// convert the YAML type to a string.
|
||||||
|
if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String {
|
||||||
|
// Based on my reading of go-yaml, it may return int, int64,
|
||||||
|
// float64, or uint64.
|
||||||
|
var s string
|
||||||
|
switch typedVal := typedYAMLObj.(type) {
|
||||||
|
case int:
|
||||||
|
s = strconv.FormatInt(int64(typedVal), 10)
|
||||||
|
case int64:
|
||||||
|
s = strconv.FormatInt(typedVal, 10)
|
||||||
|
case float64:
|
||||||
|
s = strconv.FormatFloat(typedVal, 'g', -1, 32)
|
||||||
|
case uint64:
|
||||||
|
s = strconv.FormatUint(typedVal, 10)
|
||||||
|
case bool:
|
||||||
|
if typedVal {
|
||||||
|
s = "true"
|
||||||
|
} else {
|
||||||
|
s = "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s) > 0 {
|
||||||
|
yamlObj = interface{}(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return yamlObj, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONObjectToYAMLObject converts an in-memory JSON object into a YAML in-memory MapSlice,
|
||||||
|
// without going through a byte representation. A nil or empty map[string]interface{} input is
|
||||||
|
// converted to an empty map, i.e. yaml.MapSlice(nil).
|
||||||
|
//
|
||||||
|
// interface{} slices stay interface{} slices. map[string]interface{} becomes yaml.MapSlice.
|
||||||
|
//
|
||||||
|
// int64 and float64 are down casted following the logic of github.com/go-yaml/yaml:
|
||||||
|
// - float64s are down-casted as far as possible without data-loss to int, int64, uint64.
|
||||||
|
// - int64s are down-casted to int if possible without data-loss.
|
||||||
|
//
|
||||||
|
// Big int/int64/uint64 do not lose precision as in the json-yaml roundtripping case.
|
||||||
|
//
|
||||||
|
// string, bool and any other types are unchanged.
|
||||||
|
func JSONObjectToYAMLObject(j map[string]interface{}) yaml.MapSlice {
|
||||||
|
if len(j) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ret := make(yaml.MapSlice, 0, len(j))
|
||||||
|
for k, v := range j {
|
||||||
|
ret = append(ret, yaml.MapItem{Key: k, Value: jsonToYAMLValue(v)})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonToYAMLValue(j interface{}) interface{} {
|
||||||
|
switch j := j.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
if j == nil {
|
||||||
|
return interface{}(nil)
|
||||||
|
}
|
||||||
|
return JSONObjectToYAMLObject(j)
|
||||||
|
case []interface{}:
|
||||||
|
if j == nil {
|
||||||
|
return interface{}(nil)
|
||||||
|
}
|
||||||
|
ret := make([]interface{}, len(j))
|
||||||
|
for i := range j {
|
||||||
|
ret[i] = jsonToYAMLValue(j[i])
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
case float64:
|
||||||
|
// replicate the logic in https://github.com/go-yaml/yaml/blob/51d6538a90f86fe93ac480b35f37b2be17fef232/resolve.go#L151
|
||||||
|
if i64 := int64(j); j == float64(i64) {
|
||||||
|
if i := int(i64); i64 == int64(i) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return i64
|
||||||
|
}
|
||||||
|
if ui64 := uint64(j); j == float64(ui64) {
|
||||||
|
return ui64
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
case int64:
|
||||||
|
if i := int(j); j == int64(i) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// This file contains changes that are only compatible with go 1.10 and onwards.
|
||||||
|
|
||||||
|
// +build go1.10
|
||||||
|
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// DisallowUnknownFields configures the JSON decoder to error out if unknown
|
||||||
|
// fields come along, instead of dropping them by default.
|
||||||
|
func DisallowUnknownFields(d *json.Decoder) *json.Decoder {
|
||||||
|
d.DisallowUnknownFields()
|
||||||
|
return d
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue