2022-01-14 13:57:51 +00:00
/ * *
# Copyright ( c ) 2022 , 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 configure
import (
"fmt"
2023-03-23 12:50:48 +00:00
"path/filepath"
2022-01-14 13:57:51 +00:00
2023-11-20 22:55:21 +00:00
"github.com/urfave/cli/v2"
2023-03-22 12:27:43 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
2023-04-24 11:58:03 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine"
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine/containerd"
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine/crio"
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine/docker"
2023-08-11 13:23:13 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/ocihook"
2024-08-08 14:27:07 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
2022-01-14 13:57:51 +00:00
)
const (
defaultRuntime = "docker"
2023-03-23 12:50:48 +00:00
// defaultNVIDIARuntimeName is the default name to use in configs for the NVIDIA Container Runtime
defaultNVIDIARuntimeName = "nvidia"
// defaultNVIDIARuntimeExecutable is the default NVIDIA Container Runtime executable file name
2023-08-11 13:23:13 +00:00
defaultNVIDIARuntimeExecutable = "nvidia-container-runtime"
defaultNVIDIARuntimeExpecutablePath = "/usr/bin/nvidia-container-runtime"
defaultNVIDIARuntimeHookExpecutablePath = "/usr/bin/nvidia-container-runtime-hook"
2023-03-23 12:50:48 +00:00
2023-03-23 12:57:18 +00:00
defaultContainerdConfigFilePath = "/etc/containerd/config.toml"
defaultCrioConfigFilePath = "/etc/crio/crio.conf"
defaultDockerConfigFilePath = "/etc/docker/daemon.json"
2024-08-08 22:40:00 +00:00
defaultConfigSource = configSourceFile
configSourceCommand = "command"
configSourceFile = "file"
2022-01-14 13:57:51 +00:00
)
type command struct {
2023-03-22 12:27:43 +00:00
logger logger . Interface
2022-01-14 13:57:51 +00:00
}
2024-10-10 16:50:48 +00:00
// NewCommand constructs a configure command with the specified logger
2023-03-22 12:27:43 +00:00
func NewCommand ( logger logger . Interface ) * cli . Command {
2022-01-14 13:57:51 +00:00
c := command {
logger : logger ,
}
return c . build ( )
}
// config defines the options that can be set for the CLI through config files,
// environment variables, or command line config
type config struct {
dryRun bool
runtime string
configFilePath string
2024-08-08 22:40:00 +00:00
configSource string
2023-08-11 13:23:13 +00:00
mode string
hookFilePath string
2023-03-23 12:50:48 +00:00
2024-05-15 20:21:25 +00:00
runtimeConfigOverrideJSON string
2023-03-23 12:50:48 +00:00
nvidiaRuntime struct {
name string
path string
2023-08-11 13:23:13 +00:00
hookPath string
2023-03-23 12:50:48 +00:00
setAsDefault bool
}
2023-10-31 14:24:27 +00:00
// cdi-specific options
cdi struct {
enabled bool
}
2022-01-14 13:57:51 +00:00
}
func ( m command ) build ( ) * cli . Command {
// Create a config struct to hold the parsed environment variables or command line flags
config := config { }
// Create the 'configure' command
configure := cli . Command {
Name : "configure" ,
Usage : "Add a runtime to the specified container engine" ,
2023-03-23 12:50:48 +00:00
Before : func ( c * cli . Context ) error {
2023-08-11 13:23:13 +00:00
return m . validateFlags ( c , & config )
2023-03-23 12:50:48 +00:00
} ,
2022-01-14 13:57:51 +00:00
Action : func ( c * cli . Context ) error {
return m . configureWrapper ( c , & config )
} ,
}
configure . Flags = [ ] cli . Flag {
& cli . BoolFlag {
Name : "dry-run" ,
Usage : "update the runtime configuration as required but don't write changes to disk" ,
Destination : & config . dryRun ,
} ,
& cli . StringFlag {
Name : "runtime" ,
2023-03-23 12:57:18 +00:00
Usage : "the target runtime engine; one of [containerd, crio, docker]" ,
2022-01-14 13:57:51 +00:00
Value : defaultRuntime ,
Destination : & config . r untime ,
} ,
& cli . StringFlag {
Name : "config" ,
Usage : "path to the config file for the target runtime" ,
Destination : & config . configFilePath ,
} ,
2023-08-11 13:23:13 +00:00
& cli . StringFlag {
Name : "config-mode" ,
Usage : "the config mode for runtimes that support multiple configuration mechanisms" ,
Destination : & config . mode ,
} ,
2024-08-08 22:40:00 +00:00
& cli . StringFlag {
Name : "config-source" ,
Usage : "the source to retrieve the container runtime configuration; one of [command, file]\"" ,
Destination : & config . configSource ,
Value : defaultConfigSource ,
} ,
2023-08-11 13:23:13 +00:00
& cli . StringFlag {
Name : "oci-hook-path" ,
Usage : "the path to the OCI runtime hook to create if --config-mode=oci-hook is specified. If no path is specified, the generated hook is output to STDOUT.\n\tNote: The use of OCI hooks is deprecated." ,
Destination : & config . hookFilePath ,
} ,
2022-01-14 13:57:51 +00:00
& cli . StringFlag {
Name : "nvidia-runtime-name" ,
Usage : "specify the name of the NVIDIA runtime that will be added" ,
2023-03-23 12:50:48 +00:00
Value : defaultNVIDIARuntimeName ,
Destination : & config . nvidiaRuntime . name ,
2022-01-14 13:57:51 +00:00
} ,
& cli . StringFlag {
2023-03-23 12:50:48 +00:00
Name : "nvidia-runtime-path" ,
Aliases : [ ] string { "runtime-path" } ,
2022-01-14 13:57:51 +00:00
Usage : "specify the path to the NVIDIA runtime executable" ,
2023-03-23 12:50:48 +00:00
Value : defaultNVIDIARuntimeExecutable ,
Destination : & config . nvidiaRuntime . path ,
2022-01-14 13:57:51 +00:00
} ,
2023-08-11 13:23:13 +00:00
& cli . StringFlag {
Name : "nvidia-runtime-hook-path" ,
Usage : "specify the path to the NVIDIA Container Runtime hook executable" ,
Value : defaultNVIDIARuntimeHookExpecutablePath ,
Destination : & config . nvidiaRuntime . hookPath ,
} ,
2022-01-14 13:57:51 +00:00
& cli . BoolFlag {
2023-03-23 12:50:48 +00:00
Name : "nvidia-set-as-default" ,
Aliases : [ ] string { "set-as-default" } ,
Usage : "set the NVIDIA runtime as the default runtime" ,
Destination : & config . nvidiaRuntime . setAsDefault ,
2022-01-14 13:57:51 +00:00
} ,
2023-10-31 14:24:27 +00:00
& cli . BoolFlag {
Name : "cdi.enabled" ,
2024-01-23 13:57:15 +00:00
Aliases : [ ] string { "cdi.enable" } ,
2023-10-31 14:24:27 +00:00
Usage : "Enable CDI in the configured runtime" ,
Destination : & config . cdi . enabled ,
} ,
2022-01-14 13:57:51 +00:00
}
return & configure
}
2023-08-11 13:23:13 +00:00
func ( m command ) validateFlags ( c * cli . Context , config * config ) error {
if config . mode == "oci-hook" {
if ! filepath . IsAbs ( config . nvidiaRuntime . hookPath ) {
return fmt . Errorf ( "the NVIDIA runtime hook path %q is not an absolute path" , config . nvidiaRuntime . hookPath )
}
return nil
}
if config . mode != "" && config . mode != "config-file" {
m . logger . Warningf ( "Ignoring unsupported config mode for %v: %q" , config . runtime , config . mode )
}
config . mode = "config-file"
2023-03-23 12:50:48 +00:00
switch config . runtime {
2023-03-23 12:57:18 +00:00
case "containerd" , "crio" , "docker" :
2023-03-23 12:50:48 +00:00
break
default :
return fmt . Errorf ( "unrecognized runtime '%v'" , config . runtime )
}
2022-01-14 13:57:51 +00:00
switch config . runtime {
2023-03-23 12:57:18 +00:00
case "containerd" , "crio" :
2023-03-23 12:50:48 +00:00
if config . nvidiaRuntime . path == defaultNVIDIARuntimeExecutable {
2023-08-11 13:23:13 +00:00
config . nvidiaRuntime . path = defaultNVIDIARuntimeExpecutablePath
2023-03-23 12:50:48 +00:00
}
if ! filepath . IsAbs ( config . nvidiaRuntime . path ) {
return fmt . Errorf ( "the NVIDIA runtime path %q is not an absolute path" , config . nvidiaRuntime . path )
}
2022-01-14 13:57:51 +00:00
}
2023-11-20 22:55:21 +00:00
if config . runtime != "containerd" && config . runtime != "docker" {
2023-10-31 14:24:27 +00:00
if config . cdi . enabled {
m . logger . Warningf ( "Ignoring cdi.enabled flag for %v" , config . runtime )
}
config . cdi . enabled = false
}
2024-05-15 20:21:25 +00:00
if config . runtimeConfigOverrideJSON != "" && config . runtime != "containerd" {
m . logger . Warningf ( "Ignoring runtime-config-override flag for %v" , config . runtime )
config . runtimeConfigOverrideJSON = ""
}
2024-08-08 22:40:00 +00:00
switch config . configSource {
case configSourceCommand :
if config . runtime == "docker" {
m . logger . Warningf ( "A %v Config Source is not supported for %v; using %v" , config . configSource , config . runtime , configSourceFile )
config . configSource = configSourceFile
}
case configSourceFile :
break
default :
return fmt . Errorf ( "unrecognized Config Source: %v" , config . configSource )
}
2023-03-23 12:50:48 +00:00
return nil
2022-01-14 13:57:51 +00:00
}
2023-03-23 12:50:48 +00:00
// configureWrapper updates the specified container engine config to enable the NVIDIA runtime
func ( m command ) configureWrapper ( c * cli . Context , config * config ) error {
2023-08-11 13:23:13 +00:00
switch config . mode {
case "oci-hook" :
return m . configureOCIHook ( c , config )
case "config-file" :
return m . configureConfigFile ( c , config )
}
return fmt . Errorf ( "unsupported config-mode: %v" , config . mode )
}
// configureConfigFile updates the specified container engine config file to enable the NVIDIA runtime.
func ( m command ) configureConfigFile ( c * cli . Context , config * config ) error {
2023-03-23 12:50:48 +00:00
configFilePath := config . resolveConfigFilePath ( )
2022-01-14 13:57:51 +00:00
2023-03-23 12:50:48 +00:00
var err error
2024-08-08 22:40:00 +00:00
configSource , err := config . resolveConfigSource ( )
if err != nil {
return err
}
var cfg engine . Interface
2023-03-23 12:50:48 +00:00
switch config . runtime {
2023-03-23 12:57:18 +00:00
case "containerd" :
cfg , err = containerd . New (
2023-05-26 08:15:27 +00:00
containerd . WithLogger ( m . logger ) ,
2023-03-23 12:57:18 +00:00
containerd . WithPath ( configFilePath ) ,
2024-08-08 22:40:00 +00:00
containerd . WithConfigSource ( configSource ) ,
2023-03-23 12:57:18 +00:00
)
2023-03-23 12:50:48 +00:00
case "crio" :
cfg , err = crio . New (
2023-05-26 08:15:27 +00:00
crio . WithLogger ( m . logger ) ,
2023-03-23 12:50:48 +00:00
crio . WithPath ( configFilePath ) ,
2024-08-08 22:40:00 +00:00
crio . WithConfigSource ( configSource ) ,
2023-03-23 12:50:48 +00:00
)
case "docker" :
cfg , err = docker . New (
2023-05-26 08:15:27 +00:00
docker . WithLogger ( m . logger ) ,
2023-03-23 12:50:48 +00:00
docker . WithPath ( configFilePath ) ,
)
default :
err = fmt . Errorf ( "unrecognized runtime '%v'" , config . runtime )
}
if err != nil || cfg == nil {
return fmt . Errorf ( "unable to load config for runtime %v: %v" , config . runtime , err )
2022-01-14 13:57:51 +00:00
}
2023-02-23 13:43:15 +00:00
err = cfg . AddRuntime (
2023-03-23 12:50:48 +00:00
config . nvidiaRuntime . name ,
config . nvidiaRuntime . path ,
config . nvidiaRuntime . setAsDefault ,
2022-07-14 14:19:19 +00:00
)
2022-01-14 13:57:51 +00:00
if err != nil {
return fmt . Errorf ( "unable to update config: %v" , err )
}
2023-11-20 22:55:21 +00:00
err = enableCDI ( config , cfg )
if err != nil {
return fmt . Errorf ( "failed to enable CDI in %s: %w" , config . runtime , err )
2023-10-31 14:24:27 +00:00
}
2024-10-10 16:50:48 +00:00
outputPath := config . getOutputConfigPath ( )
2023-03-23 12:50:48 +00:00
n , err := cfg . Save ( outputPath )
2022-01-14 13:57:51 +00:00
if err != nil {
return fmt . Errorf ( "unable to flush config: %v" , err )
}
2023-07-03 13:14:22 +00:00
if outputPath != "" {
if n == 0 {
m . logger . Infof ( "Removed empty config from %v" , outputPath )
} else {
m . logger . Infof ( "Wrote updated config to %v" , outputPath )
}
m . logger . Infof ( "It is recommended that %v daemon be restarted." , config . runtime )
2023-02-23 13:43:15 +00:00
}
2022-01-14 13:57:51 +00:00
return nil
}
2022-06-24 05:28:10 +00:00
2023-03-23 12:50:48 +00:00
// resolveConfigFilePath returns the default config file path for the configured container engine
func ( c * config ) resolveConfigFilePath ( ) string {
if c . configFilePath != "" {
return c . configFilePath
2022-06-24 05:28:10 +00:00
}
2023-03-23 12:50:48 +00:00
switch c . runtime {
2023-03-23 12:57:18 +00:00
case "containerd" :
return defaultContainerdConfigFilePath
2023-03-23 12:50:48 +00:00
case "crio" :
return defaultCrioConfigFilePath
case "docker" :
return defaultDockerConfigFilePath
2022-06-24 05:28:10 +00:00
}
2023-03-23 12:50:48 +00:00
return ""
}
2022-06-24 05:28:10 +00:00
2024-08-08 22:40:00 +00:00
// resolveConfigSource returns the default config source or the user provided config source
func ( c * config ) resolveConfigSource ( ) ( toml . Loader , error ) {
switch c . configSource {
case configSourceCommand :
return c . getCommandConfigSource ( ) , nil
case configSourceFile :
return toml . FromFile ( c . configFilePath ) , nil
default :
return nil , fmt . Errorf ( "unrecognized config source: %s" , c . configSource )
}
}
// getConfigSourceCommand returns the default cli command to fetch the current runtime config
func ( c * config ) getCommandConfigSource ( ) toml . Loader {
switch c . runtime {
case "containerd" :
return containerd . CommandLineSource ( "" )
case "crio" :
return crio . CommandLineSource ( "" )
}
return toml . Empty
}
2024-10-10 16:50:48 +00:00
// getOutputConfigPath returns the configured config path or "" if dry-run is enabled
func ( c * config ) getOutputConfigPath ( ) string {
2023-03-23 12:50:48 +00:00
if c . dryRun {
return ""
2023-02-23 12:49:54 +00:00
}
2023-03-23 12:50:48 +00:00
return c . resolveConfigFilePath ( )
2022-06-24 05:28:10 +00:00
}
2023-08-11 13:23:13 +00:00
// configureOCIHook creates and configures the OCI hook for the NVIDIA runtime
func ( m * command ) configureOCIHook ( c * cli . Context , config * config ) error {
err := ocihook . CreateHook ( config . hookFilePath , config . nvidiaRuntime . hookPath )
if err != nil {
return fmt . Errorf ( "error creating OCI hook: %v" , err )
}
return nil
}
2023-11-20 22:55:21 +00:00
// enableCDI enables the use of CDI in the corresponding container engine
func enableCDI ( config * config , cfg engine . Interface ) error {
if ! config . cdi . enabled {
return nil
}
switch config . runtime {
case "containerd" :
2023-12-01 23:59:34 +00:00
cfg . Set ( "enable_cdi" , true )
2023-11-20 22:55:21 +00:00
case "docker" :
2024-01-23 13:56:01 +00:00
cfg . Set ( "features" , map [ string ] bool { "cdi" : true } )
2023-12-01 23:59:34 +00:00
default :
return fmt . Errorf ( "enabling CDI in %s is not supported" , config . runtime )
2023-11-20 22:55:21 +00:00
}
2023-12-01 23:59:34 +00:00
return nil
2023-11-20 22:55:21 +00:00
}