@@ -21,8 +21,10 @@ import (
2121 "io"
2222 "os"
2323 "path/filepath"
24+ "strings"
2425
2526 "tags.cncf.io/container-device-interface/pkg/cdi"
27+ producer "tags.cncf.io/container-device-interface/pkg/cdi-producer"
2628 "tags.cncf.io/container-device-interface/specs-go"
2729
2830 "github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/transform"
@@ -42,104 +44,95 @@ func New(opts ...Option) (Interface, error) {
4244 return newBuilder (opts ... ).Build ()
4345}
4446
45- // Save writes the spec to the specified path and overwrites the file if it exists.
46- func (s * spec ) Save (path string ) error {
47- if s .transformOnSave != nil {
48- err := s .transformOnSave .Transform (s .Raw ())
47+ type Validator interface {
48+ Validate (* specs.Spec ) error
49+ }
50+
51+ type Validators []Validator
52+
53+ func (v Validators ) Validate (s * specs.Spec ) error {
54+ for _ , vv := range v {
55+ if vv == nil {
56+ continue
57+ }
58+ err := vv .Validate (s )
4959 if err != nil {
50- return fmt . Errorf ( "error applying transform: %w" , err )
60+ return err
5161 }
5262 }
53- path , err := s .normalizePath (path )
54- if err != nil {
55- return fmt .Errorf ("failed to normalize path: %w" , err )
56- }
63+ return nil
64+ }
5765
58- specDir , filename := filepath .Split (path )
59- cache , _ := cdi .NewCache (
60- cdi .WithAutoRefresh (false ),
61- cdi .WithSpecDirs (specDir ),
62- )
63- if err := cache .WriteSpec (s .Raw (), filename ); err != nil {
64- return fmt .Errorf ("failed to write spec: %w" , err )
65- }
66+ type transfromAsValidator struct {
67+ transform.Transformer
68+ }
6669
67- specDirAsRoot , err := os . OpenRoot ( specDir )
68- if err ! = nil {
69- return fmt . Errorf ( "failed to open root: %w" , err )
70+ func fromTransform ( t transform. Transformer ) Validator {
71+ if t = = nil {
72+ return nil
7073 }
71- defer specDirAsRoot .Close ()
74+ return & transfromAsValidator {t }
75+ }
7276
73- if err := specDirAsRoot .Chmod (filename , s .permissions ); err != nil {
74- return fmt .Errorf ("failed to set permissions on spec file: %w" , err )
77+ func (t * transfromAsValidator ) Validate (s * specs.Spec ) error {
78+ if t == nil || t .Transformer == nil {
79+ return nil
80+ }
81+ if err := t .Transform (s ); err != nil {
82+ return fmt .Errorf ("error applying transform: %w" , err )
7583 }
76-
7784 return nil
7885}
7986
80- // WriteTo writes the spec to the specified writer.
81- func (s * spec ) WriteTo (w io.Writer ) (int64 , error ) {
82- tmpFile , err := os .CreateTemp ("" , "nvcdi-spec-*" + s .extension ())
83- if err != nil {
84- return 0 , err
85- }
86- tmpDir , tmpFileName := filepath .Split (tmpFile .Name ())
87-
88- tmpDirRoot , err := os .OpenRoot (tmpDir )
89- if err != nil {
90- return 0 , err
87+ // Save writes the spec to the specified path and overwrites the file if it exists.
88+ func (s * spec ) Save (path string ) error {
89+ if pathWithExtension := s .ensureExtension (path ); pathWithExtension != "" {
90+ return producer .Save (s .Raw (), pathWithExtension ,
91+ s .producerOptions ()... ,
92+ )
9193 }
92- defer tmpDirRoot .Close ()
93- defer func () {
94- _ = tmpDirRoot .Remove (tmpFileName )
95- }()
94+ _ , err := s .WriteTo (os .Stdout )
95+ return err
96+ }
9697
97- if err := s .Save (tmpFile .Name ()); err != nil {
98- return 0 , err
99- }
98+ // WriteTo writes the spec to the specified writer.
99+ func (s * spec ) WriteTo (w io.Writer ) (int64 , error ) {
100+ return producer .WriteTo (s .Raw (), w ,
101+ s .producerOptions ()... ,
102+ )
103+ }
100104
101- if err := tmpFile .Close (); err != nil {
102- return 0 , fmt .Errorf ("failed to close temporary file: %w" , err )
105+ func (s * spec ) producerOptions () []producer.Option {
106+ var validators Validators
107+ if s .transformOnSave != nil {
108+ validators = append (validators , fromTransform (s .transformOnSave ))
103109 }
110+ validators = append (validators , cdi .SpecContentValidator )
104111
105- savedFile , err := tmpDirRoot .Open (tmpFileName )
106- if err != nil {
107- return 0 , fmt .Errorf ("failed to open temporary file: %w" , err )
112+ return []producer.Option {
113+ producer .WithOutputFormat (s .format ),
114+ producer .WithOverwrite (true ),
115+ producer .WithPermissions (s .permissions ),
116+ producer .WithValidator (validators ),
108117 }
109- defer savedFile .Close ()
110-
111- return savedFile .WriteTo (w )
112118}
113119
114120// Raw returns a pointer to the raw spec.
115121func (s * spec ) Raw () * specs.Spec {
116122 return s .Spec
117123}
118124
119- // normalizePath ensures that the specified path has a supported extension
120- func (s * spec ) normalizePath (path string ) (string , error ) {
121- if ext := filepath .Ext (path ); ext != ".yaml" && ext != ".json" {
122- path += s .extension ()
125+ func (s * spec ) ensureExtension (filename string ) string {
126+ if filename == "" {
127+ return ""
123128 }
124-
125- if filepath .Clean (filepath .Dir (path )) == "." {
126- pwd , err := os .Getwd ()
127- if err != nil {
128- return path , fmt .Errorf ("failed to get current working directory: %v" , err )
129- }
130- path = filepath .Join (pwd , path )
129+ ext := filepath .Ext (filename )
130+ switch ext {
131+ case ".yaml" , ".json" :
132+ return filename
133+ case ".yml" :
134+ return strings .TrimSuffix (filename , ".yml" ) + ".yaml"
135+ default :
136+ return filename + "." + s .format
131137 }
132-
133- return path , nil
134- }
135-
136- func (s * spec ) extension () string {
137- switch s .format {
138- case FormatJSON :
139- return ".json"
140- case FormatYAML :
141- return ".yaml"
142- }
143-
144- return ".yaml"
145138}
0 commit comments