[no-relnote] Use urfave for nvidia-container-runtime-hook CLI

Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
Evan Lezar 2024-10-30 13:36:11 +01:00
parent efb18a72ad
commit 1a83552aa6
No known key found for this signature in database
3 changed files with 71 additions and 58 deletions

View File

@ -30,11 +30,11 @@ func getDefaultHookConfig() (HookConfig, error) {
} }
// loadConfig loads the required paths for the hook config. // loadConfig loads the required paths for the hook config.
func loadConfig() (*config.Config, error) { func (a *app) loadConfig() (*config.Config, error) {
var configPaths []string var configPaths []string
var required bool var required bool
if len(*configflag) != 0 { if len(a.configFile) != 0 {
configPaths = append(configPaths, *configflag) configPaths = append(configPaths, a.configFile)
required = true required = true
} else { } else {
configPaths = append(configPaths, path.Join(driverPath, configPath), configPath) configPaths = append(configPaths, path.Join(driverPath, configPath), configPath)
@ -56,8 +56,8 @@ func loadConfig() (*config.Config, error) {
return config.GetDefault() return config.GetDefault()
} }
func getHookConfig() (*HookConfig, error) { func (a *app) getHookConfig() (*HookConfig, error) {
cfg, err := loadConfig() cfg, err := a.loadConfig()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load config: %v", err) return nil, fmt.Errorf("failed to load config: %v", err)
} }

View File

@ -72,16 +72,17 @@ func TestGetHookConfig(t *testing.T) {
if len(filename) > 0 { if len(filename) > 0 {
os.Remove(filename) os.Remove(filename)
} }
configflag = nil
}() }()
a := &app{}
if tc.lines != nil { if tc.lines != nil {
configFile, err := os.CreateTemp("", "*.toml") configFile, err := os.CreateTemp("", "*.toml")
require.NoError(t, err) require.NoError(t, err)
defer configFile.Close() defer configFile.Close()
filename = configFile.Name() filename = configFile.Name()
configflag = &filename a.configFile = filename
for _, line := range tc.lines { for _, line := range tc.lines {
_, err := configFile.WriteString(fmt.Sprintf("%s\n", line)) _, err := configFile.WriteString(fmt.Sprintf("%s\n", line))
@ -91,7 +92,7 @@ func TestGetHookConfig(t *testing.T) {
var config HookConfig var config HookConfig
getHookConfig := func() { getHookConfig := func() {
c, _ := getHookConfig() c, _ := a.getHookConfig()
config = *c config = *c
} }

View File

@ -1,7 +1,7 @@
package main package main
import ( import (
"flag" "errors"
"fmt" "fmt"
"log" "log"
"os" "os"
@ -13,29 +13,26 @@ import (
"strings" "strings"
"syscall" "syscall"
cli "github.com/urfave/cli/v2"
"github.com/NVIDIA/nvidia-container-toolkit/internal/config" "github.com/NVIDIA/nvidia-container-toolkit/internal/config"
"github.com/NVIDIA/nvidia-container-toolkit/internal/info" "github.com/NVIDIA/nvidia-container-toolkit/internal/info"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger" "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup" "github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
) )
var ( func (a *app) recoverIfRequired() error {
debugflag = flag.Bool("debug", false, "enable debug output")
versionflag = flag.Bool("version", false, "enable version output")
configflag = flag.String("config", "", "configuration file")
)
func exit() {
if err := recover(); err != nil { if err := recover(); err != nil {
if _, ok := err.(runtime.Error); ok { rerr, ok := err.(runtime.Error)
if ok {
log.Println(err) log.Println(err)
} }
if *debugflag { if a.isDebug {
log.Printf("%s", debug.Stack()) log.Printf("%s", debug.Stack())
} }
os.Exit(1) return rerr
} }
os.Exit(0) return nil
} }
func getCLIPath(config config.ContainerCLIConfig) string { func getCLIPath(config config.ContainerCLIConfig) string {
@ -63,15 +60,15 @@ func getRootfsPath(config containerConfig) string {
return rootfs return rootfs
} }
func doPrestart() { func (a *app) doPrestart() (rerr error) {
var err error defer func() {
rerr = errors.Join(rerr, a.recoverIfRequired())
defer exit() }()
log.SetFlags(0) log.SetFlags(0)
hook, err := getHookConfig() hook, err := a.getHookConfig()
if err != nil || hook == nil { if err != nil || hook == nil {
log.Panicln("error getting hook config:", err) return fmt.Errorf("error getting hook config: %w", err)
} }
cli := hook.NVIDIAContainerCLIConfig cli := hook.NVIDIAContainerCLIConfig
@ -79,11 +76,11 @@ func doPrestart() {
nvidia := container.Nvidia nvidia := container.Nvidia
if nvidia == nil { if nvidia == nil {
// Not a GPU container, nothing to do. // Not a GPU container, nothing to do.
return return nil
} }
if !hook.NVIDIAContainerRuntimeHookConfig.SkipModeDetection && info.ResolveAutoMode(&logInterceptor{}, hook.NVIDIAContainerRuntimeConfig.Mode, container.Image) != "legacy" { if !hook.NVIDIAContainerRuntimeHookConfig.SkipModeDetection && info.ResolveAutoMode(&logInterceptor{}, hook.NVIDIAContainerRuntimeConfig.Mode, container.Image) != "legacy" {
log.Panicln("invoking the NVIDIA Container Runtime Hook directly (e.g. specifying the docker --gpus flag) is not supported. Please use the NVIDIA Container Runtime (e.g. specify the --runtime=nvidia flag) instead.") return fmt.Errorf("invoking the NVIDIA Container Runtime Hook directly (e.g. specifying the docker --gpus flag) is not supported. Please use the NVIDIA Container Runtime (e.g. specify the --runtime=nvidia flag) instead")
} }
rootfs := getRootfsPath(container) rootfs := getRootfsPath(container)
@ -101,7 +98,7 @@ func doPrestart() {
if cli.NoPivot { if cli.NoPivot {
args = append(args, "--no-pivot") args = append(args, "--no-pivot")
} }
if *debugflag { if a.isDebug {
args = append(args, "--debug=/dev/stderr") args = append(args, "--debug=/dev/stderr")
} else if cli.Debug != "" { } else if cli.Debug != "" {
args = append(args, fmt.Sprintf("--debug=%s", cli.Debug)) args = append(args, fmt.Sprintf("--debug=%s", cli.Debug))
@ -149,45 +146,60 @@ func doPrestart() {
env := append(os.Environ(), cli.Environment...) env := append(os.Environ(), cli.Environment...)
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection? //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection?
err = syscall.Exec(args[0], args, env) return syscall.Exec(args[0], args, env)
log.Panicln("exec failed:", err)
} }
func usage() { type options struct {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) isDebug bool
flag.PrintDefaults() configFile string
fmt.Fprintf(os.Stderr, "\nCommands:\n") }
fmt.Fprintf(os.Stderr, " prestart\n run the prestart hook\n") type app struct {
fmt.Fprintf(os.Stderr, " poststart\n no-op\n") options
fmt.Fprintf(os.Stderr, " poststop\n no-op\n")
} }
func main() { func main() {
flag.Usage = usage a := &app{}
flag.Parse() // Create the top-level CLI
c := cli.NewApp()
c.Name = "NVIDIA Container Runtime Hook"
c.Version = info.GetVersionString()
if *versionflag { c.Flags = []cli.Flag{
fmt.Printf("%v version %v\n", "NVIDIA Container Runtime Hook", info.GetVersionString()) &cli.BoolFlag{
return Name: "debug",
Destination: &a.isDebug,
Usage: "Enabled debug output",
},
&cli.StringFlag{
Name: "config",
Destination: &a.configFile,
Usage: "The path to the configuration file to use",
},
} }
args := flag.Args() c.Commands = []*cli.Command{
if len(args) == 0 { {
flag.Usage() Name: "prestart",
os.Exit(2) Usage: "run the prestart hook",
Action: func(ctx *cli.Context) error {
return a.doPrestart()
},
},
{
Name: "poststart",
Aliases: []string{"poststop"},
Usage: "no-op",
Action: func(ctx *cli.Context) error {
return nil
},
},
} }
c.DefaultCommand = "prestart"
switch args[0] { // Run the CLI
case "prestart": err := c.Run(os.Args)
doPrestart() if err != nil {
os.Exit(0) os.Exit(1)
case "poststart":
fallthrough
case "poststop":
os.Exit(0)
default:
flag.Usage()
os.Exit(2)
} }
} }