/** # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # 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 installer import ( "errors" "fmt" "io" "io/fs" "os" "path/filepath" "github.com/NVIDIA/nvidia-container-toolkit/internal/logger" ) //go:generate moq -rm -fmt=goimports -out installer_mock.go . Installer type Installer interface { Install(string) error } type toolkitInstaller struct { logger logger.Interface ignoreErrors bool sourceRoot string artifactRoot *artifactRoot ensureTargetDirectory Installer } var _ Installer = (*toolkitInstaller)(nil) // New creates a toolkit installer with the specified options. func New(opts ...Option) (Installer, error) { t := &toolkitInstaller{ sourceRoot: "/", } for _, opt := range opts { opt(t) } if t.logger == nil { t.logger = logger.New() } if t.artifactRoot == nil { artifactRoot, err := newArtifactRoot(t.logger, t.sourceRoot) if err != nil { return nil, err } t.artifactRoot = artifactRoot } if t.ensureTargetDirectory == nil { t.ensureTargetDirectory = t.createDirectory() } return t, nil } // Install ensures that the required toolkit files are installed in the specified directory. func (t *toolkitInstaller) Install(destDir string) error { var installers []Installer installers = append(installers, t.ensureTargetDirectory) libraries, err := t.collectLibraries() if err != nil { return fmt.Errorf("failed to collect libraries: %w", err) } installers = append(installers, libraries...) executables, err := t.collectExecutables(destDir) if err != nil { return fmt.Errorf("failed to collect executables: %w", err) } installers = append(installers, executables...) var errs error for _, i := range installers { errs = errors.Join(errs, i.Install(destDir)) } return errs } type symlink struct { linkname string target string } func (s symlink) Install(destDir string) error { symlinkPath := filepath.Join(destDir, s.linkname) return installSymlink(s.target, symlinkPath) } //go:generate moq -rm -fmt=goimports -out file-installer_mock.go . fileInstaller type fileInstaller interface { installContent(io.Reader, string, os.FileMode) error installFile(string, string) (os.FileMode, error) installSymlink(string, string) error } var installSymlink = installSymlinkStub func installSymlinkStub(target string, link string) error { err := os.Symlink(target, link) if err != nil { return fmt.Errorf("error creating symlink '%v' => '%v': %v", link, target, err) } return nil } var installFile = installFileStub func installFileStub(src string, dest string) (os.FileMode, error) { sourceInfo, err := os.Stat(src) if err != nil { return 0, fmt.Errorf("error getting file info for '%v': %v", src, err) } source, err := os.Open(src) if err != nil { return 0, fmt.Errorf("error opening source: %w", err) } defer source.Close() mode := sourceInfo.Mode() if err := installContent(source, dest, mode); err != nil { return 0, err } return mode, nil } var installContent = installContentStub func installContentStub(content io.Reader, dest string, mode fs.FileMode) error { destination, err := os.Create(dest) if err != nil { return fmt.Errorf("error creating destination: %w", err) } defer destination.Close() _, err = io.Copy(destination, content) if err != nil { return fmt.Errorf("error copying file: %w", err) } err = os.Chmod(dest, mode) if err != nil { return fmt.Errorf("error setting mode for '%v': %v", dest, err) } return nil }