From da449e38ddc0f2f60247684b390fa59b65d356df Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 24 Nov 2021 14:34:10 +0200 Subject: [PATCH 01/11] specs-go: export OCI type conversion functions. Signed-off-by: Krisztian Litkey --- specs-go/oci.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/specs-go/oci.go b/specs-go/oci.go index 87c2148..10bc9fa 100644 --- a/specs-go/oci.go +++ b/specs-go/oci.go @@ -47,11 +47,11 @@ func ApplyEditsToOCISpec(config *spec.Spec, edits *ContainerEdits) error { if config.Linux == nil { config.Linux = &spec.Linux{} } - config.Linux.Devices = append(config.Linux.Devices, toOCILinuxDevice(d)) + config.Linux.Devices = append(config.Linux.Devices, d.ToOCI()) } for _, m := range edits.Mounts { - config.Mounts = append(config.Mounts, toOCIMount(m)) + config.Mounts = append(config.Mounts, m.ToOCI()) } for _, h := range edits.Hooks { @@ -60,17 +60,17 @@ func ApplyEditsToOCISpec(config *spec.Spec, edits *ContainerEdits) error { } switch h.HookName { case "prestart": - config.Hooks.Prestart = append(config.Hooks.Prestart, toOCIHook(h)) + config.Hooks.Prestart = append(config.Hooks.Prestart, h.ToOCI()) case "createRuntime": - config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, toOCIHook(h)) + config.Hooks.CreateRuntime = append(config.Hooks.CreateRuntime, h.ToOCI()) case "createContainer": - config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, toOCIHook(h)) + config.Hooks.CreateContainer = append(config.Hooks.CreateContainer, h.ToOCI()) case "startContainer": - config.Hooks.StartContainer = append(config.Hooks.StartContainer, toOCIHook(h)) + config.Hooks.StartContainer = append(config.Hooks.StartContainer, h.ToOCI()) case "poststart": - config.Hooks.Poststart = append(config.Hooks.Poststart, toOCIHook(h)) + config.Hooks.Poststart = append(config.Hooks.Poststart, h.ToOCI()) case "poststop": - config.Hooks.Poststop = append(config.Hooks.Poststop, toOCIHook(h)) + config.Hooks.Poststop = append(config.Hooks.Poststop, h.ToOCI()) default: fmt.Printf("CDI: Unknown hook %q\n", h.HookName) } @@ -79,7 +79,8 @@ func ApplyEditsToOCISpec(config *spec.Spec, edits *ContainerEdits) error { return nil } -func toOCIHook(h *Hook) spec.Hook { +// 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, @@ -88,7 +89,8 @@ func toOCIHook(h *Hook) spec.Hook { } } -func toOCIMount(m *Mount) spec.Mount { +// 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, @@ -96,7 +98,8 @@ func toOCIMount(m *Mount) spec.Mount { } } -func toOCILinuxDevice(d *DeviceNode) spec.LinuxDevice { +// 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, From 57b060e234c3b3018b3471a7b1bfb2b7d1d46587 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Fri, 3 Dec 2021 22:44:55 +0200 Subject: [PATCH 02/11] pkg/cdi: implement qualified device names. Implement composing, parsing and validating qualified device names and their vendor, class and device components. Signed-off-by: Krisztian Litkey --- pkg/cdi/qualified-device.go | 203 +++++++++++++++++++++++++++++++ pkg/cdi/qualified-device_test.go | 139 +++++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 pkg/cdi/qualified-device.go create mode 100644 pkg/cdi/qualified-device_test.go diff --git a/pkg/cdi/qualified-device.go b/pkg/cdi/qualified-device.go new file mode 100644 index 0000000..54f1914 --- /dev/null +++ b/pkg/cdi/qualified-device.go @@ -0,0 +1,203 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "strings" + + "github.com/pkg/errors" +) + +// QualifiedName returns the qualified name for a device. +// The syntax for a qualified device names is +// "/=". +// 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 +// "/" +// If parsing fails, an empty vendor and the class set to the +// verbatim input is returned. +func ParseQualifier(kind string) (string, string) { + parts := strings.SplitN(kind, "/", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", kind + } + return parts[0], parts[1] +} + +// ValidateVendorName checks the validity of a vendor name. +// A vendor name may contain the following ASCII characters: +// - upper- and lowercase letters ('A'-'Z', 'a'-'z') +// - digits ('0'-'9') +// - underscore, dash, and dot ('_', '-', and '.') +func ValidateVendorName(vendor string) error { + if vendor == "" { + return errors.Errorf("invalid (empty) vendor name") + } + if !isLetter(rune(vendor[0])) { + return errors.Errorf("invalid vendor %q, should start with letter", vendor) + } + for _, c := range string(vendor[1 : len(vendor)-1]) { + switch { + case isAlphaNumeric(c): + case c == '_' || c == '-' || c == '.': + default: + return errors.Errorf("invalid character '%c' in vendor name %q", + c, vendor) + } + } + if !isAlphaNumeric(rune(vendor[len(vendor)-1])) { + return errors.Errorf("invalid vendor %q, should end with letter", vendor) + } + + return nil +} + +// ValidateClassName checks the validity of class name. +// A class name may contain the following ASCII characters: +// - upper- and lowercase letters ('A'-'Z', 'a'-'z') +// - digits ('0'-'9') +// - underscore and dash ('_', '-') +func ValidateClassName(class string) error { + if class == "" { + return errors.Errorf("invalid (empty) device class") + } + if !isLetter(rune(class[0])) { + return errors.Errorf("invalid class %q, should start with letter", class) + } + for _, c := range string(class[1 : len(class)-1]) { + switch { + case isAlphaNumeric(c): + case c == '_' || c == '-': + default: + return errors.Errorf("invalid character '%c' in device class %q", + c, class) + } + } + if !isAlphaNumeric(rune(class[len(class)-1])) { + return errors.Errorf("invalid class %q, should end with letter", class) + } + return nil +} + +// ValidateDeviceName checks the validity of a device name. +// A device name may contain the following ASCII characters: +// - upper- and lowercase letters ('A'-'Z', 'a'-'z') +// - digits ('0'-'9') +// - underscore, dash, dot, colon ('_', '-', '.', ':') +func ValidateDeviceName(name string) error { + if name == "" { + return errors.Errorf("invalid (empty) device name") + } + if !isLetter(rune(name[0])) { + return errors.Errorf("invalid name %q, should start with letter", name) + } + for _, c := range string(name[1 : len(name)-1]) { + switch { + case isAlphaNumeric(c): + case c == '_' || c == '-' || c == '.' || c == ':': + default: + return errors.Errorf("invalid character '%c' in device name %q", + c, name) + } + } + if !isAlphaNumeric(rune(name[len(name)-1])) { + return errors.Errorf("invalid name %q, should start with letter", name) + } + return nil +} + +func isLetter(c rune) bool { + return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') +} + +func isDigit(c rune) bool { + return '0' <= c && c <= '9' +} + +func isAlphaNumeric(c rune) bool { + return isLetter(c) || isDigit(c) +} diff --git a/pkg/cdi/qualified-device_test.go b/pkg/cdi/qualified-device_test.go new file mode 100644 index 0000000..6f4d8b4 --- /dev/null +++ b/pkg/cdi/qualified-device_test.go @@ -0,0 +1,139 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestQualifiedName(t *testing.T) { + type testCase = struct { + device string + vendor string + class string + name string + isQualified bool + isParsable bool + } + + for _, tc := range []*testCase{ + { + device: "vendor.com/class=dev", + vendor: "vendor.com", + class: "class", + name: "dev", + isQualified: true, + }, + { + device: "vendor1.com/class1=dev1", + vendor: "vendor1.com", + class: "class1", + name: "dev1", + isQualified: true, + }, + { + device: "other-vendor1.com/class_1=dev_1", + vendor: "other-vendor1.com", + class: "class_1", + name: "dev_1", + isQualified: true, + }, + { + device: "yet_another-vendor2.com/c-lass_2=dev_1:2.3", + vendor: "yet_another-vendor2.com", + class: "c-lass_2", + name: "dev_1:2.3", + isQualified: true, + }, + { + device: "_invalid.com/class=dev", + vendor: "_invalid.com", + class: "class", + name: "dev", + isParsable: true, + }, + { + device: "invalid2.com-/class=dev", + vendor: "invalid2.com-", + class: "class", + name: "dev", + isParsable: true, + }, + { + device: "invalid3.com/_class=dev", + vendor: "invalid3.com", + class: "_class", + name: "dev", + isParsable: true, + }, + { + device: "invalid4.com/class_=dev", + vendor: "invalid4.com", + class: "class_", + name: "dev", + isParsable: true, + }, + { + device: "invalid5.com/class=-dev", + vendor: "invalid5.com", + class: "class", + name: "-dev", + isParsable: true, + }, + { + device: "invalid6.com/class=dev:", + vendor: "invalid6.com", + class: "class", + name: "dev:", + isParsable: true, + }, + { + device: "*.com/*dev=*gpu*", + vendor: "*.com", + class: "*dev", + name: "*gpu*", + isParsable: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + vendor, class, name, err := ParseQualifiedName(tc.device) + if tc.isQualified { + require.True(t, IsQualifiedName(tc.device), "qualified name %q", tc.device) + require.NoError(t, err) + require.Equal(t, tc.vendor, vendor, "qualified name %q", tc.device) + require.Equal(t, tc.class, class, "qualified name %q", tc.device) + require.Equal(t, tc.name, name, "qualified name %q", tc.device) + + vendor, class, name = ParseDevice(tc.device) + require.Equal(t, tc.vendor, vendor, "parsed name %q", tc.device) + require.Equal(t, tc.class, class, "parse name %q", tc.device) + require.Equal(t, tc.name, name, "parsed name %q", tc.device) + + device := QualifiedName(vendor, class, name) + require.Equal(t, tc.device, device, "constructed device %q", tc.device) + } else if tc.isParsable { + require.False(t, IsQualifiedName(tc.device), "parsed name %q", tc.device) + vendor, class, name = ParseDevice(tc.device) + require.Equal(t, tc.vendor, vendor, "parsed name %q", tc.device) + require.Equal(t, tc.class, class, "parse name %q", tc.device) + require.Equal(t, tc.name, name, "parsed name %q", tc.device) + } + }) + } +} From 03e5cc4707ee6934c2d4ba9941991e5ac6821644 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Sat, 4 Dec 2021 01:38:27 +0200 Subject: [PATCH 03/11] pkg/cdi: add validation and application of edits. Implement container edit validation, applying container edits to OCI Specs. Signed-off-by: Krisztian Litkey --- pkg/cdi/container-edits.go | 222 +++++++++++++++ pkg/cdi/container-edits_test.go | 460 ++++++++++++++++++++++++++++++++ 2 files changed, 682 insertions(+) create mode 100644 pkg/cdi/container-edits.go create mode 100644 pkg/cdi/container-edits_test.go diff --git a/pkg/cdi/container-edits.go b/pkg/cdi/container-edits.go new file mode 100644 index 0000000..767cba5 --- /dev/null +++ b/pkg/cdi/container-edits.go @@ -0,0 +1,222 @@ +/* + 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" + + "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 { + specgen.AddDevice(d.ToOCI()) + } + for _, m := range e.Mounts { + specgen.AddMount(m.ToOCI()) + } + 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 +} + +// 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 { + if d.Path == "" { + return errors.New("invalid (empty) device path") + } + if d.Type != "" && d.Type != "b" && d.Type != "c" { + 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{} + } +} diff --git a/pkg/cdi/container-edits_test.go b/pkg/cdi/container-edits_test.go new file mode 100644 index 0000000..04e9add --- /dev/null +++ b/pkg/cdi/container-edits_test.go @@ -0,0 +1,460 @@ +/* + 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" + "testing" + + cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" + oci "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/require" +) + +func TestValidateContainerEdits(t *testing.T) { + type testCase struct { + name string + spec *oci.Spec + edits *cdi.ContainerEdits + invalid bool + } + for _, tc := range []*testCase{ + { + name: "valid, empty edits", + edits: nil, + }, + { + name: "valid, env var", + edits: &cdi.ContainerEdits{ + Env: []string{"BAR=BARVALUE1"}, + }, + }, + { + name: "invalid env, empty var", + edits: &cdi.ContainerEdits{ + Env: []string{""}, + }, + invalid: true, + }, + { + name: "invalid env, no var name", + edits: &cdi.ContainerEdits{ + Env: []string{"=foo"}, + }, + invalid: true, + }, + { + name: "invalid env, no assignment", + edits: &cdi.ContainerEdits{ + Env: []string{"FOOBAR"}, + }, + invalid: true, + }, + { + name: "valid device, path only", + edits: &cdi.ContainerEdits{ + DeviceNodes: []*cdi.DeviceNode{ + { + Path: "/dev/null", + }, + }, + }, + }, + { + name: "valid device, path+type", + edits: &cdi.ContainerEdits{ + DeviceNodes: []*cdi.DeviceNode{ + { + Path: "/dev/null", + Type: "b", + }, + }, + }, + }, + { + name: "valid device, path+type+permissions", + edits: &cdi.ContainerEdits{ + DeviceNodes: []*cdi.DeviceNode{ + { + Path: "/dev/null", + Type: "b", + Permissions: "rwm", + }, + }, + }, + }, + { + name: "invalid device, empty path", + edits: &cdi.ContainerEdits{ + DeviceNodes: []*cdi.DeviceNode{ + { + Path: "", + }, + }, + }, + invalid: true, + }, + { + name: "invalid device, wrong type", + edits: &cdi.ContainerEdits{ + DeviceNodes: []*cdi.DeviceNode{ + { + Path: "/dev/vendorctl", + Type: "f", + }, + }, + }, + invalid: true, + }, + { + name: "invalid device, wrong permissions", + edits: &cdi.ContainerEdits{ + DeviceNodes: []*cdi.DeviceNode{ + { + Path: "/dev/vendorctl", + Type: "b", + Permissions: "to land", + }, + }, + }, + invalid: true, + }, + { + name: "valid mount", + edits: &cdi.ContainerEdits{ + Mounts: []*cdi.Mount{ + { + HostPath: "/dev/vendorctl", + ContainerPath: "/dev/vendorctl", + }, + }, + }, + }, + { + name: "invalid mount, empty host path", + edits: &cdi.ContainerEdits{ + Mounts: []*cdi.Mount{ + { + HostPath: "", + ContainerPath: "/dev/vendorctl", + }, + }, + }, + invalid: true, + }, + { + name: "invalid mount, empty container path", + edits: &cdi.ContainerEdits{ + Mounts: []*cdi.Mount{ + { + HostPath: "/dev/vendorctl", + ContainerPath: "", + }, + }, + }, + invalid: true, + }, + { + name: "valid hooks", + edits: &cdi.ContainerEdits{ + Hooks: []*cdi.Hook{ + { + HookName: "prestart", + Path: "/usr/local/bin/prestart-vendor-hook", + Args: []string{"--verbose"}, + Env: []string{"VENDOR_ENV1=value1"}, + }, + { + HookName: "createRuntime", + Path: "/usr/local/bin/cr-vendor-hook", + Args: []string{"--debug"}, + Env: []string{"VENDOR_ENV2=value2"}, + }, + { + HookName: "createContainer", + Path: "/usr/local/bin/cc-vendor-hook", + Args: []string{"--create"}, + Env: []string{"VENDOR_ENV3=value3"}, + }, + { + HookName: "startContainer", + Path: "/usr/local/bin/sc-vendor-hook", + Args: []string{"--start"}, + Env: []string{"VENDOR_ENV4=value4"}, + }, + { + HookName: "poststart", + Path: "/usr/local/bin/poststart-vendor-hook", + Env: []string{"VENDOR_ENV5=value5"}, + }, + { + HookName: "poststop", + Path: "/usr/local/bin/poststop-vendor-hook", + }, + }, + }, + }, + { + name: "invalid hook, empty path", + edits: &cdi.ContainerEdits{ + Hooks: []*cdi.Hook{ + { + HookName: "prestart", + }, + }, + }, + invalid: true, + }, + { + name: "invalid hook, wrong hook name", + edits: &cdi.ContainerEdits{ + Hooks: []*cdi.Hook{ + { + HookName: "misCreateRuntime", + Path: "/usr/local/bin/cr-vendor-hook", + Args: []string{"--debug"}, + Env: []string{"VENDOR_ENV2=value2"}, + }, + }, + }, + invalid: true, + }, + { + name: "invalid hook, wrong env", + edits: &cdi.ContainerEdits{ + Hooks: []*cdi.Hook{ + { + HookName: "poststart", + Path: "/usr/local/bin/cr-vendor-hook", + Args: []string{"--debug"}, + Env: []string{"=value2"}, + }, + }, + }, + invalid: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + edits := ContainerEdits{tc.edits} + err := edits.Validate() + if tc.invalid { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestApplyContainerEdits(t *testing.T) { + type testCase struct { + name string + spec *oci.Spec + edits *cdi.ContainerEdits + result *oci.Spec + } + for _, tc := range []*testCase{ + { + name: "empty spec, empty edits", + spec: &oci.Spec{}, + edits: nil, + result: &oci.Spec{}, + }, + { + name: "empty spec, env var", + spec: &oci.Spec{}, + edits: &cdi.ContainerEdits{ + Env: []string{"BAR=BARVALUE1"}, + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "BAR=BARVALUE1", + }, + }, + }, + }, + { + name: "empty spec, device", + spec: &oci.Spec{}, + edits: &cdi.ContainerEdits{ + DeviceNodes: []*cdi.DeviceNode{ + { + Path: "/dev/null", + }, + }, + }, + result: &oci.Spec{ + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + }, + }, + }, + }, + { + name: "empty spec, device, env var", + spec: &oci.Spec{}, + edits: &cdi.ContainerEdits{ + Env: []string{ + "FOO=BAR", + }, + DeviceNodes: []*cdi.DeviceNode{ + { + Path: "/dev/null", + Type: "b", + }, + }, + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "FOO=BAR", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + Type: "b", + }, + }, + }, + }, + }, + { + name: "empty spec, mount", + spec: &oci.Spec{}, + edits: &cdi.ContainerEdits{ + Mounts: []*cdi.Mount{ + { + HostPath: "/dev/host-vendorctl", + ContainerPath: "/dev/cntr-vendorctl", + }, + }, + }, + result: &oci.Spec{ + Mounts: []oci.Mount{ + { + Source: "/dev/host-vendorctl", + Destination: "/dev/cntr-vendorctl", + }, + }, + }, + }, + { + name: "empty spec, hooks", + spec: &oci.Spec{}, + edits: &cdi.ContainerEdits{ + Hooks: []*cdi.Hook{ + { + HookName: "prestart", + Path: "/usr/local/bin/prestart-vendor-hook", + Args: []string{"--verbose"}, + Env: []string{"VENDOR_ENV1=value1"}, + }, + { + HookName: "createRuntime", + Path: "/usr/local/bin/cr-vendor-hook", + Args: []string{"--debug"}, + Env: []string{"VENDOR_ENV2=value2"}, + }, + { + HookName: "createContainer", + Path: "/usr/local/bin/cc-vendor-hook", + Args: []string{"--create"}, + Env: []string{"VENDOR_ENV3=value3"}, + }, + { + HookName: "startContainer", + Path: "/usr/local/bin/sc-vendor-hook", + Args: []string{"--start"}, + Env: []string{"VENDOR_ENV4=value4"}, + }, + { + HookName: "poststart", + Path: "/usr/local/bin/poststart-vendor-hook", + Env: []string{"VENDOR_ENV5=value5"}, + }, + { + HookName: "poststop", + Path: "/usr/local/bin/poststop-vendor-hook", + }, + }, + }, + result: &oci.Spec{ + Hooks: &oci.Hooks{ + Prestart: []oci.Hook{ + { + Path: "/usr/local/bin/prestart-vendor-hook", + Args: []string{"--verbose"}, + Env: []string{"VENDOR_ENV1=value1"}, + }, + }, + CreateRuntime: []oci.Hook{ + { + Path: "/usr/local/bin/cr-vendor-hook", + Args: []string{"--debug"}, + Env: []string{"VENDOR_ENV2=value2"}, + }, + }, + CreateContainer: []oci.Hook{ + { + Path: "/usr/local/bin/cc-vendor-hook", + Args: []string{"--create"}, + Env: []string{"VENDOR_ENV3=value3"}, + }, + }, + StartContainer: []oci.Hook{ + { + Path: "/usr/local/bin/sc-vendor-hook", + Args: []string{"--start"}, + Env: []string{"VENDOR_ENV4=value4"}, + }, + }, + Poststart: []oci.Hook{ + { + Path: "/usr/local/bin/poststart-vendor-hook", + Env: []string{"VENDOR_ENV5=value5"}, + }, + }, + Poststop: []oci.Hook{ + { + Path: "/usr/local/bin/poststop-vendor-hook", + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + edits := ContainerEdits{tc.edits} + err := edits.Validate() + require.NoError(t, err) + err = edits.Apply(tc.spec) + require.NoError(t, err) + require.Equal(t, tc.result, tc.spec) + }) + } +} + +func fileMode(mode int) *os.FileMode { + fm := os.FileMode(mode) + return &fm +} From 43fc5a0be56c8d526f0e248e7f0804144c26f17f Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Sat, 4 Dec 2021 02:13:33 +0200 Subject: [PATCH 04/11] pkg/cdi: add spec, device loading and validation. Implement loading and validation of specs and devices. Signed-off-by: Krisztian Litkey --- pkg/cdi/device.go | 67 ++++++++ pkg/cdi/device_test.go | 78 +++++++++ pkg/cdi/spec.go | 165 +++++++++++++++++++ pkg/cdi/spec_test.go | 348 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 658 insertions(+) create mode 100644 pkg/cdi/device.go create mode 100644 pkg/cdi/device_test.go create mode 100644 pkg/cdi/spec.go create mode 100644 pkg/cdi/spec_test.go diff --git a/pkg/cdi/device.go b/pkg/cdi/device.go new file mode 100644 index 0000000..d4c2491 --- /dev/null +++ b/pkg/cdi/device.go @@ -0,0 +1,67 @@ +/* + 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" + "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) +} + +// Validate the device. +func (d *Device) validate() error { + if err := ValidateDeviceName(d.Name); err != nil { + return err + } + edits := ContainerEdits{&d.ContainerEdits} + 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 +} diff --git a/pkg/cdi/device_test.go b/pkg/cdi/device_test.go new file mode 100644 index 0000000..650a612 --- /dev/null +++ b/pkg/cdi/device_test.go @@ -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 ( + "testing" + + cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" + "github.com/stretchr/testify/require" +) + +func TestDeviceValidate(t *testing.T) { + type testCase struct { + name string + device *Device + invalid bool + } + for _, tc := range []*testCase{ + { + name: "valid name, valid edits", + device: &Device{ + Device: &cdi.Device{ + Name: "dev", + ContainerEdits: cdi.ContainerEdits{ + Env: []string{"FOO=BAR"}, + }, + }, + }, + }, + { + name: "valid name, invalid edits", + device: &Device{ + Device: &cdi.Device{ + Name: "dev", + ContainerEdits: cdi.ContainerEdits{ + Env: []string{"=BAR"}, + }, + }, + }, + invalid: true, + }, + { + name: "invalid name, valid edits", + device: &Device{ + Device: &cdi.Device{ + Name: "a dev ice", + ContainerEdits: cdi.ContainerEdits{ + Env: []string{"FOO=BAR"}, + }, + }, + }, + invalid: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + err := tc.device.validate() + if tc.invalid { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/cdi/spec.go b/pkg/cdi/spec.go new file mode 100644 index 0000000..ec1ac35 --- /dev/null +++ b/pkg/cdi/spec.go @@ -0,0 +1,165 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + "sigs.k8s.io/yaml" + + cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" +) + +var ( + // Valid CDI Spec versions. + validSpecVersions = map[string]struct{}{ + "0.1.0": {}, + "0.2.0": {}, + } +) + +// 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) + } + + return NewSpec(raw, path, priority) +} + +// 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) { + var err error + + spec := &Spec{ + Spec: raw, + path: filepath.Clean(path), + priority: priority, + } + + spec.vendor, spec.class = ParseQualifier(spec.Kind) + + if spec.devices, err = spec.validate(); err != nil { + return nil, errors.Wrap(err, "invalid CDI Spec") + } + + return spec, nil +} + +// GetVendor returns the vendor of this Spec. +func (s *Spec) GetVendor() string { + return s.vendor +} + +// GetClass returns the device class of this Spec. +func (s *Spec) GetClass() string { + return s.class +} + +// GetDevice returns the device for the given unqualified name. +func (s *Spec) GetDevice(name string) *Device { + return s.devices[name] +} + +// GetPath returns the filesystem path of this Spec. +func (s *Spec) GetPath() string { + return s.path +} + +// GetPriority returns the priority of this Spec. +func (s *Spec) GetPriority() int { + return s.priority +} + +// Validate the Spec. +func (s *Spec) validate() (map[string]*Device, error) { + if _, ok := validSpecVersions[s.Version]; !ok { + return nil, errors.Errorf("invalid version %q", s.Version) + } + if err := ValidateVendorName(s.vendor); err != nil { + return nil, err + } + if err := ValidateClassName(s.class); err != nil { + return nil, err + } + edits := &ContainerEdits{&s.ContainerEdits} + if err := 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 +} + +// Parse raw CDI Spec file data. +func parseSpec(data []byte) (*cdi.Spec, error) { + raw := &cdi.Spec{} + err := yaml.UnmarshalStrict(data, &raw) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal CDI Spec") + } + return raw, validateJSONSchema(raw) +} + +// Validate CDI Spec against JSON Schema. +func validateJSONSchema(raw *cdi.Spec) error { + // TODO + return nil +} diff --git a/pkg/cdi/spec_test.go b/pkg/cdi/spec_test.go new file mode 100644 index 0000000..fab119d --- /dev/null +++ b/pkg/cdi/spec_test.go @@ -0,0 +1,348 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/pkg/errors" + + cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" + "github.com/stretchr/testify/require" +) + +func TestReadSpec(t *testing.T) { + type testCase struct { + name string + data string + unparsable bool + invalid bool + } + for _, tc := range []*testCase{ + { + name: "unparsable", + data: ` +xyzzy: garbled +`, + unparsable: true, + }, + { + name: "invalid, missing CDI version", + data: ` +kind: "vendor.com/device" +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + invalid: true, + }, + { + name: "valid", + data: ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + file, err := mkTestFile(t, []byte(tc.data)) + if err != nil { + t.Errorf("failed to create CDI Spec test file: %v", err) + return + } + + spec, err := ReadSpec(file, 0) + if tc.unparsable { + require.Error(t, err) + require.Nil(t, spec) + return + } + if tc.invalid { + require.Error(t, err) + require.Nil(t, spec) + return + } + require.NoError(t, err) + require.NotNil(t, spec) + }) + } +} + +func TestNewSpec(t *testing.T) { + type testCase struct { + name string + data string + unparsable bool + invalid bool + } + for _, tc := range []*testCase{ + { + name: "unparsable", + data: ` +xyzzy: garbled +`, + unparsable: true, + }, + { + name: "invalid, missing CDI version", + data: ` +kind: "vendor.com/device" +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + invalid: true, + }, + { + name: "invalid, invalid CDI version", + data: ` +cdiVersion: "0.0.0" +kind: "vendor.com/device" +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + invalid: true, + }, + { + name: "invalid, missing vendor/class", + data: ` +cdiVersion: "0.2.0" +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + invalid: true, + }, + { + name: "invalid, invalid vendor", + data: ` +cdiVersion: "0.2.0" +kind: vendor.com-/device +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + invalid: true, + }, + { + name: "invalid, invalid class", + data: ` +cdiVersion: "0.2.0" +kind: vendor.com/device= +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + invalid: true, + }, + { + name: "invalid, invalid device", + data: ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" +`, + invalid: true, + }, + { + name: "invalid, conflicting devices", + data: ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" + - name: "dev2" + containerEdits: + env: + - "BAR=FOO" + - name: "dev1" + containerEdits: + env: + - "SPACE=BAR" +`, + invalid: true, + }, + { + name: "valid", + data: ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" + - name: "dev2" + containerEdits: + env: + - "BAR=FOO" + - name: "dev3" + containerEdits: + env: + - "SPACE=BAR" +`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + raw *cdi.Spec + spec *Spec + err error + ) + + raw, err = parseSpec([]byte(tc.data)) + if tc.unparsable { + require.Error(t, err) + return + } + require.NoError(t, err) + + spec, err = NewSpec(raw, tc.name, 0) + if tc.invalid { + require.Error(t, err) + require.Nil(t, spec) + return + } + require.NoError(t, err) + require.NotNil(t, spec) + }) + } +} + +func TestGetters(t *testing.T) { + type testCase struct { + name string + priority int + data string + invalid bool + vendor string + class string + } + for _, tc := range []*testCase{ + { + name: "unparsable", + data: ` +xyzzy: garbled +`, + invalid: true, + }, + { + name: "invalid, missing CDI version", + data: ` +kind: "vendor.com/device" +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + invalid: true, + }, + { + name: "valid", + priority: 1, + data: ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + vendor: "vendor.com", + class: "device", + }, + } { + t.Run(tc.name, func(t *testing.T) { + file, err := mkTestFile(t, []byte(tc.data)) + if err != nil { + t.Errorf("failed to create CDI Spec test file: %v", err) + return + } + + spec, err := ReadSpec(file, tc.priority) + if tc.invalid { + require.Error(t, err) + require.Nil(t, spec) + return + } + + require.NoError(t, err) + require.NotNil(t, spec) + require.Equal(t, file, spec.GetPath()) + require.Equal(t, tc.priority, spec.GetPriority()) + + vendor, class := spec.GetVendor(), spec.GetClass() + require.Equal(t, tc.vendor, vendor) + require.Equal(t, tc.class, class) + + for name, d := range spec.devices { + require.Equal(t, spec, d.GetSpec()) + require.Equal(t, d, spec.GetDevice(name)) + require.Equal(t, QualifiedName(vendor, class, name), d.GetQualifiedName()) + } + }) + } +} + +// Create an automatically cleaned up temporary file for a test. +func mkTestFile(t *testing.T, data []byte) (string, error) { + tmp, err := ioutil.TempFile("", ".cdi-test.*") + if err != nil { + return "", errors.Wrapf(err, "failed to create test file") + } + + if data != nil { + _, err := tmp.Write(data) + if err != nil { + return "", errors.Wrap(err, "failed to write test file content") + } + } + + file := tmp.Name() + t.Cleanup(func() { + os.Remove(file) + }) + + tmp.Close() + return file, nil +} From b730453b8e7e4550ebd7c7b7e836b5f8e64929c0 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Tue, 7 Dec 2021 01:22:38 +0200 Subject: [PATCH 05/11] pkg/cdi: implement cache of specs and devices. Implement discovery and caching of spec files and devices, refreshing the cache. Signed-off-by: Krisztian Litkey --- pkg/cdi/cache.go | 147 ++++++++++++++++++++++ pkg/cdi/cache_test.go | 206 +++++++++++++++++++++++++++++++ pkg/cdi/spec-dirs.go | 108 ++++++++++++++++ pkg/cdi/spec-dirs_test.go | 251 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 712 insertions(+) create mode 100644 pkg/cdi/cache.go create mode 100644 pkg/cdi/cache_test.go create mode 100644 pkg/cdi/spec-dirs.go create mode 100644 pkg/cdi/spec-dirs_test.go diff --git a/pkg/cdi/cache.go b/pkg/cdi/cache.go new file mode 100644 index 0000000..cf0a673 --- /dev/null +++ b/pkg/cdi/cache.go @@ -0,0 +1,147 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "path/filepath" + "sync" + + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" +) + +// Option is an option to change some aspect of default CDI behavior. +type Option func(*Cache) error + +// Cache stores CDI Specs loaded from Spec directories. +type Cache struct { + sync.Mutex + specDirs []string + specs map[string][]*Spec + devices map[string]*Device + errors map[string][]error +} + +// NewCache creates a new CDI Cache. The cache is populated from a set +// of CDI Spec directories. These can be specified using a WithSpecDirs +// option. The default set of directories is exposed in DefaultSpecDirs. +func NewCache(options ...Option) (*Cache, error) { + c := &Cache{} + + if err := c.Configure(options...); err != nil { + return nil, err + } + if len(c.specDirs) == 0 { + c.Configure(WithSpecDirs(DefaultSpecDirs...)) + } + + return c, c.Refresh() +} + +// Configure applies options to the cache. Updates the cache if options have +// changed. +func (c *Cache) Configure(options ...Option) error { + if len(options) == 0 { + return nil + } + + c.Lock() + defer c.Unlock() + + for _, o := range options { + if err := o(c); err != nil { + return errors.Wrapf(err, "failed to apply cache options") + } + } + + return nil +} + +// Refresh rescans the CDI Spec directories and refreshes the Cache. +func (c *Cache) Refresh() error { + var ( + specs = map[string][]*Spec{} + devices = map[string]*Device{} + conflicts = map[string]struct{}{} + specErrors = map[string][]error{} + result []error + ) + + // collect errors per spec file path and once globally + collectError := func(err error, paths ...string) { + result = append(result, err) + for _, path := range paths { + specErrors[path] = append(specErrors[path], err) + } + } + // resolve conflicts based on device Spec priority (order of precedence) + resolveConflict := func(name string, dev *Device, old *Device) bool { + devSpec, oldSpec := dev.GetSpec(), old.GetSpec() + devPrio, oldPrio := devSpec.GetPriority(), oldSpec.GetPriority() + switch { + case devPrio > oldPrio: + return false + case devPrio == oldPrio: + devPath, oldPath := devSpec.GetPath(), oldSpec.GetPath() + collectError(errors.Errorf("conflicting device %q (specs %q, %q)", + name, devPath, oldPath), devPath, oldPath) + conflicts[name] = struct{}{} + } + return true + } + + _ = scanSpecDirs(c.specDirs, func(path string, priority int, spec *Spec, err error) error { + path = filepath.Clean(path) + if err != nil { + collectError(errors.Wrapf(err, "failed to load CDI Spec"), path) + return nil + } + + vendor := spec.GetVendor() + specs[vendor] = append(specs[vendor], spec) + + for _, dev := range spec.devices { + qualified := dev.GetQualifiedName() + other, ok := devices[qualified] + if ok { + if resolveConflict(qualified, dev, other) { + continue + } + } + devices[qualified] = dev + } + + return nil + }) + + for conflict := range conflicts { + delete(devices, conflict) + } + + c.Lock() + defer c.Unlock() + + c.specs = specs + c.devices = devices + c.errors = specErrors + + if len(result) > 0 { + return multierror.Append(nil, result...) + } + + return nil +} diff --git a/pkg/cdi/cache_test.go b/pkg/cdi/cache_test.go new file mode 100644 index 0000000..923c77b --- /dev/null +++ b/pkg/cdi/cache_test.go @@ -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 ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewCache(t *testing.T) { + type testCase struct { + name string + etc map[string]string + run map[string]string + sources map[string]string + errors map[string]struct{} + } + for _, tc := range []*testCase{ + { + name: "no spec dirs", + }, + { + name: "no spec files", + etc: map[string]string{}, + run: map[string]string{}, + }, + { + name: "one spec file", + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + sources: map[string]string{ + "vendor1.com/device=dev1": "etc/vendor1.yaml", + }, + }, + { + name: "multiple spec files with override", + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + }, + run: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + sources: map[string]string{ + "vendor1.com/device=dev1": "run/vendor1.yaml", + "vendor1.com/device=dev2": "etc/vendor1.yaml", + }, + }, + { + name: "multiple spec files, with conflicts", + run: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + "vendor1-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + sources: map[string]string{ + "vendor1.com/device=dev2": "run/vendor1.yaml", + }, + errors: map[string]struct{}{ + "run/vendor1.yaml": {}, + "run/vendor1-other.yaml": {}, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + dir string + err error + cache *Cache + ) + if tc.etc != nil || tc.run != nil { + dir, err = createSpecDirs(t, tc.etc, tc.run) + if err != nil { + t.Errorf("failed to create test directory: %v", err) + return + } + } + cache, err = NewCache(WithSpecDirs( + filepath.Join(dir, "etc"), + filepath.Join(dir, "run")), + ) + + if len(tc.errors) == 0 { + require.Nil(t, err) + } + require.NotNil(t, cache) + + for name, dev := range cache.devices { + require.Equal(t, filepath.Join(dir, tc.sources[name]), + dev.GetSpec().GetPath()) + } + for name, path := range tc.sources { + dev := cache.devices[name] + require.NotNil(t, dev) + require.Equal(t, filepath.Join(dir, path), + dev.GetSpec().GetPath()) + } + + for path := range tc.errors { + fullPath := filepath.Join(dir, path) + _, ok := cache.errors[fullPath] + require.True(t, ok) + } + for fullPath := range cache.errors { + path, err := filepath.Rel(dir, fullPath) + require.Nil(t, err) + _, ok := tc.errors[path] + require.True(t, ok) + } + }) + } +} + +// Create and populate automatically cleaned up spec directories. +func createSpecDirs(t *testing.T, etc, run map[string]string) (string, error) { + return mkTestDir(t, map[string]map[string]string{ + "etc": etc, + "run": run, + }) +} + +// Update spec directories with new data. +func updateSpecDirs(t *testing.T, dir string, etc, run map[string]string) error { + updates := map[string]map[string]string{ + "etc": {}, + "run": {}, + } + for sub, entries := range map[string]map[string]string{ + "etc": etc, + "run": run, + } { + path := filepath.Join(dir, sub) + for name, data := range entries { + if data == "remove" { + os.Remove(filepath.Join(path, name)) + } else { + updates[sub][name] = data + } + } + } + return updateTestDir(t, dir, updates) +} diff --git a/pkg/cdi/spec-dirs.go b/pkg/cdi/spec-dirs.go new file mode 100644 index 0000000..511a451 --- /dev/null +++ b/pkg/cdi/spec-dirs.go @@ -0,0 +1,108 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +const ( + // DefaultStaticDir is the default directory for static CDI Specs. + DefaultStaticDir = "/etc/cdi" + // DefaultDynamicDir is the default directory for generated CDI Specs + DefaultDynamicDir = "/var/run/cdi" +) + +var ( + // DefaultSpecDirs is the default Spec directory configuration. + // Altering this variable changes the defaults for the package. + // The new defaults only affect Cache instances created after + // the change. + DefaultSpecDirs = []string{DefaultStaticDir, DefaultDynamicDir} + // ErrStopScan can be returned from a ScanSpecFunc to stop the scan. + ErrStopScan = errors.New("stop Spec scan") +) + +// WithSpecDirs returns an option to override the CDI Spec directories. +func WithSpecDirs(dirs ...string) Option { + return func(c *Cache) error { + c.specDirs = make([]string, len(dirs)) + for i, dir := range dirs { + c.specDirs[i] = filepath.Clean(dir) + } + return nil + } +} + +// scanSpecFunc is a function for processing CDI Spec files. +type scanSpecFunc func(string, int, *Spec, error) error + +// ScanSpecDirs scans the given directories looking for CDI Spec files, +// which are all files with a '.json' or '.yaml' suffix. For every Spec +// file discovered, ScanSpecDirs loads a Spec from the file then calls +// the scan function passing it the path to the file, the priority (the +// index of the directory in the slice of directories given), the Spec +// itself, and any error encountered while loading the Spec. +// +// Scanning stops once all files have been processed or when the scan +// function returns an error. The result of ScanSpecDirs is the error +// returned by the scan function, if any. The special error ErrStopScan +// can be used to terminate the scan gracefully without ScanSpecDirs +// returning an error. ScanSpecDirs silently skips any subdirectories. +func scanSpecDirs(dirs []string, scanFn scanSpecFunc) error { + var ( + spec *Spec + err error + ) + + for priority, dir := range dirs { + err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + // for initial stat failure Walk calls us with nil info + if info == nil { + return err + } + // first call from Walk is for dir itself, others we skip + if info.IsDir() { + if path == dir { + return nil + } + return filepath.SkipDir + } + + // ignore obviously non-Spec files + if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" { + return nil + } + + if err != nil { + return scanFn(path, priority, nil, err) + } + + spec, err = ReadSpec(path, priority) + return scanFn(path, priority, spec, err) + }) + + if err != nil && err != ErrStopScan { + return err + } + } + + return nil +} diff --git a/pkg/cdi/spec-dirs_test.go b/pkg/cdi/spec-dirs_test.go new file mode 100644 index 0000000..71e3d38 --- /dev/null +++ b/pkg/cdi/spec-dirs_test.go @@ -0,0 +1,251 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func TestScanSpecDirs(t *testing.T) { + type testCase struct { + name string + files map[string]string + success map[string]struct{} + failure map[string]struct{} + vendors map[string]string + classes map[string]string + abort bool + } + for _, tc := range []*testCase{ + { + name: "no directory", + files: nil, + }, + { + name: "no files", + files: map[string]string{}, + success: map[string]struct{}{}, + failure: map[string]struct{}{}, + vendors: map[string]string{}, + classes: map[string]string{}, + }, + { + name: "one valid file", + files: map[string]string{ + "valid.yaml": ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + }, + success: map[string]struct{}{ + "valid.yaml": {}, + }, + failure: map[string]struct{}{}, + vendors: map[string]string{ + "valid.yaml": "vendor.com", + }, + classes: map[string]string{ + "valid.yaml": "device", + }, + }, + { + name: "one invalid file", + files: map[string]string{ + "invalid.yaml": ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" +`, + }, + success: map[string]struct{}{}, + failure: map[string]struct{}{ + "invalid.yaml": {}, + }, + vendors: map[string]string{}, + classes: map[string]string{}, + }, + { + name: "two valid files, one invalid file", + files: map[string]string{ + "valid1.yaml": ` +cdiVersion: "0.2.0" +kind: vendor.com/device1 +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + "valid2.yaml": ` +cdiVersion: "0.2.0" +kind: vendor.com/device2 +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + "invalid.yaml": ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" +`, + }, + success: map[string]struct{}{ + "valid1.yaml": {}, + "valid2.yaml": {}, + }, + failure: map[string]struct{}{ + "invalid.yaml": {}, + }, + vendors: map[string]string{ + "valid1.yaml": "vendor.com", + "valid2.yaml": "vendor.com", + }, + classes: map[string]string{ + "valid1.yaml": "device1", + "valid2.yaml": "device2", + }, + }, + { + // we assume running on an fs with sorted readdir()... + name: "one valid file, one invalid file, abort on first error", + files: map[string]string{ + "valid.yaml": ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" + containerEdits: + env: + - "FOO=BAR" +`, + "invalid.yaml": ` +cdiVersion: "0.2.0" +kind: vendor.com/device +devices: + - name: "dev1" +`, + }, + success: map[string]struct{}{}, + failure: map[string]struct{}{ + "invalid.yaml": {}, + }, + vendors: map[string]string{}, + classes: map[string]string{}, + abort: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + dir string + err error + success map[string]struct{} + failure map[string]struct{} + vendors map[string]string + classes map[string]string + ) + if tc.files != nil { + dir, err = mkTestDir(t, map[string]map[string]string{ + "etc": tc.files, + }) + if err != nil { + t.Errorf("failed to populate test directory: %v", err) + } + dir = filepath.Join(dir, "etc") + success = map[string]struct{}{} + failure = map[string]struct{}{} + vendors = map[string]string{} + classes = map[string]string{} + } + + dirs := []string{dir} + err = scanSpecDirs(dirs, func(path string, prio int, spec *Spec, err error) error { + name := filepath.Base(path) + if err != nil { + failure[name] = struct{}{} + if tc.abort { + return err + } + } else { + success[name] = struct{}{} + vendors[name] = spec.GetVendor() + classes[name] = spec.GetClass() + } + return nil + }) + + if tc.files == nil { + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + return + } + + require.Equal(t, tc.success, success) + require.Equal(t, tc.failure, failure) + require.Equal(t, tc.vendors, vendors) + require.Equal(t, tc.classes, classes) + }) + } +} + +// Create an automatically cleaned up temporary directory, with optional content. +func mkTestDir(t *testing.T, dirs map[string]map[string]string) (string, error) { + tmp, err := ioutil.TempDir("", ".cache-test*") + if err != nil { + return "", errors.Wrapf(err, "failed to create test directory") + } + + t.Cleanup(func() { + os.RemoveAll(tmp) + }) + + if err = updateTestDir(t, tmp, dirs); err != nil { + return "", err + } + + return tmp, nil +} + +func updateTestDir(t *testing.T, tmp string, dirs map[string]map[string]string) error { + for sub, content := range dirs { + dir := filepath.Join(tmp, sub) + if err := os.MkdirAll(dir, 0755); err != nil { + return errors.Wrapf(err, "failed to create directory %q", dir) + } + for file, data := range content { + file := filepath.Join(dir, file) + if err := ioutil.WriteFile(file, []byte(data), 0644); err != nil { + return errors.Wrapf(err, "failed to write file %q", file) + } + } + } + return nil +} From 46744e2ed10eab0327c30c9d0e4fae246f522217 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 8 Dec 2021 15:55:09 +0200 Subject: [PATCH 06/11] pkg/cdi: initial registry implementation. Implement registry, related interfaces and functions. Signed-off-by: Krisztian Litkey --- pkg/cdi/cache.go | 128 ++++++ pkg/cdi/cache_test.go | 826 +++++++++++++++++++++++++++++++++++++++ pkg/cdi/device.go | 7 + pkg/cdi/registry.go | 139 +++++++ pkg/cdi/registry_test.go | 701 +++++++++++++++++++++++++++++++++ pkg/cdi/spec.go | 7 + 6 files changed, 1808 insertions(+) create mode 100644 pkg/cdi/registry.go create mode 100644 pkg/cdi/registry_test.go diff --git a/pkg/cdi/cache.go b/pkg/cdi/cache.go index cf0a673..c4befc8 100644 --- a/pkg/cdi/cache.go +++ b/pkg/cdi/cache.go @@ -18,9 +18,12 @@ package cdi import ( "path/filepath" + "sort" + "strings" "sync" "github.com/hashicorp/go-multierror" + oci "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -145,3 +148,128 @@ func (c *Cache) Refresh() error { return nil } + +// InjectDevices injects the given qualified devices to an OCI Spec. It +// returns any unresolvable devices and an error if injection fails for +// any of the devices. +func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, error) { + var ( + unresolved []string + specs = map[*Spec]struct{}{} + ) + + if ociSpec == nil { + return devices, errors.Errorf("can't inject devices, nil OCI Spec") + } + + c.Lock() + defer c.Unlock() + + 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{}{} + d.GetSpec().ApplyEdits(ociSpec) + } + d.ApplyEdits(ociSpec) + } + + if unresolved != nil { + return unresolved, errors.Errorf("unresolvable CDI devices %s", + strings.Join(devices, ", ")) + } + + return nil, nil +} + +// GetDevice returns the cached device for the given qualified name. +func (c *Cache) GetDevice(device string) *Device { + c.Lock() + defer c.Unlock() + + return c.devices[device] +} + +// ListDevices lists all cached devices by qualified name. +func (c *Cache) ListDevices() []string { + var devices []string + + c.Lock() + defer c.Unlock() + + for name := range c.devices { + devices = append(devices, name) + } + sort.Strings(devices) + + return devices +} + +// ListVendors lists all vendors known to the cache. +func (c *Cache) ListVendors() []string { + var vendors []string + + c.Lock() + defer c.Unlock() + + for vendor := range c.specs { + vendors = append(vendors, vendor) + } + sort.Strings(vendors) + + return vendors +} + +// ListClasses lists all device classes known to the cache. +func (c *Cache) ListClasses() []string { + var ( + cmap = map[string]struct{}{} + classes []string + ) + + c.Lock() + defer c.Unlock() + + for _, specs := range c.specs { + for _, spec := range specs { + cmap[spec.GetClass()] = struct{}{} + } + } + for class := range cmap { + classes = append(classes, class) + } + sort.Strings(classes) + + return classes +} + +// GetVendorSpecs returns all specs for the given vendor. +func (c *Cache) GetVendorSpecs(vendor string) []*Spec { + c.Lock() + defer c.Unlock() + + return c.specs[vendor] +} + +// GetSpecErrors returns all errors encountered for the spec during the +// last cache refresh. +func (c *Cache) GetSpecErrors(spec *Spec) []error { + return c.errors[spec.GetPath()] +} + +// GetErrors returns all errors encountered during the last +// cache refresh. +func (c *Cache) GetErrors() map[string][]error { + return c.errors +} + +// GetSpecDirectories returns the CDI Spec directories currently in use. +func (c *Cache) GetSpecDirectories() []string { + dirs := make([]string, len(c.specDirs)) + copy(dirs, c.specDirs) + return dirs +} diff --git a/pkg/cdi/cache_test.go b/pkg/cdi/cache_test.go index 923c77b..f24687b 100644 --- a/pkg/cdi/cache_test.go +++ b/pkg/cdi/cache_test.go @@ -21,6 +21,7 @@ import ( "path/filepath" "testing" + oci "github.com/opencontainers/runtime-spec/specs-go" "github.com/stretchr/testify/require" ) @@ -175,6 +176,831 @@ devices: } } +func TestRefreshCache(t *testing.T) { + type specDirs struct { + etc map[string]string + run map[string]string + } + type testCase struct { + name string + updates []specDirs + errors []map[string]struct{} + devices [][]string + devprio []map[string]int + } + for _, tc := range []*testCase{ + { + name: "empty cache, add one Spec", + updates: []specDirs{ + {}, + { + run: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + }, + devices: [][]string{ + nil, + { + "vendor1.com/device=dev1", + }, + }, + devprio: []map[string]int{ + {}, + { + "vendor1.com/device=dev1": 1, + }, + }, + errors: []map[string]struct{}{ + {}, + {}, + }, + }, + { + name: "one Spec, add another, no shadowing, no conflicts", + updates: []specDirs{ + { + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + { + run: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + }, + }, + }, + devices: [][]string{ + { + "vendor1.com/device=dev1", + }, + { + "vendor1.com/device=dev1", + "vendor1.com/device=dev2", + }, + }, + devprio: []map[string]int{ + { + "vendor1.com/device=dev1": 0, + }, + { + "vendor1.com/device=dev1": 0, + "vendor1.com/device=dev2": 1, + }, + }, + errors: []map[string]struct{}{ + {}, + {}, + }, + }, + { + name: "two Specs, remove one", + updates: []specDirs{ + { + run: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + "vendor1-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + }, + }, + { + run: map[string]string{ + "vendor1.yaml": "remove", + }, + }, + }, + devices: [][]string{ + { + "vendor1.com/device=dev1", + "vendor1.com/device=dev2", + }, + { + "vendor1.com/device=dev2", + }, + }, + devprio: []map[string]int{ + { + "vendor1.com/device=dev1": 1, + "vendor1.com/device=dev2": 1, + }, + { + "vendor1.com/device=dev2": 1, + }, + }, + errors: []map[string]struct{}{ + {}, + {}, + }, + }, + { + name: "one Spec, add another, shadowing", + updates: []specDirs{ + { + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + { + run: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + }, + devices: [][]string{ + { + "vendor1.com/device=dev1", + }, + { + "vendor1.com/device=dev1", + }, + }, + devprio: []map[string]int{ + { + "vendor1.com/device=dev1": 0, + }, + { + "vendor1.com/device=dev1": 1, + }, + }, + errors: []map[string]struct{}{ + {}, + {}, + }, + }, + { + name: "one Spec, add another, conflicts", + updates: []specDirs{ + { + run: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + }, + }, + { + run: map[string]string{ + "vendor1-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev3" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev3" +`, + }, + }, + }, + devices: [][]string{ + { + "vendor1.com/device=dev1", + "vendor1.com/device=dev2", + }, + { + "vendor1.com/device=dev2", + "vendor1.com/device=dev3", + }, + }, + devprio: []map[string]int{ + { + "vendor1.com/device=dev1": 1, + "vendor1.com/device=dev2": 1, + }, + { + "vendor1.com/device=dev2": 1, + "vendor1.com/device=dev3": 1, + }, + }, + errors: []map[string]struct{}{ + {}, + { + "run/vendor1.yaml": {}, + "run/vendor1-other.yaml": {}, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + dir string + err error + cache *Cache + ) + for idx, update := range tc.updates { + if idx == 0 { + dir, err = createSpecDirs(t, update.etc, update.run) + if err != nil { + t.Errorf("failed to create test directory: %v", err) + return + } + cache, err = NewCache( + WithSpecDirs( + filepath.Join(dir, "etc"), + filepath.Join(dir, "run"), + ), + ) + } else { + err = updateSpecDirs(t, dir, update.etc, update.run) + if err != nil { + t.Errorf("failed to update test directory: %v", err) + return + } + } + err = cache.Refresh() + + if len(tc.errors[idx]) == 0 { + require.Nil(t, err) + } else { + require.NotNil(t, err) + } + require.NotNil(t, cache) + + devices := cache.ListDevices() + if len(tc.devices[idx]) == 0 { + require.True(t, len(devices) == 0) + } else { + require.Equal(t, tc.devices[idx], devices) + } + + for name, prio := range tc.devprio[idx] { + dev := cache.GetDevice(name) + require.NotNil(t, dev) + require.Equal(t, dev.GetSpec().GetPriority(), prio) + } + + for _, v := range cache.ListVendors() { + for _, spec := range cache.GetVendorSpecs(v) { + err := cache.GetSpecErrors(spec) + relSpecPath, _ := filepath.Rel(dir, spec.GetPath()) + _, ok := tc.errors[idx][relSpecPath] + require.True(t, (err == nil && !ok) || (err != nil && ok)) + } + } + } + }) + } +} + +func TestInjectDevice(t *testing.T) { + type specDirs struct { + etc map[string]string + run map[string]string + } + type testCase struct { + name string + cdiSpecs specDirs + ociSpec *oci.Spec + devices []string + result *oci.Spec + unresolved []string + } + for _, tc := range []*testCase{ + { + name: "empty OCI Spec, inject one device", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + ociSpec: &oci.Spec{}, + devices: []string{ + "vendor1.com/device=dev1", + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "VENDOR1_SPEC_VAR1=VAL1", + "VENDOR1_VAR1=VAL1", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/vendor1-dev1", + }, + }, + }, + }, + }, + { + name: "non-empty OCI Spec, inject one device", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + ociSpec: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + }, + }, + }, + devices: []string{ + "vendor1.com/device=dev1", + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + "VENDOR1_SPEC_VAR1=VAL1", + "VENDOR1_VAR1=VAL1", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + { + Path: "/dev/vendor1-dev1", + }, + }, + }, + }, + }, + { + name: "non-empty OCI Spec, inject several devices, hooks", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_DEV1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-dev2" + hooks: + - hookName: prestart + path: "/usr/local/bin/prestart-vendor-hook" + args: + - "--verbose" + env: + - "HOOK_ENV1=PRESTART_VAL1" + - hookName: createRuntime + path: "/usr/local/bin/cr-vendor-hook" + args: + - "--debug" + env: + - "HOOK_ENV1=CREATE_RUNTIME_VAL1" + - name: "dev3" + containerEdits: + env: + - "VENDOR1_DEV3=VAL3" + deviceNodes: + - path: "/dev/vendor1-dev3" +`, + }, + }, + ociSpec: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + }, + }, + }, + devices: []string{ + "vendor1.com/device=dev1", + "vendor1.com/device=dev2", + "vendor1.com/device=dev3", + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + "VENDOR1_SPEC_VAR1=VAL1", + "VENDOR1_DEV1=VAL1", + "VENDOR1_DEV2=VAL2", + "VENDOR1_DEV3=VAL3", + }, + }, + Hooks: &oci.Hooks{ + Prestart: []oci.Hook{ + { + Path: "/usr/local/bin/prestart-vendor-hook", + Args: []string{"--verbose"}, + Env: []string{"HOOK_ENV1=PRESTART_VAL1"}, + }, + }, + CreateRuntime: []oci.Hook{ + { + Path: "/usr/local/bin/cr-vendor-hook", + Args: []string{"--debug"}, + Env: []string{"HOOK_ENV1=CREATE_RUNTIME_VAL1"}, + }, + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + { + Path: "/dev/vendor1-dev1", + }, + { + Path: "/dev/vendor1-dev2", + }, + { + Path: "/dev/vendor1-dev3", + }, + }, + }, + }, + }, + { + name: "empty OCI Spec, non-existent device", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + ociSpec: &oci.Spec{}, + devices: []string{ + "vendor1.com/device=dev2", + }, + result: &oci.Spec{}, + unresolved: []string{ + "vendor1.com/device=dev2", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + dir string + err error + cache *Cache + ) + dir, err = createSpecDirs(t, tc.cdiSpecs.etc, tc.cdiSpecs.run) + if err != nil { + t.Errorf("failed to create test directory: %v", err) + return + } + cache, err = NewCache( + WithSpecDirs( + filepath.Join(dir, "etc"), + filepath.Join(dir, "run"), + ), + ) + require.Nil(t, err) + require.NotNil(t, cache) + + unresolved, err := cache.InjectDevices(tc.ociSpec, tc.devices...) + if len(tc.unresolved) != 0 { + require.NotNil(t, err) + require.Equal(t, tc.unresolved, unresolved) + return + } + + require.Nil(t, err) + require.Equal(t, tc.result, tc.ociSpec) + }) + } +} + +func TestListVendorsAndClasses(t *testing.T) { + type specDirs struct { + etc map[string]string + run map[string]string + } + type testCase struct { + name string + cdiSpecs specDirs + vendors []string + classes []string + } + for _, tc := range []*testCase{ + { + name: "no vendors, no classes", + }, + { + name: "one vendor, one class", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + vendors: []string{ + "vendor1.com", + }, + classes: []string{ + "device", + }, + }, + { + name: "one vendor, multiple classes", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + "vendor1-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/other-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-other-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-other-dev2" +`, + }, + }, + vendors: []string{ + "vendor1.com", + }, + classes: []string{ + "device", + "other-device", + }, + }, + { + name: "multiple vendor, multiple classes", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + "vendor2.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor2.com/other-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-dev2" +`, + "vendor2-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor2.com/another-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-another-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-another-dev2" +`, + "vendor3.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor3.com/yet-another-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor3-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor3-dev2" +`, + }, + }, + vendors: []string{ + "vendor1.com", + "vendor2.com", + "vendor3.com", + }, + classes: []string{ + "another-device", + "device", + "other-device", + "yet-another-device", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + dir string + err error + cache *Cache + ) + dir, err = createSpecDirs(t, tc.cdiSpecs.etc, tc.cdiSpecs.run) + if err != nil { + t.Errorf("failed to create test directory: %v", err) + return + } + cache, err = NewCache( + WithSpecDirs( + filepath.Join(dir, "etc"), + filepath.Join(dir, "run"), + ), + ) + require.Nil(t, err) + require.NotNil(t, cache) + + vendors := cache.ListVendors() + require.Equal(t, tc.vendors, vendors) + classes := cache.ListClasses() + require.Equal(t, tc.classes, classes) + }) + } +} + // Create and populate automatically cleaned up spec directories. func createSpecDirs(t *testing.T, etc, run map[string]string) (string, error) { return mkTestDir(t, map[string]map[string]string{ diff --git a/pkg/cdi/device.go b/pkg/cdi/device.go index d4c2491..84b7142 100644 --- a/pkg/cdi/device.go +++ b/pkg/cdi/device.go @@ -18,6 +18,7 @@ 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" ) @@ -51,6 +52,12 @@ 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 { + e := ContainerEdits{&d.ContainerEdits} + return e.Apply(ociSpec) +} + // Validate the device. func (d *Device) validate() error { if err := ValidateDeviceName(d.Name); err != nil { diff --git a/pkg/cdi/registry.go b/pkg/cdi/registry.go new file mode 100644 index 0000000..fa6e0af --- /dev/null +++ b/pkg/cdi/registry.go @@ -0,0 +1,139 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "sync" + + oci "github.com/opencontainers/runtime-spec/specs-go" +) + +// +// Registry keeps a cache of all CDI Specs installed or generated on +// the host. Registry is the primary interface clients should use to +// interact with CDI. +// +// The most commonly used Registry functions are for refreshing the +// registry and injecting CDI devices into an OCI Spec. +// +type Registry interface { + RegistryResolver + RegistryRefresher + DeviceDB() RegistryDeviceDB + SpecDB() RegistrySpecDB +} + +// RegistryRefresher is the registry interface for refreshing the +// cache of CDI Specs and devices. +// +// Refresh rescans all CDI Spec directories and updates the +// state of the cache to reflect any changes. It returns any +// errors encountered during the refresh. +// +// GetErrors returns all errors encountered for any of the scanned +// Spec files during the last cache refresh. +// +// GetSpecDirectories returns the set up CDI Spec directories +// currently in use. The directories are returned in the scan +// order of Refresh(). +type RegistryRefresher interface { + Refresh() error + GetErrors() map[string][]error + GetSpecDirectories() []string +} + +// RegistryResolver is the registry interface for injecting CDI +// devices into an OCI Spec. +// +// InjectDevices takes an OCI Spec and injects into it a set of +// CDI devices given by qualified name. It returns the names of +// any unresolved devices and an error if injection fails. +type RegistryResolver interface { + InjectDevices(spec *oci.Spec, device ...string) (unresolved []string, err error) +} + +// RegistryDeviceDB is the registry interface for querying devices. +// +// GetDevice returns the CDI device for the given qualified name. If +// the device is not GetDevice returns nil. +// +// ListDevices returns a slice with the names of qualified device +// known. The returned slice is sorted. +type RegistryDeviceDB interface { + GetDevice(device string) *Device + ListDevices() []string +} + +// RegistrySpecDB is the registry interface for querying CDI Specs. +// +// ListVendors returns a slice with all vendors known. The returned +// slice is sorted. +// +// ListClasses returns a slice with all classes known. The returned +// slice is sorted. +// +// GetVendorSpecs returns a slice of all Specs for the vendor. +// +// GetSpecErrors returns any errors for the Spec encountered during +// the last cache refresh. +type RegistrySpecDB interface { + ListVendors() []string + ListClasses() []string + GetVendorSpecs(vendor string) []*Spec + GetSpecErrors(*Spec) []error +} + +type registry struct { + *Cache +} + +var _ Registry = ®istry{} + +var ( + reg *registry + initOnce sync.Once +) + +// GetRegistry returns the CDI registry. If any options are given, those +// are applied to the registry. +func GetRegistry(options ...Option) Registry { + var new bool + initOnce.Do(func() { + reg, _ = getRegistry(options...) + new = true + }) + if !new && len(options) > 0 { + reg.Configure(options...) + reg.Refresh() + } + return reg +} + +// DeviceDB returns the registry interface for querying devices. +func (r *registry) DeviceDB() RegistryDeviceDB { + return r +} + +// SpecDB returns the registry interface for querying Specs. +func (r *registry) SpecDB() RegistrySpecDB { + return r +} + +func getRegistry(options ...Option) (*registry, error) { + c, err := NewCache(options...) + return ®istry{c}, err +} diff --git a/pkg/cdi/registry_test.go b/pkg/cdi/registry_test.go new file mode 100644 index 0000000..ac799c6 --- /dev/null +++ b/pkg/cdi/registry_test.go @@ -0,0 +1,701 @@ +/* + Copyright © 2021 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cdi + +import ( + "path/filepath" + "testing" + + oci "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/require" +) + +func TestRegistryResolver(t *testing.T) { + type specDirs struct { + etc map[string]string + run map[string]string + } + type testCase struct { + name string + cdiSpecs specDirs + ociSpec *oci.Spec + devices []string + result *oci.Spec + unresolved []string + } + for _, tc := range []*testCase{ + { + name: "empty OCI Spec, inject one device", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + ociSpec: &oci.Spec{}, + devices: []string{ + "vendor1.com/device=dev1", + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "VENDOR1_SPEC_VAR1=VAL1", + "VENDOR1_VAR1=VAL1", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/vendor1-dev1", + }, + }, + }, + }, + }, + { + name: "non-empty OCI Spec, inject one device", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + ociSpec: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + }, + }, + }, + devices: []string{ + "vendor1.com/device=dev1", + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + "VENDOR1_SPEC_VAR1=VAL1", + "VENDOR1_VAR1=VAL1", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + { + Path: "/dev/vendor1-dev1", + }, + }, + }, + }, + }, + { + name: "non-empty OCI Spec, inject several devices, hooks", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_DEV1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-dev2" + hooks: + - hookName: prestart + path: "/usr/local/bin/prestart-vendor-hook" + args: + - "--verbose" + env: + - "HOOK_ENV1=PRESTART_VAL1" + - hookName: createRuntime + path: "/usr/local/bin/cr-vendor-hook" + args: + - "--debug" + env: + - "HOOK_ENV1=CREATE_RUNTIME_VAL1" + - name: "dev3" + containerEdits: + env: + - "VENDOR1_DEV3=VAL3" + deviceNodes: + - path: "/dev/vendor1-dev3" +`, + }, + }, + ociSpec: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + }, + }, + }, + devices: []string{ + "vendor1.com/device=dev1", + "vendor1.com/device=dev2", + "vendor1.com/device=dev3", + }, + result: &oci.Spec{ + Process: &oci.Process{ + Env: []string{ + "ORIG_VAR1=VAL1", + "ORIG_VAR2=VAL2", + "VENDOR1_SPEC_VAR1=VAL1", + "VENDOR1_DEV1=VAL1", + "VENDOR1_DEV2=VAL2", + "VENDOR1_DEV3=VAL3", + }, + }, + Hooks: &oci.Hooks{ + Prestart: []oci.Hook{ + { + Path: "/usr/local/bin/prestart-vendor-hook", + Args: []string{"--verbose"}, + Env: []string{"HOOK_ENV1=PRESTART_VAL1"}, + }, + }, + CreateRuntime: []oci.Hook{ + { + Path: "/usr/local/bin/cr-vendor-hook", + Args: []string{"--debug"}, + Env: []string{"HOOK_ENV1=CREATE_RUNTIME_VAL1"}, + }, + }, + }, + Linux: &oci.Linux{ + Devices: []oci.LinuxDevice{ + { + Path: "/dev/null", + }, + { + Path: "/dev/zero", + }, + { + Path: "/dev/vendor1-dev1", + }, + { + Path: "/dev/vendor1-dev2", + }, + { + Path: "/dev/vendor1-dev3", + }, + }, + }, + }, + }, + { + name: "empty OCI Spec, non-existent device", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + ociSpec: &oci.Spec{}, + devices: []string{ + "vendor1.com/device=dev2", + }, + result: &oci.Spec{}, + unresolved: []string{ + "vendor1.com/device=dev2", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + dir string + err error + reg Registry + ) + dir, err = createSpecDirs(t, tc.cdiSpecs.etc, tc.cdiSpecs.run) + if err != nil { + t.Errorf("failed to create test directory: %v", err) + return + } + reg = GetRegistry( + WithSpecDirs( + filepath.Join(dir, "etc"), + filepath.Join(dir, "run"), + ), + ) + require.Nil(t, err) + require.NotNil(t, reg) + + unresolved, err := reg.InjectDevices(tc.ociSpec, tc.devices...) + if len(tc.unresolved) != 0 { + require.NotNil(t, err) + require.Equal(t, tc.unresolved, unresolved) + return + } + + require.Nil(t, err) + require.Equal(t, tc.result, tc.ociSpec) + }) + } +} + +func TestRegistrySpecDB(t *testing.T) { + type specDirs struct { + etc map[string]string + run map[string]string + } + type testCase struct { + name string + cdiSpecs specDirs + vendors []string + classes []string + } + for _, tc := range []*testCase{ + { + name: "no vendors, no classes", + }, + { + name: "one vendor, one class", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + vendors: []string{ + "vendor1.com", + }, + classes: []string{ + "device", + }, + }, + { + name: "one vendor, multiple classes", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + "vendor1-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/other-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-other-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-other-dev2" +`, + }, + }, + vendors: []string{ + "vendor1.com", + }, + classes: []string{ + "device", + "other-device", + }, + }, + { + name: "multiple vendor, multiple classes", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + "vendor2.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor2.com/other-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-dev2" +`, + "vendor2-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor2.com/another-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-another-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-another-dev2" +`, + "vendor3.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor3.com/yet-another-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor3-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor3-dev2" +`, + }, + }, + vendors: []string{ + "vendor1.com", + "vendor2.com", + "vendor3.com", + }, + classes: []string{ + "another-device", + "device", + "other-device", + "yet-another-device", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + dir string + err error + reg Registry + ) + dir, err = createSpecDirs(t, tc.cdiSpecs.etc, tc.cdiSpecs.run) + if err != nil { + t.Errorf("failed to create test directory: %v", err) + return + } + reg = GetRegistry( + WithSpecDirs( + filepath.Join(dir, "etc"), + filepath.Join(dir, "run"), + ), + ) + require.Nil(t, err) + require.NotNil(t, reg) + + vendors := reg.SpecDB().ListVendors() + require.Equal(t, tc.vendors, vendors) + classes := reg.SpecDB().ListClasses() + require.Equal(t, tc.classes, classes) + }) + } +} + +func TestRegistryDeviceDB(t *testing.T) { + type specDirs struct { + etc map[string]string + run map[string]string + } + type testCase struct { + name string + cdiSpecs specDirs + devices []string + } + for _, tc := range []*testCase{ + { + name: "no vendors, no classes", + }, + { + name: "one vendor, one class", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: + env: + - VENDOR1_SPEC_VAR1=VAL1 +devices: + - name: "dev1" + containerEdits: + env: + - "VENDOR1_VAR1=VAL1" + deviceNodes: + - path: "/dev/vendor1-dev1" +`, + }, + }, + devices: []string{ + "vendor1.com/device=dev1", + }, + }, + { + name: "one vendor, multiple classes", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + "vendor1-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/other-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-other-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-other-dev2" +`, + }, + }, + devices: []string{ + "vendor1.com/device=dev1", + "vendor1.com/device=dev2", + "vendor1.com/other-device=dev1", + "vendor1.com/other-device=dev2", + }, + }, + { + name: "multiple vendor, multiple classes", + cdiSpecs: specDirs{ + etc: map[string]string{ + "vendor1.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor1.com/device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor1-dev1" + - name: "dev2" + containerEdits: + env: + - "VENDOR1_DEV2=VAL2" + deviceNodes: + - path: "/dev/vendor1-dev2" +`, + "vendor2.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor2.com/other-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-dev2" +`, + "vendor2-other.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor2.com/another-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-another-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor2-another-dev2" +`, + "vendor3.yaml": ` +cdiVersion: "0.2.0" +kind: "vendor3.com/yet-another-device" +containerEdits: +devices: + - name: "dev1" + containerEdits: + deviceNodes: + - path: "/dev/vendor3-dev1" + - name: "dev2" + containerEdits: + deviceNodes: + - path: "/dev/vendor3-dev2" +`, + }, + }, + devices: []string{ + "vendor1.com/device=dev1", + "vendor1.com/device=dev2", + "vendor2.com/another-device=dev1", + "vendor2.com/another-device=dev2", + "vendor2.com/other-device=dev1", + "vendor2.com/other-device=dev2", + "vendor3.com/yet-another-device=dev1", + "vendor3.com/yet-another-device=dev2", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var ( + dir string + err error + reg Registry + ) + dir, err = createSpecDirs(t, tc.cdiSpecs.etc, tc.cdiSpecs.run) + if err != nil { + t.Errorf("failed to create test directory: %v", err) + return + } + reg = GetRegistry( + WithSpecDirs( + filepath.Join(dir, "etc"), + filepath.Join(dir, "run"), + ), + ) + require.Nil(t, err) + require.NotNil(t, reg) + + devices := reg.DeviceDB().ListDevices() + require.Equal(t, tc.devices, devices) + }) + } +} diff --git a/pkg/cdi/spec.go b/pkg/cdi/spec.go index ec1ac35..20f1884 100644 --- a/pkg/cdi/spec.go +++ b/pkg/cdi/spec.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" + oci "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "sigs.k8s.io/yaml" @@ -117,6 +118,12 @@ 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 { + e := ContainerEdits{&s.ContainerEdits} + return e.Apply(ociSpec) +} + // Validate the Spec. func (s *Spec) validate() (map[string]*Device, error) { if _, ok := validSpecVersions[s.Version]; !ok { From a2f33549206a60d013feaea2ed164fe1c59340f9 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Sun, 28 Nov 2021 19:19:01 +0200 Subject: [PATCH 07/11] cmd/cdi: add sample binary to exercise the API. Add a sample 'cdi' binary to exercise the API. It can also serve as a CDI command line debug utility for Spec maintainers. Signed-off-by: Krisztian Litkey --- cmd/cdi/cmd/cdi-api.go | 351 ++++++++++++++++++++++++++++++++++++++++ cmd/cdi/cmd/classes.go | 35 ++++ cmd/cdi/cmd/devices.go | 50 ++++++ cmd/cdi/cmd/dirs.go | 41 +++++ cmd/cdi/cmd/format.go | 66 ++++++++ cmd/cdi/cmd/inject.go | 96 +++++++++++ cmd/cdi/cmd/monitor.go | 169 +++++++++++++++++++ cmd/cdi/cmd/resolve.go | 92 +++++++++++ cmd/cdi/cmd/root.go | 60 +++++++ cmd/cdi/cmd/specs.go | 57 +++++++ cmd/cdi/cmd/validate.go | 52 ++++++ cmd/cdi/cmd/vendors.go | 35 ++++ cmd/cdi/main.go | 23 +++ 13 files changed, 1127 insertions(+) create mode 100644 cmd/cdi/cmd/cdi-api.go create mode 100644 cmd/cdi/cmd/classes.go create mode 100644 cmd/cdi/cmd/devices.go create mode 100644 cmd/cdi/cmd/dirs.go create mode 100644 cmd/cdi/cmd/format.go create mode 100644 cmd/cdi/cmd/inject.go create mode 100644 cmd/cdi/cmd/monitor.go create mode 100644 cmd/cdi/cmd/resolve.go create mode 100644 cmd/cdi/cmd/root.go create mode 100644 cmd/cdi/cmd/specs.go create mode 100644 cmd/cdi/cmd/validate.go create mode 100644 cmd/cdi/cmd/vendors.go create mode 100644 cmd/cdi/main.go diff --git a/cmd/cdi/cmd/cdi-api.go b/cmd/cdi/cmd/cdi-api.go new file mode 100644 index 0000000..019eab6 --- /dev/null +++ b/cmd/cdi/cmd/cdi-api.go @@ -0,0 +1,351 @@ +/* + 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 cmd + +import ( + "fmt" + "path/filepath" + "sort" + "strings" + + "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" + oci "github.com/opencontainers/runtime-spec/specs-go" + gen "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" +) + +func cdiListVendors() { + var ( + registry = cdi.GetRegistry() + vendors = registry.SpecDB().ListVendors() + ) + + if len(vendors) == 0 { + fmt.Printf("No CDI vendors found.\n") + return + } + + fmt.Printf("CDI vendors found:\n") + for idx, vendor := range vendors { + fmt.Printf(" %d. %q (%d CDI Spec Files)\n", idx, vendor, + len(registry.SpecDB().GetVendorSpecs(vendor))) + } +} + +func cdiListClasses() { + var ( + registry = cdi.GetRegistry() + vendors = map[string][]string{} + ) + + for _, class := range registry.SpecDB().ListClasses() { + vendors[class] = []string{} + for _, vendor := range registry.SpecDB().ListVendors() { + for _, spec := range registry.SpecDB().GetVendorSpecs(vendor) { + if spec.GetClass() == class { + vendors[class] = append(vendors[class], vendor) + } + } + } + } + + if len(vendors) == 0 { + fmt.Printf("No CDI device classes found.\n") + return + } + + fmt.Printf("CDI device classes found:\n") + for idx, class := range registry.SpecDB().ListClasses() { + sort.Strings(vendors[class]) + fmt.Printf(" %d. %s (%d vendors: %s)\n", idx, class, + len(vendors[class]), strings.Join(vendors[class], ", ")) + } +} + +func cdiListDevices(verbose bool, format string) { + var ( + registry = cdi.GetRegistry() + devices = registry.DeviceDB().ListDevices() + ) + + if len(devices) == 0 { + fmt.Printf("No CDI devices found.\n") + return + } + + fmt.Printf("CDI devices found:\n") + for idx, device := range devices { + cdiPrintDevice(idx, registry.DeviceDB().GetDevice(device), verbose, format, 2) + } +} + +func cdiPrintDevice(idx int, dev *cdi.Device, verbose bool, format string, level int) { + if !verbose { + if idx >= 0 { + fmt.Printf("%s%d. %s\n", indent(level), idx, dev.GetQualifiedName()) + return + } + fmt.Printf("%s%s\n", indent(level), dev.GetQualifiedName()) + return + } + + var ( + spec = dev.GetSpec() + ) + + format = chooseFormat(format, spec.GetPath()) + + fmt.Printf(" %s (%s)\n", dev.GetQualifiedName(), spec.GetPath()) + fmt.Printf("%s", marshalObject(level+2, dev.Device, format)) + edits := spec.ContainerEdits + if len(edits.Env)+len(edits.DeviceNodes)+len(edits.Hooks)+len(edits.Mounts) > 0 { + fmt.Printf("%s global Spec containerEdits:\n", indent(level+2)) + fmt.Printf("%s", marshalObject(level+4, spec.ContainerEdits, format)) + } +} + +func cdiShowSpecDirs() { + var ( + registry = cdi.GetRegistry() + specDirs = registry.GetSpecDirectories() + cdiErrors = registry.GetErrors() + ) + fmt.Printf("CDI Spec directories in use:\n") + for prio, dir := range specDirs { + fmt.Printf(" %s (priority %d)\n", dir, prio) + for path, specErrors := range cdiErrors { + if filepath.Dir(path) != dir { + continue + } + for _, err := range specErrors { + fmt.Printf(" - has error %v\n", err) + } + } + } +} + +func cdiInjectDevices(format string, ociSpec *oci.Spec, patterns []string) error { + var ( + registry = cdi.GetRegistry() + matches = map[string]struct{}{} + devices = []string{} + ) + + for _, device := range registry.DeviceDB().ListDevices() { + for _, glob := range patterns { + match, err := filepath.Match(glob, device) + if err != nil { + return errors.Wrapf(err, "failed to match pattern %q against %q", + glob, device) + } + if match { + matches[device] = struct{}{} + } + } + } + for device := range matches { + devices = append(devices, device) + } + sort.Strings(devices) + + unresolved, err := registry.InjectDevices(ociSpec, devices...) + + if len(unresolved) > 0 { + fmt.Printf("Unresolved CDI devices:\n") + for idx, device := range unresolved { + fmt.Printf(" %d. %s\n", idx, device) + } + } + if err != nil { + return errors.Wrapf(err, "OCI device injection failed") + } + + fmt.Printf("Updated OCI Spec:\n") + fmt.Printf("%s", marshalObject(2, ociSpec, format)) + + return nil +} + +func cdiResolveDevices(ociSpecFiles ...string) error { + var ( + cache *cdi.Cache + ociSpec *oci.Spec + devices []string + unresolved []string + err error + ) + + if cache, err = cdi.NewCache(); err != nil { + return errors.Wrap(err, "failed to create CDI cache instance") + } + + for _, ociSpecFile := range ociSpecFiles { + ociSpec, err = readOCISpec(ociSpecFile) + if err != nil { + return err + } + + devices = collectCDIDevicesFromOCISpec(ociSpec) + + unresolved, err = cache.InjectDevices(ociSpec, devices...) + if len(unresolved) > 0 { + fmt.Printf("Unresolved CDI devices:\n") + for idx, device := range unresolved { + fmt.Printf(" %d. %s\n", idx, device) + } + } + if err != nil { + return errors.Wrapf(err, "failed to resolve devices for OCI Spec %q", ociSpecFile) + } + + format := chooseFormat(injectCfg.output, ociSpecFile) + fmt.Printf("%s", marshalObject(2, ociSpec, format)) + } + + return nil +} + +func collectCDIDevicesFromOCISpec(spec *oci.Spec) []string { + var ( + cdiDevs []string + ) + + if spec.Linux == nil || len(spec.Linux.Devices) == 0 { + return nil + } + + devices := spec.Linux.Devices + g := gen.NewFromSpec(spec) + g.ClearLinuxDevices() + + for _, d := range devices { + if !cdi.IsQualifiedName(d.Path) { + g.AddDevice(d) + continue + } + cdiDevs = append(cdiDevs, d.Path) + } + + return cdiDevs +} + +func cdiListSpecs(verbose bool, format string, vendors ...string) { + var ( + registry = cdi.GetRegistry() + ) + + format = chooseFormat(format, "format-as.yaml") + + if len(vendors) == 0 { + vendors = registry.SpecDB().ListVendors() + } + + if len(vendors) == 0 { + fmt.Printf("No CDI Specs found.\n") + cdiErrors := registry.GetErrors() + if len(cdiErrors) > 0 { + for path, specErrors := range cdiErrors { + fmt.Printf("%s has errors:\n", path) + for idx, err := range specErrors { + fmt.Printf(" %d. %v\n", idx, err) + } + } + } + return + } + + fmt.Printf("CDI Specs found:\n") + for _, vendor := range registry.SpecDB().ListVendors() { + fmt.Printf("Vendor %s:\n", vendor) + for _, spec := range registry.SpecDB().GetVendorSpecs(vendor) { + cdiPrintSpec(spec, verbose, format, 2) + cdiPrintSpecErrors(spec, verbose, 2) + } + } +} + +func cdiPrintSpec(spec *cdi.Spec, verbose bool, format string, level int) { + fmt.Printf("%sSpec File %s\n", indent(level), spec.GetPath()) + + if verbose { + fmt.Printf("%s", marshalObject(level+2, spec.Spec, format)) + } +} + +func cdiPrintSpecErrors(spec *cdi.Spec, verbose bool, level int) { + var ( + registry = cdi.GetRegistry() + cdiErrors = registry.GetErrors() + ) + + if len(cdiErrors) > 0 { + for path, specErrors := range cdiErrors { + if len(specErrors) == 0 { + continue + } + fmt.Printf("%s%s has %d errors:\n", indent(level), path, len(specErrors)) + for idx, err := range specErrors { + fmt.Printf("%s%d. %v\n", indent(level+2), idx, err) + } + } + } +} + +func cdiPrintRegistry(args ...string) { + if len(args) == 0 { + args = []string{"all"} + } + + for _, what := range args { + switch what { + case "vendors", "vendor": + cdiListVendors() + case "classes", "class": + cdiListClasses() + case "specs", "spec": + cdiListSpecs(monitorCfg.verbose, monitorCfg.output) + case "devices", "device": + cdiListDevices(monitorCfg.verbose, monitorCfg.output) + case "all": + cdiListVendors() + cdiListClasses() + cdiListSpecs(monitorCfg.verbose, monitorCfg.output) + cdiListDevices(monitorCfg.verbose, monitorCfg.output) + default: + fmt.Printf("Unrecognized CDI aspect/object %q... ignoring it\n", what) + } + } +} + +func cdiPrintRegistryErrors() { + var ( + registry = cdi.GetRegistry() + cdiErrors = registry.GetErrors() + ) + + if len(cdiErrors) == 0 { + return + } + + fmt.Printf("CDI Registry has errors:\n") + for path, specErrors := range cdiErrors { + fmt.Printf("Spec file %s:\n", path) + for idx, err := range specErrors { + fmt.Printf(" %d: %v", idx, err) + } + } +} diff --git a/cmd/cdi/cmd/classes.go b/cmd/cdi/cmd/classes.go new file mode 100644 index 0000000..ec7bd3b --- /dev/null +++ b/cmd/cdi/cmd/classes.go @@ -0,0 +1,35 @@ +/* + 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 cmd + +import ( + "github.com/spf13/cobra" +) + +// classesCmd is our command for listing device classes in the registry. +var classesCmd = &cobra.Command{ + Use: "classes", + Short: "List CDI device classes", + Long: `List CDI device classes found in the registry.`, + Run: func(cmd *cobra.Command, args []string) { + cdiListClasses() + }, +} + +func init() { + rootCmd.AddCommand(classesCmd) +} diff --git a/cmd/cdi/cmd/devices.go b/cmd/cdi/cmd/devices.go new file mode 100644 index 0000000..f58daea --- /dev/null +++ b/cmd/cdi/cmd/devices.go @@ -0,0 +1,50 @@ +/* + 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 cmd + +import ( + "github.com/spf13/cobra" +) + +type devicesFlags struct { + verbose bool + output string +} + +// devicesCmd is our command for listing devices found in the CDI registry. +var devicesCmd = &cobra.Command{ + Aliases: []string{"devs", "dev"}, + Use: "devices", + Short: "List devices in the CDI registry", + Long: ` +The 'devices' command lists devices found in the CDI registry.`, + Run: func(cmd *cobra.Command, args []string) { + cdiListDevices(devicesCfg.verbose, devicesCfg.output) + }, +} + +var ( + devicesCfg devicesFlags +) + +func init() { + rootCmd.AddCommand(devicesCmd) + devicesCmd.Flags().BoolVarP(&devicesCfg.verbose, + "verbose", "v", false, "list CDI Spec details") + devicesCmd.Flags().StringVarP(&devicesCfg.output, + "output", "o", "", "output format for details (json|yaml)") +} diff --git a/cmd/cdi/cmd/dirs.go b/cmd/cdi/cmd/dirs.go new file mode 100644 index 0000000..13b1ee4 --- /dev/null +++ b/cmd/cdi/cmd/dirs.go @@ -0,0 +1,41 @@ +/* + 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 cmd + +import ( + "github.com/spf13/cobra" +) + +// dirsCmd is our command for listing CDI Spec directories in use. +var dirsCmd = &cobra.Command{ + Use: "dirs", + Short: "Show CDI Spec directories in use", + Long: ` +Show which directories are used by the registry to discover and +load CDI Specs. The later an entry is in the list the higher its +priority. This priority is inherited by Spec files loaded from +the directory and is used to resolve device conflicts. If there +are multiple definitions for a CDI device, the Spec file with +the highest priority takes precedence over the others.`, + Run: func(cmd *cobra.Command, args []string) { + cdiShowSpecDirs() + }, +} + +func init() { + rootCmd.AddCommand(dirsCmd) +} diff --git a/cmd/cdi/cmd/format.go b/cmd/cdi/cmd/format.go new file mode 100644 index 0000000..3372696 --- /dev/null +++ b/cmd/cdi/cmd/format.go @@ -0,0 +1,66 @@ +/* + 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 cmd + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + + "sigs.k8s.io/yaml" +) + +func chooseFormat(format string, path string) string { + if format == "" { + if ext := filepath.Ext(path); ext == ".json" || ext == ".yaml" { + format = ext[1:] + } else { + format = "yaml" + } + } + return format +} + +func marshalObject(level int, obj interface{}, format string) string { + var ( + raw []byte + err error + out string + ) + + if format == "json" { + raw, err = json.MarshalIndent(obj, "", " ") + } else { + raw, err = yaml.Marshal(obj) + } + + if err != nil { + return fmt.Sprintf("%s ", + Short: "Inject CDI devices into an OCI Spec", + Long: ` +The 'inject' command reads an OCI Spec from a file (use "-" for stdin), +injects a requested set of CDI devices into it and dumps the resulting +updated OCI Spec.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + fmt.Printf("OCI Spec argument and devices expected\n") + os.Exit(1) + } + + ociSpec, err := readOCISpec(args[0]) + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } + if err := cdiInjectDevices(injectCfg.output, ociSpec, args[1:]); err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } + }, +} + +func readOCISpec(path string) (*oci.Spec, error) { + var ( + spec *oci.Spec + data []byte + err error + ) + + if path == "-" { + data, err = ioutil.ReadAll(os.Stdin) + } else { + data, err = ioutil.ReadFile(path) + } + + if err != nil { + return nil, errors.Wrapf(err, "failed to read OCI Spec (%q)", path) + } + + spec = &oci.Spec{} + if err = yaml.Unmarshal(data, spec); err != nil { + return nil, errors.Wrapf(err, "failed to parse OCI Spec (%q)", path) + } + + return spec, nil +} + +var ( + injectCfg injectFlags +) + +func init() { + rootCmd.AddCommand(injectCmd) + injectCmd.Flags().StringVarP(&injectCfg.output, + "output", "o", "", "output format for OCI Spec (json|yaml)") +} diff --git a/cmd/cdi/cmd/monitor.go b/cmd/cdi/cmd/monitor.go new file mode 100644 index 0000000..b2b466a --- /dev/null +++ b/cmd/cdi/cmd/monitor.go @@ -0,0 +1,169 @@ +/* + 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 cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "gopkg.in/fsnotify.v1" + + cdi "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" +) + +type monitorFlags struct { + verbose bool + output string +} + +// monitorCmd is our command for monitoring CDI Spec refreshes. +var monitorCmd = &cobra.Command{ + Use: "monitor [specs] [vendors] [classes] [devices] [all]", + Short: "Monitor CDI Spec directories and refresh on changes", + Long: ` +The 'monitor' command monitors the CDI Spec directories and refreshes +the cache upon changes. The arguments passed to monitor control what +information to show upon each refresh.`, + Run: func(cmd *cobra.Command, args []string) { + monitorSpecDirs(args...) + }, +} + +func monitorSpecDirs(args ...string) { + var ( + registry = cdi.GetRegistry() + specDirs = registry.GetSpecDirectories() + dirWatch *fsnotify.Watcher + err error + done chan error + ) + + dirWatch, err = monitorDirectories(specDirs...) + if err != nil { + fmt.Printf("failed to set up CDI Spec dir monitoring: %v\n", err) + os.Exit(1) + } + + for _, dir := range specDirs { + if _, err = os.Stat(dir); err != nil { + if !os.IsNotExist(err) { + fmt.Printf("failed to stat CDI Spec directory %s: %v\n", dir, err) + os.Exit(1) + } + fmt.Printf("WARNING: CDI Spec directory %s does not exist...\n", dir) + continue + } + + if err = dirWatch.Add(dir); err != nil { + fmt.Printf("failed to watch CDI directory %q: %v\n", dir, err) + os.Exit(1) + } + } + + done = make(chan error, 1) + + go func() { + if len(args) == 0 { + args = []string{"all"} + } + + cdiPrintRegistry(args...) + + for { + select { + case evt, ok := <-dirWatch.Events: + if !ok { + close(done) + return + } + + if evt.Op != fsnotify.Write && evt.Op != fsnotify.Remove { + continue + } + + name, ext := filepath.Base(evt.Name), filepath.Ext(evt.Name) + if ext != ".json" && ext != ".yaml" { + fmt.Printf("ignoring %s %q (not a CDI Spec)...\n", evt.Op, evt.Name) + continue + } + + if name != "" && (name[0] == '.' || name[0] == '#') { + fmt.Printf("ignoring probable editor temporary file %q...\n", evt.Name) + continue + } + + fmt.Printf("refreshing CDI registry (%s changed)...\n", evt.Name) + + if err = registry.Refresh(); err != nil { + fmt.Printf(" => refresh failed: %v\n", err) + } else { + fmt.Printf(" => refresh OK\n") + cdiPrintRegistry(args...) + } + + case err, ok := <-dirWatch.Errors: + if ok { + done <- err + } + return + } + } + }() + + err = <-done + if err != nil { + fmt.Printf("CDI Spec watch failed: %v\n", err) + os.Exit(1) + } +} + +func monitorDirectories(dirs ...string) (*fsnotify.Watcher, error) { + w, err := fsnotify.NewWatcher() + if err != nil { + return nil, errors.Wrapf(err, "failed to create directory watch for %s", + strings.Join(dirs, ",")) + } + + for _, dir := range dirs { + if _, err = os.Stat(dir); err != nil { + fmt.Printf("WARNING: failed to stat dir %q, NOT watching it...", dir) + continue + } + + if err = w.Add(dir); err != nil { + return nil, errors.Wrapf(err, "failed to add %q to fsnotify watch", dir) + } + } + + return w, nil +} + +var ( + monitorCfg monitorFlags +) + +func init() { + rootCmd.AddCommand(monitorCmd) + monitorCmd.Flags().BoolVarP(&monitorCfg.verbose, + "verbose", "v", false, "print details") + monitorCmd.Flags().StringVarP(&monitorCfg.output, + "output", "o", "", "output format for details (json|yaml)") +} diff --git a/cmd/cdi/cmd/resolve.go b/cmd/cdi/cmd/resolve.go new file mode 100644 index 0000000..7872e73 --- /dev/null +++ b/cmd/cdi/cmd/resolve.go @@ -0,0 +1,92 @@ +/* + 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 cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +type resolveFlags struct { + output string +} + +// resolveCmd is our command for resolving CDI devices present in an OCI Spec. +var resolveCmd = &cobra.Command{ + Aliases: []string{"res"}, + Use: "resolve", + Short: "Resolve CDI devices present in an OCI Spec", + Long: ` +The 'resolve' command takes an OCI Spec file (use "-" for stdin), +resolves any CDI Devices present in the Spec and dumps the result.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + fmt.Printf("OCI Spec argument(s) expected\n") + os.Exit(1) + } + if err := cdiResolveDevices(args...); err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } + }, +} + +/* +func resolveDevices(ociSpecFiles ...string) error { + for _, ociSpecFile := range ociSpecFiles { + ociSpec, err := readOCISpec(ociSpecFile) + if err != nil { + return err + } + + resolved, err := cdi.ResolveDevices(ociSpec) + if err != nil { + return errors.Wrapf(err, "CDI device resolution faile in %q", + ociSpecFile) + } + + output := injectCfg.output + if output == "" { + if filepath.Ext(ociSpecFile) == ".json" { + output = "json" + } else { + output = "yaml" + } + } + + if resolved != nil { + fmt.Printf("OCI Spec %q: resolved devices %q\n", ociSpecFile, + strings.Join(resolved, ", ")) + fmt.Printf("%s", marshalObject(2, ociSpec, output)) + } + } + + return nil +} +*/ + +var ( + resolveCfg resolveFlags +) + +func init() { + rootCmd.AddCommand(resolveCmd) + resolveCmd.Flags().StringVarP(&injectCfg.output, + "output", "o", "", "output format for OCI Spec (json|yaml)") +} diff --git a/cmd/cdi/cmd/root.go b/cmd/cdi/cmd/root.go new file mode 100644 index 0000000..01f868e --- /dev/null +++ b/cmd/cdi/cmd/root.go @@ -0,0 +1,60 @@ +/* + 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 cmd + +import ( + "github.com/spf13/cobra" + + "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" +) + +var specDirs []string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "cdi", + Short: "Inpsect and interact with the CDI Registry", + Long: ` +The 'cdi' utility allows you to inspect and interact with the +CDI Registry. Various commands are available for listing CDI +Spec files, vendors, classes, devices, validating the content +of the registry, injecting devices into OCI Specs, and for +monitoring changes in the Registry. + +See cdi --help for a list of available commands. You can get +additional help about by using 'cdi -h'.`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + cobra.CheckErr(rootCmd.Execute()) +} + +func init() { + cobra.OnInitialize(initSpecDirs) + rootCmd.PersistentFlags().StringSliceVarP(&specDirs, "spec-dirs", "d", nil, "directories to scan for CDI Spec files") +} + +func initSpecDirs() { + if len(specDirs) > 0 { + cdi.GetRegistry(cdi.WithSpecDirs(specDirs...)) + if len(cdi.GetRegistry().GetErrors()) > 0 { + cdiPrintRegistryErrors() + } + } +} diff --git a/cmd/cdi/cmd/specs.go b/cmd/cdi/cmd/specs.go new file mode 100644 index 0000000..2bff6eb --- /dev/null +++ b/cmd/cdi/cmd/specs.go @@ -0,0 +1,57 @@ +/* + 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 cmd + +import ( + "fmt" + "strings" + + "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" + "github.com/spf13/cobra" +) + +type specFlags struct { + verbose bool + output string +} + +// specsCmd is our command for listing Spec files. +var specsCmd = &cobra.Command{ + Use: "specs [vendor-list]", + Short: "List available CDI Specs", + Long: fmt.Sprintf(` +The 'specs' command lists all CDI Specs present in the registry. +If a vendor list is given, only CDI Specs by the given vendors are +listed. The CDI Specs are discovered and loaded to the registry +from CDI Spec directories. The default CDI Spec directories are: + %s.`, strings.Join(cdi.DefaultSpecDirs, ", ")), + Run: func(cmd *cobra.Command, vendors []string) { + cdiListSpecs(specCfg.verbose, specCfg.output, vendors...) + }, +} + +var ( + specCfg specFlags +) + +func init() { + rootCmd.AddCommand(specsCmd) + specsCmd.Flags().BoolVarP(&specCfg.verbose, + "verbose", "v", false, "list CDI Spec details") + specsCmd.Flags().StringVarP(&specCfg.output, + "output", "o", "", "output format for details (json|yaml)") +} diff --git a/cmd/cdi/cmd/validate.go b/cmd/cdi/cmd/validate.go new file mode 100644 index 0000000..e18de9f --- /dev/null +++ b/cmd/cdi/cmd/validate.go @@ -0,0 +1,52 @@ +/* + 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 cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" +) + +// validateCmd is our CDI command for validating CDI Spec files in the registry. +var validateCmd = &cobra.Command{ + Use: "validate", + Short: "List CDI registry errors", + Long: ` +The 'validate' command lists errors encountered during the population +of the CDI registry. It exits with an exit status of 1 if any errors +were reported by the registry.`, + Run: func(cmd *cobra.Command, args []string) { + cdiErrors := cdi.GetRegistry().GetErrors() + if len(cdiErrors) == 0 { + fmt.Printf("No CDI Registry errors.\n") + return + } + + fmt.Printf("CDI Registry has errors:\n") + cdiPrintRegistryErrors() + + os.Exit(1) + }, +} + +func init() { + rootCmd.AddCommand(validateCmd) +} diff --git a/cmd/cdi/cmd/vendors.go b/cmd/cdi/cmd/vendors.go new file mode 100644 index 0000000..c82bec3 --- /dev/null +++ b/cmd/cdi/cmd/vendors.go @@ -0,0 +1,35 @@ +/* + 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 cmd + +import ( + "github.com/spf13/cobra" +) + +// vendorsCmd is our command for listing vendors. +var vendorsCmd = &cobra.Command{ + Use: "vendors", + Short: "List vendors", + Long: `List vendors with CDI Specs in the registry.`, + Run: func(cmd *cobra.Command, args []string) { + cdiListVendors() + }, +} + +func init() { + rootCmd.AddCommand(vendorsCmd) +} diff --git a/cmd/cdi/main.go b/cmd/cdi/main.go new file mode 100644 index 0000000..a18ed4c --- /dev/null +++ b/cmd/cdi/main.go @@ -0,0 +1,23 @@ +/* + 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 main + +import "github.com/container-orchestrated-devices/container-device-interface/cmd/cdi/cmd" + +func main() { + cmd.Execute() +} From 77139233d7a06fac97fef60874fe5dee19d22a31 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Mon, 22 Nov 2021 22:53:22 +0200 Subject: [PATCH 08/11] go.{mod,sum}: update dependencies. Signed-off-by: Krisztian Litkey --- go.mod | 22 ++- go.sum | 612 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 633 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e557ba0..62ff624 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,28 @@ module github.com/container-orchestrated-devices/container-device-interface go 1.14 require ( + github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/coreos/etcd v3.3.10+incompatible // indirect + github.com/coreos/go-etcd v2.0.0+incompatible // indirect + github.com/cpuguy83/go-md2man v1.0.10 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/opencontainers/runtime-spec v1.0.2 - github.com/stretchr/testify v1.3.0 + github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e // indirect + github.com/opencontainers/selinux v1.10.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/cobra v1.2.1 // indirect + github.com/spf13/viper v1.8.1 // indirect + github.com/stretchr/testify v1.7.0 + github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect + github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect github.com/xeipuuv/gojsonschema v1.2.0 + github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect + gopkg.in/fsnotify.v1 v1.4.7 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 626a83b..6c2c894 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,629 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e h1:2Tg49TNXSTIsX8AAtmo1aQ1IbfnoUFzkOp7p2iWygtc= +github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK5zsQavY8NPMkU= +github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777 h1:wejkGHRTr38uaKRqECZlsCsJ1/TGxIyFbH32x5zUdu4= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From 14f4076c59a399536270b7e045a9a98601bc06a0 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 8 Dec 2021 15:54:03 +0200 Subject: [PATCH 09/11] pkg/cdi: add package level documentation. Add a package-level documentation/overview. Signed-off-by: Krisztian Litkey --- pkg/cdi/doc.go | 130 +++++++++++++++++++++++++++++++++++++++++++ pkg/cdi/spec-dirs.go | 8 ++- 2 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 pkg/cdi/doc.go diff --git a/pkg/cdi/doc.go b/pkg/cdi/doc.go new file mode 100644 index 0000000..4fcdc44 --- /dev/null +++ b/pkg/cdi/doc.go @@ -0,0 +1,130 @@ +// Package cdi has the primary purpose of providing an API for +// interacting with CDI and consuming CDI devices. +// +// For more information about Container Device Interface, please refer to +// https://github.com/container-orchestrated-devices/container-device-interface +// +// Container Device Interface +// +// Container Device Interface, or CDI for short, provides comprehensive +// third party device support for container runtimes. CDI uses vendor +// provided specification files, CDI Specs for short, to describe how a +// container's runtime environment should be modified when one or more +// of the vendor-specific devices is injected into the container. Beyond +// describing the low level platform-specific details of how to gain +// basic access to a device, CDI Specs allow more fine-grained device +// initialization, and the automatic injection of any necessary vendor- +// or device-specific software that might be required for a container +// to use a device or take full advantage of it. +// +// In the CDI device model containers request access to a device using +// fully qualified device names, qualified names for short, consisting of +// a vendor identifier, a device class and a device name or identifier. +// These pieces of information together uniquely identify a device among +// all device vendors, classes and device instances. +// +// This package implements an API for easy consumption of CDI. The API +// implements discovery, loading and caching of CDI Specs and injection +// of CDI devices into containers. This is the most common functionality +// the vast majority of CDI consumers need. The API should be usable both +// by OCI runtime clients and runtime implementations. +// +// CDI Registry +// +// The primary interface to interact with CDI devices is the Registry. It +// is essentially a cache of all Specs and devices discovered in standard +// CDI directories on the host. The registry has two main functionality, +// injecting devices into an OCI Spec and refreshing the cache of CDI +// Specs and devices. +// +// Device Injection +// +// Using the Registry one can inject CDI devices into a container with code +// similar to the following snippet: +// +// import ( +// "fmt" +// "strings" +// +// "github.com/pkg/errors" +// log "github.com/sirupsen/logrus" +// +// "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" +// oci "github.com/opencontainers/runtime-spec/specs-go" +// ) +// +// func injectCDIDevices(spec *oci.Spec, devices []string) error { +// log.Debug("pristine OCI Spec: %s", dumpSpec(spec)) +// +// unresolved, err := cdi.GetRegistry().InjectDevices(spec, devices) +// if err != nil { +// return errors.Wrap(err, "CDI device injection failed") +// } +// +// log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec)) +// return nil +// } +// +// Cache Refresh +// +// In a runtime implementation one typically wants to make sure the +// CDI Spec cache is up to date before performing device injection. +// A code snippet similar to the following accmplishes that: +// +// import ( +// "fmt" +// "strings" +// +// "github.com/pkg/errors" +// log "github.com/sirupsen/logrus" +// +// "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" +// oci "github.com/opencontainers/runtime-spec/specs-go" +// ) +// +// func injectCDIDevices(spec *oci.Spec, devices []string) error { +// registry := cdi.GetRegistry() +// +// if err := registry.Refresh(); err != nil { +// // Note: +// // It is up to the implementation to decide whether +// // to abort injection on errors. A failed Refresh() +// // does not necessarily render the registry unusable. +// // For instance, a parse error in a Spec file for +// // vendor A does not have any effect on devices of +// // vendor B... +// log.Warnf("pre-injection Refresh() failed: %v", err) +// } +// +// log.Debug("pristine OCI Spec: %s", dumpSpec(spec)) +// +// unresolved, err := registry.InjectDevices(spec, devices) +// if err != nil { +// return errors.Wrap(err, "CDI device injection failed") +// } +// +// log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec)) +// return nil +// } +// +// Generated Spec Files, Multiple Directories, Device Precedence +// +// There are systems where the set of available or usable CDI devices +// changes dynamically and this needs to be reflected in the CDI Specs. +// This is done by dynamically regenerating CDI Spec files which are +// affected by these changes. +// +// CDI can collect Spec files from multiple directories. Spec files are +// automatically assigned priorities according to which directory they +// were loaded from. The later a directory occurs in the list of CDI +// directories to scan, the higher priority Spec files loaded from that +// directory are assigned to. When two or more Spec files define the +// same device, conflict is resolved by chosing the definition from the +// Spec file with the highest priority. +// +// The default CDI directory configuration is chosen to encourage +// separating dynamically generated CDI Spec files from static ones. +// The default directories are '/etc/cdi' and '/var/run/cdi'. By putting +// dynamically generated Spec files under '/var/run/cdi', those take +// precedence over static ones in '/etc/cdi'. +package cdi diff --git a/pkg/cdi/spec-dirs.go b/pkg/cdi/spec-dirs.go index 511a451..ad017fe 100644 --- a/pkg/cdi/spec-dirs.go +++ b/pkg/cdi/spec-dirs.go @@ -32,9 +32,11 @@ const ( var ( // DefaultSpecDirs is the default Spec directory configuration. - // Altering this variable changes the defaults for the package. - // The new defaults only affect Cache instances created after - // the change. + // 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") From b093549d7f10f69e86c6383f0dcd74aaa7c256a9 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Sun, 28 Nov 2021 19:26:20 +0200 Subject: [PATCH 10/11] Makefile: add simple Makefile. Signed-off-by: Krisztian Litkey --- Makefile | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eda243a --- /dev/null +++ b/Makefile @@ -0,0 +1,107 @@ +GO_CMD := go +GO_BUILD := $(GO_CMD) build +GO_TEST := $(GO_CMD) test -v -cover + +GO_LINT := golint -set_exit_status +GO_FMT := gofmt +GO_VET := $(GO_CMD) vet + +CDI_PKG := $(shell grep ^module go.mod | sed 's/^module *//g') +GO_MODS := $(shell $(GO_CMD) list ./...) +GO_PKGS := $(shell $(GO_CMD) list ./... | grep -v cmd/ | sed 's:$(CDI_PKG):.:g') + +BINARIES := bin/cdi + +ifneq ($(V),1) + Q := @ +endif + + +# +# top-level targets +# + +all: build + +build: $(BINARIES) + +clean: clean-binaries clean-schema + +test: test-gopkgs test-schema + +# +# targets for running test prior to filing a PR +# + +pre-pr-checks pr-checks: test fmt lint vet + + +fmt format: + $(Q)report=$$($(GO_FMT) -s -d -w $$(find . -name *.go)); \ + if [ -n "$$report" ]; then \ + echo "$$report"; \ + exit 1; \ + fi + +lint: + $(Q)status=0; for f in $$(find . -name \*.go); do \ + $(GO_LINT) $$f || status=1; \ + done; \ + exit $$status + +vet: + $(Q)$(GO_VET) $(GO_MODS) + +# +# build targets +# + +bin/%: + $(Q)echo "Building $@..."; \ + $(GO_BUILD) -o $@ ./$(subst bin/,cmd/,$@) + +# +# cleanup targets +# + +# clean up binaries +clean-binaries: + $(Q) rm -f $(BINARIES) + +# clean up schema validator +clean-schema: + $(Q)rm -f schema/validate + +# +# test targets +# + +# tests for go packages +test-gopkgs: + $(Q)status=0; for pkg in $(GO_PKGS); do \ + $(GO_TEST) $$pkg; \ + if [ $$? != 0 ]; then \ + echo "*** Test FAILED for package $$pkg."; \ + status=1; \ + fi; \ + done; \ + exit $$status + +# tests for CDI Spec JSON schema +test-schema: + $(Q)echo "Building in schema..."; \ + $(MAKE) -C schema test + + +# +# dependencies +# + +# quasi-automatic dependency for bin/cdi +bin/cdi: $(wildcard cmd/cdi/*.go cmd/cdi/cmd/*.go) $(shell \ + for dir in \ + $$($(GO_CMD) list -f '{{ join .Deps "\n"}}' ./cmd/cdi/... | \ + grep $(CDI_PKG)/pkg/ | \ + sed 's:$(CDI_PKG):.:g'); do \ + find $$dir -name \*.go; \ + done | sort | uniq) From 20f04d71a3b6507e1cd24e459077fc79933c8438 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Mon, 29 Nov 2021 18:39:15 +0200 Subject: [PATCH 11/11] .github: update sanity check workflow. Signed-off-by: Krisztian Litkey --- .github/workflows/sanity.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml index 871d11f..b57fdc5 100644 --- a/.github/workflows/sanity.yml +++ b/.github/workflows/sanity.yml @@ -12,8 +12,8 @@ jobs: - name: Install golint run: go get -u golang.org/x/lint/golint - name: Lint - run: golint -set_exit_status ./specs-go/*.go ./pkg/* + run: make lint - name: Fmt - run: test -z "$(gofmt -l ./specs-go/config.go)" + run: make fmt - name: Vet - run: go vet ./specs-go + run: make vet