| /** |
| # 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 spec |
| |
| import ( |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| |
| "tags.cncf.io/container-device-interface/pkg/cdi" |
| "tags.cncf.io/container-device-interface/specs-go" |
| |
| "github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/transform" |
| ) |
| |
| type spec struct { |
| *specs.Spec |
| format string |
| permissions os.FileMode |
| transformOnSave transform.Transformer |
| } |
| |
| var _ Interface = (*spec)(nil) |
| |
| // New creates a new spec with the specified options. |
| func New(opts ...Option) (Interface, error) { |
| return newBuilder(opts...).Build() |
| } |
| |
| // Save writes the spec to the specified path and overwrites the file if it exists. |
| func (s *spec) Save(path string) error { |
| if s.transformOnSave != nil { |
| err := s.transformOnSave.Transform(s.Raw()) |
| if err != nil { |
| return fmt.Errorf("error applying transform: %w", err) |
| } |
| } |
| path, err := s.normalizePath(path) |
| if err != nil { |
| return fmt.Errorf("failed to normalize path: %w", err) |
| } |
| |
| specDir := filepath.Dir(path) |
| cache, _ := cdi.NewCache( |
| cdi.WithAutoRefresh(false), |
| cdi.WithSpecDirs(specDir), |
| ) |
| if err := cache.WriteSpec(s.Raw(), filepath.Base(path)); err != nil { |
| return fmt.Errorf("failed to write spec: %w", err) |
| } |
| |
| if err := os.Chmod(path, s.permissions); err != nil { |
| return fmt.Errorf("failed to set permissions on spec file: %w", err) |
| } |
| |
| return nil |
| } |
| |
| // WriteTo writes the spec to the specified writer. |
| func (s *spec) WriteTo(w io.Writer) (int64, error) { |
| name, err := cdi.GenerateNameForSpec(s.Raw()) |
| if err != nil { |
| return 0, err |
| } |
| |
| path, _ := s.normalizePath(name) |
| tmpFile, err := os.CreateTemp("", "*"+filepath.Base(path)) |
| if err != nil { |
| return 0, err |
| } |
| defer os.Remove(tmpFile.Name()) |
| |
| if err := s.Save(tmpFile.Name()); err != nil { |
| return 0, err |
| } |
| |
| err = tmpFile.Close() |
| if err != nil { |
| return 0, fmt.Errorf("failed to close temporary file: %w", err) |
| } |
| |
| r, err := os.Open(tmpFile.Name()) |
| if err != nil { |
| return 0, fmt.Errorf("failed to open temporary file: %w", err) |
| } |
| defer r.Close() |
| |
| return io.Copy(w, r) |
| } |
| |
| // Raw returns a pointer to the raw spec. |
| func (s *spec) Raw() *specs.Spec { |
| return s.Spec |
| } |
| |
| // normalizePath ensures that the specified path has a supported extension |
| func (s *spec) normalizePath(path string) (string, error) { |
| if ext := filepath.Ext(path); ext != ".yaml" && ext != ".json" { |
| path += s.extension() |
| } |
| |
| if filepath.Clean(filepath.Dir(path)) == "." { |
| pwd, err := os.Getwd() |
| if err != nil { |
| return path, fmt.Errorf("failed to get current working directory: %v", err) |
| } |
| path = filepath.Join(pwd, path) |
| } |
| |
| return path, nil |
| } |
| |
| func (s *spec) extension() string { |
| switch s.format { |
| case FormatJSON: |
| return ".json" |
| case FormatYAML: |
| return ".yaml" |
| } |
| |
| return ".yaml" |
| } |