| /** |
| # Copyright (c) NVIDIA CORPORATION. All rights reserved. |
| # |
| # 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 transform |
| |
| import ( |
| "fmt" |
| |
| "github.com/NVIDIA/nvidia-container-toolkit/internal/edits" |
| |
| "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 |
| } |