Commit d7e13eb9 authored by songlinfeng's avatar songlinfeng
Browse files

add dtk-container-toolkit

parent fcdba4f3
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package transform
import "tags.cncf.io/container-device-interface/specs-go"
// Transformer defines the API for applying arbitrary transforms to a spec in-place
type Transformer interface {
Transform(*specs.Spec) error
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package transform
import (
"tags.cncf.io/container-device-interface/specs-go"
)
type dedupe struct{}
var _ Transformer = (*dedupe)(nil)
// NewDedupe creates a transformer that deduplicates container edits.
func NewDedupe() (Transformer, error) {
return &dedupe{}, nil
}
// Transform removes duplicate entris from devices and common container edits.
func (d dedupe) Transform(spec *specs.Spec) error {
if spec == nil {
return nil
}
if err := d.transformEdits(&spec.ContainerEdits); err != nil {
return err
}
var updatedDevices []specs.Device
for _, device := range spec.Devices {
device := device
if err := d.transformEdits(&device.ContainerEdits); err != nil {
return err
}
updatedDevices = append(updatedDevices, device)
}
spec.Devices = updatedDevices
return nil
}
func (d dedupe) transformEdits(edits *specs.ContainerEdits) error {
deviceNodes, err := d.deduplicateDeviceNodes(edits.DeviceNodes)
if err != nil {
return err
}
edits.DeviceNodes = deviceNodes
envs, err := d.deduplicateEnvs(edits.Env)
if err != nil {
return err
}
edits.Env = envs
hooks, err := d.deduplicateHooks(edits.Hooks)
if err != nil {
return err
}
edits.Hooks = hooks
mounts, err := d.deduplicateMounts(edits.Mounts)
if err != nil {
return err
}
edits.Mounts = mounts
return nil
}
func (d dedupe) deduplicateDeviceNodes(entities []*specs.DeviceNode) ([]*specs.DeviceNode, error) {
seen := make(map[string]bool)
var deviceNodes []*specs.DeviceNode
for _, e := range entities {
if e == nil {
continue
}
id, err := deviceNode(*e).id()
if err != nil {
return nil, err
}
if seen[id] {
continue
}
seen[id] = true
deviceNodes = append(deviceNodes, e)
}
return deviceNodes, nil
}
func (d dedupe) deduplicateEnvs(entities []string) ([]string, error) {
seen := make(map[string]bool)
var envs []string
for _, e := range entities {
id := e
if seen[id] {
continue
}
seen[id] = true
envs = append(envs, e)
}
return envs, nil
}
func (d dedupe) deduplicateHooks(entities []*specs.Hook) ([]*specs.Hook, error) {
seen := make(map[string]bool)
var hooks []*specs.Hook
for _, e := range entities {
if e == nil {
continue
}
id, err := hook(*e).id()
if err != nil {
return nil, err
}
if seen[id] {
continue
}
seen[id] = true
hooks = append(hooks, e)
}
return hooks, nil
}
func (d dedupe) deduplicateMounts(entities []*specs.Mount) ([]*specs.Mount, error) {
seen := make(map[string]bool)
var mounts []*specs.Mount
for _, e := range entities {
if e == nil {
continue
}
id, err := mount(*e).id()
if err != nil {
return nil, err
}
if seen[id] {
continue
}
seen[id] = true
mounts = append(mounts, e)
}
return mounts, nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package transform
import (
"encoding/json"
"tags.cncf.io/container-device-interface/specs-go"
)
type containerEdits specs.ContainerEdits
// IsEmpty returns true if the edits are empty.
func (e containerEdits) IsEmpty() bool {
// Devices with empty edits are invalid
if len(e.DeviceNodes) > 0 {
return false
}
if len(e.Env) > 0 {
return false
}
if len(e.Hooks) > 0 {
return false
}
if len(e.Mounts) > 0 {
return false
}
return true
}
func (e *containerEdits) getEntityIds() ([]string, error) {
if e == nil {
return nil, nil
}
uniqueIDs := make(map[string]bool)
deviceNodes, err := e.getDeviceNodeIDs()
if err != nil {
return nil, err
}
for k := range deviceNodes {
uniqueIDs[k] = true
}
envs, err := e.getEnvIDs()
if err != nil {
return nil, err
}
for k := range envs {
uniqueIDs[k] = true
}
hooks, err := e.getHookIDs()
if err != nil {
return nil, err
}
for k := range hooks {
uniqueIDs[k] = true
}
mounts, err := e.getMountIDs()
if err != nil {
return nil, err
}
for k := range mounts {
uniqueIDs[k] = true
}
var ids []string
for k := range uniqueIDs {
ids = append(ids, k)
}
return ids, nil
}
func (e *containerEdits) getDeviceNodeIDs() (map[string]bool, error) {
deviceIDs := make(map[string]bool)
for _, entity := range e.DeviceNodes {
id, err := deviceNode(*entity).id()
if err != nil {
return nil, err
}
deviceIDs[id] = true
}
return deviceIDs, nil
}
func (e *containerEdits) getEnvIDs() (map[string]bool, error) {
envIDs := make(map[string]bool)
for _, entity := range e.Env {
id, err := env(entity).id()
if err != nil {
return nil, err
}
envIDs[id] = true
}
return envIDs, nil
}
func (e *containerEdits) getHookIDs() (map[string]bool, error) {
hookIDs := make(map[string]bool)
for _, entity := range e.Hooks {
id, err := hook(*entity).id()
if err != nil {
return nil, err
}
hookIDs[id] = true
}
return hookIDs, nil
}
func (e *containerEdits) getMountIDs() (map[string]bool, error) {
mountIDs := make(map[string]bool)
for _, entity := range e.Mounts {
id, err := mount(*entity).id()
if err != nil {
return nil, err
}
mountIDs[id] = true
}
return mountIDs, nil
}
type deviceNode specs.DeviceNode
func (dn deviceNode) id() (string, error) {
b, err := json.Marshal(dn)
return string(b), err
}
type env string
func (e env) id() (string, error) {
return string(e), nil
}
type mount specs.Mount
func (m mount) id() (string, error) {
b, err := json.Marshal(m)
return string(b), err
}
type hook specs.Hook
func (m hook) id() (string, error) {
b, err := json.Marshal(m)
return string(b), err
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package transform
import "tags.cncf.io/container-device-interface/specs-go"
type merged []Transformer
// Merge creates a merged transofrmer from the specified transformers.
func Merge(transformers ...Transformer) Transformer {
return merged(transformers)
}
// Transform applies all the transformers in the merged set.
func (t merged) Transform(spec *specs.Spec) error {
for _, transformer := range t {
if err := transformer.Transform(spec); err != nil {
return err
}
}
return nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package transform
import (
"dtk-container-toolkit/internal/edits"
"fmt"
"tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/pkg/parser"
"tags.cncf.io/container-device-interface/specs-go"
)
const (
allDeviceName = "all"
)
type mergedDevice struct {
name string
skipIfExists bool
simplifier Transformer
}
var _ Transformer = (*mergedDevice)(nil)
// MergedDeviceOption is a function that configures a merged device
type MergedDeviceOption func(*mergedDevice)
// WithName sets the name of the merged device
func WithName(name string) MergedDeviceOption {
return func(m *mergedDevice) {
m.name = name
}
}
// WithSkipIfExists sets whether to skip adding the merged device if it already exists
func WithSkipIfExists(skipIfExists bool) MergedDeviceOption {
return func(m *mergedDevice) {
m.skipIfExists = skipIfExists
}
}
// NewMergedDevice creates a transformer with the specified options
func NewMergedDevice(opts ...MergedDeviceOption) (Transformer, error) {
m := &mergedDevice{}
for _, opt := range opts {
opt(m)
}
if m.name == "" {
m.name = allDeviceName
}
m.simplifier = NewSimplifier()
if err := parser.ValidateDeviceName(m.name); err != nil {
return nil, fmt.Errorf("invalid device name %q: %v", m.name, err)
}
return m, nil
}
// Transform adds a merged device to the spec
func (m mergedDevice) Transform(spec *specs.Spec) error {
if spec == nil {
return nil
}
mergedDevice, err := mergeDeviceSpecs(spec.Devices, m.name)
if err != nil {
return fmt.Errorf("failed to generate merged device %q: %v", m.name, err)
}
if mergedDevice == nil {
if m.skipIfExists {
return nil
}
return fmt.Errorf("device %q already exists", m.name)
}
spec.Devices = append(spec.Devices, *mergedDevice)
if err := m.simplifier.Transform(spec); err != nil {
return fmt.Errorf("failed to simplify spec after merging device %q: %v", m.name, err)
}
return nil
}
// mergeDeviceSpecs creates a device with the specified name which combines the edits from the previous devices.
// If a device of the specified name already exists, no device is created and nil is returned.
func mergeDeviceSpecs(deviceSpecs []specs.Device, mergedDeviceName string) (*specs.Device, error) {
for _, d := range deviceSpecs {
if d.Name == mergedDeviceName {
return nil, nil
}
}
mergedEdits := edits.NewContainerEdits()
for _, d := range deviceSpecs {
d := d
edit := cdi.ContainerEdits{
ContainerEdits: &d.ContainerEdits,
}
mergedEdits.Append(&edit)
}
merged := specs.Device{
Name: mergedDeviceName,
ContainerEdits: *mergedEdits.ContainerEdits,
}
return &merged, nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package noop
import (
"dtk-container-toolkit/pkg/c3000cdi/transform"
"tags.cncf.io/container-device-interface/specs-go"
)
type noop struct{}
var _ transform.Transformer = (*noop)(nil)
// New returns a no-op transformer.
func New() transform.Transformer {
return noop{}
}
// Transform is a no-op for a noop transformer.
func (n noop) Transform(spec *specs.Spec) error {
return nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package transform
import (
"fmt"
"tags.cncf.io/container-device-interface/specs-go"
)
type remove map[string]bool
func newRemover(ids ...string) Transformer {
r := make(remove)
for _, id := range ids {
r[id] = true
}
return r
}
// Transform remove the specified entities from the spec.
func (r remove) Transform(spec *specs.Spec) error {
if spec == nil {
return nil
}
for _, device := range spec.Devices {
device := device
if err := r.transformEdits(&device.ContainerEdits); err != nil {
return fmt.Errorf("failed to remove edits from device %q: %w", device.Name, err)
}
}
return r.transformEdits(&spec.ContainerEdits)
}
func (r remove) transformEdits(edits *specs.ContainerEdits) error {
if edits == nil {
return nil
}
var deviceNodes []*specs.DeviceNode
for _, entity := range edits.DeviceNodes {
id, err := deviceNode(*entity).id()
if err != nil {
return err
}
if r[id] {
continue
}
deviceNodes = append(deviceNodes, entity)
}
edits.DeviceNodes = deviceNodes
var envs []string
for _, entity := range edits.Env {
id := entity
if r[id] {
continue
}
envs = append(envs, entity)
}
edits.Env = envs
var hooks []*specs.Hook
for _, entity := range edits.Hooks {
id, err := hook(*entity).id()
if err != nil {
return err
}
if r[id] {
continue
}
hooks = append(hooks, entity)
}
edits.Hooks = hooks
var mounts []*specs.Mount
for _, entity := range edits.Mounts {
id, err := mount(*entity).id()
if err != nil {
return err
}
if r[id] {
continue
}
mounts = append(mounts, entity)
}
edits.Mounts = mounts
return nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package root
import (
"dtk-container-toolkit/pkg/c3000cdi/transform"
"dtk-container-toolkit/pkg/c3000cdi/transform/noop"
)
type builder struct {
transformer
relativeTo string
}
func (b *builder) build() transform.Transformer {
if b.root == b.targetRoot {
return noop.New()
}
if b.relativeTo == "container" {
return containerRootTransformer(b.transformer)
}
return hostRootTransformer(b.transformer)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package root
import (
"dtk-container-toolkit/pkg/c3000cdi/transform"
"fmt"
"strings"
"tags.cncf.io/container-device-interface/specs-go"
)
// containerRootTransformer transforms the roots of container paths in a CDI spec.
type containerRootTransformer transformer
var _ transform.Transformer = (*containerRootTransformer)(nil)
// Transform replaces the root in a spec with a new root.
// It walks the spec and replaces all container paths that start with root with the target root.
func (t containerRootTransformer) Transform(spec *specs.Spec) error {
if spec == nil {
return nil
}
for _, d := range spec.Devices {
d := d
if err := t.applyToEdits(&d.ContainerEdits); err != nil {
return fmt.Errorf("failed to apply root transform to device %s: %w", d.Name, err)
}
}
if err := t.applyToEdits(&spec.ContainerEdits); err != nil {
return fmt.Errorf("failed to apply root transform to spec: %w", err)
}
return nil
}
func (t containerRootTransformer) applyToEdits(edits *specs.ContainerEdits) error {
for i, dn := range edits.DeviceNodes {
edits.DeviceNodes[i] = t.transformDeviceNode(dn)
}
for i, hook := range edits.Hooks {
edits.Hooks[i] = t.transformHook(hook)
}
for i, mount := range edits.Mounts {
edits.Mounts[i] = t.transformMount(mount)
}
return nil
}
func (t containerRootTransformer) transformDeviceNode(dn *specs.DeviceNode) *specs.DeviceNode {
dn.Path = t.transformPath(dn.Path)
return dn
}
func (t containerRootTransformer) transformHook(hook *specs.Hook) *specs.Hook {
// The Path in the startContainer hook MUST resolve in the container namespace.
if hook.HookName == "startContainer" {
hook.Path = t.transformPath(hook.Path)
}
// The createContainer and startContainer hooks MUST execute in the container namespace.
if hook.HookName != "createContainer" && hook.HookName != "startContainer" {
return hook
}
var args []string
for _, arg := range hook.Args {
if !strings.Contains(arg, "::") {
args = append(args, t.transformPath(arg))
continue
}
// For the 'create-symlinks' hook, special care is taken for the
// '--link' flag argument which takes the form <target>::<link>.
// Both paths, the target and link paths, are transformed.
split := strings.SplitN(arg, "::", 2)
split[0] = t.transformPath(split[0])
split[1] = t.transformPath(split[1])
args = append(args, strings.Join(split, "::"))
}
hook.Args = args
return hook
}
func (t containerRootTransformer) transformMount(mount *specs.Mount) *specs.Mount {
mount.ContainerPath = t.transformPath(mount.ContainerPath)
return mount
}
func (t containerRootTransformer) transformPath(path string) string {
return (transformer)(t).transformPath(path)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package root
import (
"dtk-container-toolkit/pkg/c3000cdi/transform"
"fmt"
"strings"
"tags.cncf.io/container-device-interface/specs-go"
)
// hostRootTransformer transforms the roots of host paths in a CDI spec.
type hostRootTransformer transformer
var _ transform.Transformer = (*hostRootTransformer)(nil)
// Transform replaces the root in a spec with a new root.
// It walks the spec and replaces all host paths that start with root with the target root.
func (t hostRootTransformer) Transform(spec *specs.Spec) error {
if spec == nil {
return nil
}
for _, d := range spec.Devices {
d := d
if err := t.applyToEdits(&d.ContainerEdits); err != nil {
return fmt.Errorf("failed to apply root transform to device %s: %w", d.Name, err)
}
}
if err := t.applyToEdits(&spec.ContainerEdits); err != nil {
return fmt.Errorf("failed to apply root transform to spec: %w", err)
}
return nil
}
func (t hostRootTransformer) applyToEdits(edits *specs.ContainerEdits) error {
for i, dn := range edits.DeviceNodes {
edits.DeviceNodes[i] = t.transformDeviceNode(dn)
}
for i, hook := range edits.Hooks {
edits.Hooks[i] = t.transformHook(hook)
}
for i, mount := range edits.Mounts {
edits.Mounts[i] = t.transformMount(mount)
}
return nil
}
func (t hostRootTransformer) transformDeviceNode(dn *specs.DeviceNode) *specs.DeviceNode {
if dn.HostPath == "" {
dn.HostPath = dn.Path
}
dn.HostPath = t.transformPath(dn.HostPath)
return dn
}
func (t hostRootTransformer) transformHook(hook *specs.Hook) *specs.Hook {
// The Path in the startContainer hook MUST resolve in the container namespace.
if hook.HookName != "startContainer" {
hook.Path = t.transformPath(hook.Path)
}
// The createContainer and startContainer hooks MUST execute in the container namespace.
if hook.HookName == "createContainer" || hook.HookName == "startContainer" {
return hook
}
var args []string
for _, arg := range hook.Args {
if !strings.Contains(arg, "::") {
args = append(args, t.transformPath(arg))
continue
}
// For the 'create-symlinks' hook, special care is taken for the
// '--link' flag argument which takes the form <target>::<link>.
// Both paths, the target and link paths, are transformed.
split := strings.SplitN(arg, "::", 2)
split[0] = t.transformPath(split[0])
split[1] = t.transformPath(split[1])
args = append(args, strings.Join(split, "::"))
}
hook.Args = args
return hook
}
func (t hostRootTransformer) transformMount(mount *specs.Mount) *specs.Mount {
mount.HostPath = t.transformPath(mount.HostPath)
return mount
}
func (t hostRootTransformer) transformPath(path string) string {
return (transformer)(t).transformPath(path)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package root
// Option defines a functional option for configuring a transormer.
type Option func(*builder)
// WithRoot sets the (from) root for the root transformer.
func WithRoot(root string) Option {
return func(b *builder) {
b.root = root
}
}
// WithTargetRoot sets the (to) target root for the root transformer.
func WithTargetRoot(root string) Option {
return func(b *builder) {
b.targetRoot = root
}
}
// WithRelativeTo sets whether the specified root is relative to the host or container.
func WithRelativeTo(relativeTo string) Option {
return func(b *builder) {
b.relativeTo = relativeTo
}
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package root
import (
"dtk-container-toolkit/pkg/c3000cdi/transform"
"path/filepath"
"strings"
)
// transformer transforms roots of paths.
type transformer struct {
root string
targetRoot string
}
// New creates a root transformer using the specified options.
func New(opts ...Option) transform.Transformer {
b := &builder{}
for _, opt := range opts {
opt(b)
}
return b.build()
}
func (t transformer) transformPath(path string) string {
if !strings.HasPrefix(path, t.root) {
return path
}
return filepath.Join(t.targetRoot, strings.TrimPrefix(path, t.root))
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package transform
import (
"fmt"
"tags.cncf.io/container-device-interface/specs-go"
)
type simplify struct{}
var _ Transformer = (*simplify)(nil)
// NewSimplifier creates a simplifier transformer.
// This transoformer ensures that entities in the spec are deduplicated and that common edits are removed from device-specific edits.
func NewSimplifier() Transformer {
return Merge(
dedupe{},
simplify{},
sorter{},
)
}
// Transform simplifies the supplied spec.
// Edits that are present in the common edits are removed from device-specific edits.
func (s simplify) Transform(spec *specs.Spec) error {
if spec == nil {
return nil
}
dedupe := dedupe{}
if err := dedupe.Transform(spec); err != nil {
return err
}
commonEntityIDs, err := (*containerEdits)(&spec.ContainerEdits).getEntityIds()
if err != nil {
return err
}
toRemove := newRemover(commonEntityIDs...)
var updatedDevices []specs.Device
for _, device := range spec.Devices {
deviceAsSpec := specs.Spec{
ContainerEdits: device.ContainerEdits,
}
err := toRemove.Transform(&deviceAsSpec)
if err != nil {
return fmt.Errorf("failed to transform device edits: %w", err)
}
if !(containerEdits)(deviceAsSpec.ContainerEdits).IsEmpty() {
// Devices with empty edits are invalid.
// We only update the container edits for the device if this would
// result in a valid device.
device.ContainerEdits = deviceAsSpec.ContainerEdits
}
updatedDevices = append(updatedDevices, device)
}
spec.Devices = updatedDevices
return nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package transform
import (
"os"
"path/filepath"
"sort"
"strings"
"tags.cncf.io/container-device-interface/specs-go"
)
type sorter struct{}
var _ Transformer = (*sorter)(nil)
// NewSorter creates a transformer that sorts container edits.
func NewSorter() Transformer {
return nil
}
// Transform sorts the entities in the specified CDI specification.
func (d sorter) Transform(spec *specs.Spec) error {
if spec == nil {
return nil
}
if err := d.transformEdits(&spec.ContainerEdits); err != nil {
return err
}
var updatedDevices []specs.Device
for _, device := range spec.Devices {
device := device
if err := d.transformEdits(&device.ContainerEdits); err != nil {
return err
}
updatedDevices = append(updatedDevices, device)
}
spec.Devices = d.sortDevices(updatedDevices)
return nil
}
func (d sorter) transformEdits(edits *specs.ContainerEdits) error {
edits.DeviceNodes = d.sortDeviceNodes(edits.DeviceNodes)
edits.Mounts = d.sortMounts(edits.Mounts)
return nil
}
func (d sorter) sortDevices(devices []specs.Device) []specs.Device {
sort.Slice(devices, func(i, j int) bool {
return devices[i].Name < devices[j].Name
})
return devices
}
// sortDeviceNodes sorts the specified device nodes by container path.
// If two device nodes have the same container path, the host path is used to break ties.
func (d sorter) sortDeviceNodes(entities []*specs.DeviceNode) []*specs.DeviceNode {
sort.Slice(entities, func(i, j int) bool {
ip := strings.Count(filepath.Clean(entities[i].Path), string(os.PathSeparator))
jp := strings.Count(filepath.Clean(entities[j].Path), string(os.PathSeparator))
if ip == jp {
return entities[i].Path < entities[j].Path
}
return ip < jp
})
return entities
}
// sortMounts sorts the specified mounts by container path.
// If two mounts have the same mount path, the host path is used to break ties.
func (d sorter) sortMounts(entities []*specs.Mount) []*specs.Mount {
sort.Slice(entities, func(i, j int) bool {
ip := strings.Count(filepath.Clean(entities[i].ContainerPath), string(os.PathSeparator))
jp := strings.Count(filepath.Clean(entities[j].ContainerPath), string(os.PathSeparator))
if ip == jp {
return entities[i].ContainerPath < entities[j].ContainerPath
}
return ip < jp
})
return entities
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package c3000cdi
import (
"dtk-container-toolkit/internal/discover"
"dtk-container-toolkit/internal/logger"
"fmt"
"path/filepath"
)
type deviceFolderPermissions struct {
logger logger.Interface
devRoot string
dtkCDIHookPath string
devices discover.Discover
}
var _ discover.Discover = (*deviceFolderPermissions)(nil)
// newDeviceFolderPermissionHookDiscoverer creates a discoverer that can be used to update the permissions for the parent folders of nested device nodes from the specified set of device specs.
// This works around an issue with rootless podman when using crun as a low-level runtime.
// See https://github.com/containers/crun/issues/1047
// The nested devices that are applicable to the NVIDIA GPU devices are:
// - DRM devices at /dev/dri/*
func newDeviceFolderPermissionHookDiscoverer(logger logger.Interface, devRoot string, dtkCDIHookPath string, devices discover.Discover) discover.Discover {
d := &deviceFolderPermissions{
logger: logger,
devRoot: devRoot,
dtkCDIHookPath: dtkCDIHookPath,
devices: devices,
}
return d
}
// Devices are empty for this discoverer
func (d *deviceFolderPermissions) Devices() ([]discover.Device, error) {
return nil, nil
}
// Hooks returns a set of hooks that sets the file mode to 755 of parent folders for nested device nodes.
func (d *deviceFolderPermissions) Hooks() ([]discover.Hook, error) {
folders, err := d.getDeviceSubfolders()
if err != nil {
return nil, fmt.Errorf("failed to get device subfolders: %v", err)
}
if len(folders) == 0 {
return nil, nil
}
args := []string{"--mode", "755"}
for _, folder := range folders {
args = append(args, "--path", folder)
}
hook := discover.CreateDtkCDIHook(
d.dtkCDIHookPath,
"chmod",
args...,
)
return []discover.Hook{hook}, nil
}
func (d *deviceFolderPermissions) getDeviceSubfolders() ([]string, error) {
// For now we only consider the following special case paths
allowedPaths := map[string]bool{
"/dev/dri": true,
}
devices, err := d.devices.Devices()
if err != nil {
return nil, fmt.Errorf("failed to get devices: %v", err)
}
var folders []string
seen := make(map[string]bool)
for _, device := range devices {
df := filepath.Dir(device.Path)
if seen[df] {
continue
}
// We only consider the special case paths
if !allowedPaths[df] {
continue
}
folders = append(folders, df)
seen[df] = true
if len(folders) == len(allowedPaths) {
break
}
}
return folders, nil
}
// Mounts are empty for this discoverer
func (d *deviceFolderPermissions) Mounts() ([]discover.Mount, error) {
return nil, nil
}
func (d *deviceFolderPermissions) AdditionalGIDs() ([]uint32, error) {
return nil, nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package c3000cdi
import (
"dtk-container-toolkit/internal/config/image"
"dtk-container-toolkit/pkg/c3000cdi/spec"
"dtk-container-toolkit/pkg/c3000cdi/transform"
"tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go"
)
type wrapper struct {
Interface
vendor string
class string
mergedDeviceOptions []transform.MergedDeviceOption
}
// GetSpec combines the device specs and common edits from the wrapped Interface to a single spec.Interface.
func (l *wrapper) GetSpec() (spec.Interface, error) {
deviceSpecs, err := l.GetAllDeviceSpecs()
if err != nil {
return nil, err
}
edits, err := l.GetCommonEdits()
if err != nil {
return nil, err
}
return spec.New(
spec.WithDeviceSpecs(deviceSpecs),
spec.WithEdits(*edits.ContainerEdits),
spec.WithVendor(l.vendor),
spec.WithClass(l.class),
spec.WithMergedDeviceOptions(l.mergedDeviceOptions...),
)
}
// GetAllDeviceSpecs returns the device specs for all available devices.
func (l *wrapper) GetAllDeviceSpecs() ([]specs.Device, error) {
return l.Interface.GetAllDeviceSpecs()
}
// GetCommonEdits returns the wrapped edits and adds additional edits on top.
func (m *wrapper) GetCommonEdits() (*cdi.ContainerEdits, error) {
edits, err := m.Interface.GetCommonEdits()
if err != nil {
return nil, err
}
edits.Env = append(edits.Env, image.EnvVarDTKVisibleDevices+"=void")
return edits, nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package engine
// Interface defines the API for a runtime config updater.
type Interface interface {
DefaultRuntime() string
AddRuntime(string, string, bool, ...map[string]interface{}) error
Set(string, interface{})
RemoveRuntime(string) error
Save(string) (int64, error)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package engine
import (
"fmt"
"os"
"path/filepath"
)
// Config represents a runtime config
type Config string
// Write writes the specified contents to a config file.
func (c Config) Write(output []byte) (int, error) {
path := string(c)
if path == "" {
n, err := os.Stdout.Write(output)
if err == nil {
os.Stdout.WriteString("\n")
}
return n, err
}
if len(output) == 0 {
err := os.Remove(path)
if err != nil {
return 0, fmt.Errorf("unable to remove empty file: %v", err)
}
return 0, nil
}
if dir := filepath.Dir(path); dir != "" {
err := os.MkdirAll(dir, 0755)
if err != nil {
return 0, fmt.Errorf("unable to create directory %v: %v", dir, err)
}
}
f, err := os.Create(path)
if err != nil {
return 0, fmt.Errorf("unable to open %v for writing: %v", path, err)
}
defer f.Close()
return f.Write(output)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package containerd
import (
"dtk-container-toolkit/pkg/config/engine"
"fmt"
"github.com/pelletier/go-toml"
)
// ConfigV1 represents a version 1 containerd config
type ConfigV1 Config
var _ engine.Interface = (*ConfigV1)(nil)
// AddRuntime adds a runtime to the containerd config
func (c *ConfigV1) AddRuntime(name string, path string, setAsDefault bool, configOverrides ...map[string]interface{}) error {
if c == nil || c.Tree == nil {
return fmt.Errorf("config is nil")
}
config := *c.Tree
config.Set("version", int64(1))
if runc, ok := config.GetPath([]string{"plugins", "cri", "containerd", "runtimes", "runc"}).(*toml.Tree); ok {
runc, _ = toml.Load(runc.String())
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name}, runc)
}
if config.GetPath([]string{"plugins", "cri", "containerd", "runtimes", name}) == nil {
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "runtime_type"}, c.RuntimeType)
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "runtime_root"}, "")
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "runtime_engine"}, "")
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "privileged_without_host_devices"}, false)
}
if len(c.ContainerAnnotations) > 0 {
annotations, err := (*Config)(c).getRuntimeAnnotations([]string{"plugins", "cri", "containerd", "runtimes", name, "container_annotations"})
if err != nil {
return err
}
annotations = append(c.ContainerAnnotations, annotations...)
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "container_annotations"}, annotations)
}
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "options", "BinaryName"}, path)
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "options", "Runtime"}, path)
if setAsDefault && c.UseDefaultRuntimeName {
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime_name"}, name)
} else if setAsDefault {
// Note: This is deprecated in containerd 1.4.0 and will be removed in 1.5.0
if config.GetPath([]string{"plugins", "cri", "containerd", "default_runtime"}) == nil {
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_type"}, c.RuntimeType)
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_root"}, "")
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_engine"}, "")
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "privileged_without_host_devices"}, false)
}
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "BinaryName"}, path)
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "Runtime"}, path)
defaultRuntimeSubtree := subtreeAtPath(config, "plugins", "cri", "containerd", "default_runtime")
if err := defaultRuntimeSubtree.applyOverrides(configOverrides...); err != nil {
return fmt.Errorf("failed to apply config overrides to default_runtime: %w", err)
}
}
runtimeSubtree := subtreeAtPath(config, "plugins", "cri", "containerd", "runtimes", name)
if err := runtimeSubtree.applyOverrides(configOverrides...); err != nil {
return fmt.Errorf("failed to apply config overrides: %w", err)
}
*c.Tree = config
return nil
}
// DefaultRuntime returns the default runtime for the cri-o config
func (c ConfigV1) DefaultRuntime() string {
if runtime, ok := c.GetPath([]string{"plugins", "cri", "containerd", "default_runtime_name"}).(string); ok {
return runtime
}
return ""
}
// RemoveRuntime removes a runtime from the docker config
func (c *ConfigV1) RemoveRuntime(name string) error {
if c == nil || c.Tree == nil {
return nil
}
config := *c.Tree
// If the specified runtime was set as the default runtime we need to remove the default runtime too.
runtimePath, ok := config.GetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "options", "BinaryName"}).(string)
if !ok || runtimePath == "" {
runtimePath, _ = config.GetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "options", "Runtime"}).(string)
}
defaultRuntimePath, ok := config.GetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "BinaryName"}).(string)
if !ok || defaultRuntimePath == "" {
defaultRuntimePath, _ = config.GetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "Runtime"}).(string)
}
if runtimePath != "" && defaultRuntimePath != "" && runtimePath == defaultRuntimePath {
config.DeletePath([]string{"plugins", "cri", "containerd", "default_runtime"})
}
config.DeletePath([]string{"plugins", "cri", "containerd", "runtimes", name})
if runtime, ok := config.GetPath([]string{"plugins", "cri", "containerd", "default_runtime_name"}).(string); ok {
if runtime == name {
config.DeletePath([]string{"plugins", "cri", "containerd", "default_runtime_name"})
}
}
runtimeConfigPath := []string{"plugins", "cri", "containerd", "runtimes", name}
for i := 0; i < len(runtimeConfigPath); i++ {
if runtimes, ok := config.GetPath(runtimeConfigPath[:len(runtimeConfigPath)-i]).(*toml.Tree); ok {
if len(runtimes.Keys()) == 0 {
config.DeletePath(runtimeConfigPath[:len(runtimeConfigPath)-i])
}
}
}
if len(config.Keys()) == 1 && config.Keys()[0] == "version" {
config.Delete("version")
}
*c.Tree = config
return nil
}
// SetOption sets the specified containerd option.
func (c *ConfigV1) Set(key string, value interface{}) {
config := *c.Tree
config.SetPath([]string{"plugins", "cri", "containerd", key}, value)
*c.Tree = config
}
// Save wrotes the config to a file
func (c ConfigV1) Save(path string) (int64, error) {
return (Config)(c).Save(path)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package containerd
import (
"dtk-container-toolkit/pkg/config/engine"
"fmt"
"github.com/pelletier/go-toml"
)
// AddRuntime adds a runtime to the containerd config
func (c *Config) AddRuntime(name string, path string, setAsDefault bool, configOverrides ...map[string]interface{}) error {
if c == nil || c.Tree == nil {
return fmt.Errorf("config is nil")
}
config := *c.Tree
config.Set("version", int64(2))
if runc, ok := config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", "runc"}).(*toml.Tree); ok {
runc, _ = toml.Load(runc.String())
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}, runc)
}
if config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}) == nil {
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_type"}, c.RuntimeType)
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_root"}, "")
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_engine"}, "")
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "privileged_without_host_devices"}, false)
}
if len(c.ContainerAnnotations) > 0 {
annotations, err := c.getRuntimeAnnotations([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "container_annotations"})
if err != nil {
return err
}
annotations = append(c.ContainerAnnotations, annotations...)
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "container_annotations"}, annotations)
}
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "options", "BinaryName"}, path)
if setAsDefault {
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}, name)
}
runtimeSubtree := subtreeAtPath(config, "plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name)
if err := runtimeSubtree.applyOverrides(configOverrides...); err != nil {
return fmt.Errorf("failed to apply config overrides: %w", err)
}
*c.Tree = config
return nil
}
func (c *Config) getRuntimeAnnotations(path []string) ([]string, error) {
if c == nil || c.Tree == nil {
return nil, nil
}
config := *c.Tree
if !config.HasPath(path) {
return nil, nil
}
annotationsI, ok := config.GetPath(path).([]interface{})
if !ok {
return nil, fmt.Errorf("invalid annotations: %v", annotationsI)
}
var annotations []string
for _, annotation := range annotationsI {
a, ok := annotation.(string)
if !ok {
return nil, fmt.Errorf("invalid annotation: %v", annotation)
}
annotations = append(annotations, a)
}
return annotations, nil
}
// Set sets the specified containerd option.
func (c *Config) Set(key string, value interface{}) {
config := *c.Tree
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", key}, value)
*c.Tree = config
}
// DefaultRuntime returns the default runtime for the cri-o config
func (c Config) DefaultRuntime() string {
if runtime, ok := c.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}).(string); ok {
return runtime
}
return ""
}
// RemoveRuntime removes a runtime from the docker config
func (c *Config) RemoveRuntime(name string) error {
if c == nil || c.Tree == nil {
return nil
}
config := *c.Tree
config.DeletePath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name})
if runtime, ok := config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}).(string); ok {
if runtime == name {
config.DeletePath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"})
}
}
runtimePath := []string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}
for i := 0; i < len(runtimePath); i++ {
if runtimes, ok := config.GetPath(runtimePath[:len(runtimePath)-i]).(*toml.Tree); ok {
if len(runtimes.Keys()) == 0 {
config.DeletePath(runtimePath[:len(runtimePath)-i])
}
}
}
if len(config.Keys()) == 1 && config.Keys()[0] == "version" {
config.Delete("version")
}
*c.Tree = config
return nil
}
// Save writes the config to the specified path
func (c Config) Save(path string) (int64, error) {
config := c.Tree
output, err := config.Marshal()
if err != nil {
return 0, fmt.Errorf("unable to convert to TOML: %v", err)
}
n, err := engine.Config(path).Write(output)
return int64(n), err
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment