diff --git a/cmd/nvidia-ctk-installer/container/runtime/runtime.go b/cmd/nvidia-ctk-installer/container/runtime/runtime.go index 950e1aeca..ed63d3c59 100644 --- a/cmd/nvidia-ctk-installer/container/runtime/runtime.go +++ b/cmd/nvidia-ctk-installer/container/runtime/runtime.go @@ -138,6 +138,15 @@ func Flags(opts *Options) []cli.Flag { // Validate checks whether the specified options are valid func (opts *Options) Validate(logger logger.Interface, c *cli.Command, runtime string, toolkitRoot string, to *toolkit.Options) error { + // Check that the specified runtime is supported. + switch runtime { + case containerd.Name: + case crio.Name: + case docker.Name: + default: + return fmt.Errorf("invalid runtime %q; expected one of [containerd | crio | docker]", runtime) + } + // We set this option here to ensure that it is available in future calls. opts.RuntimeDir = toolkitRoot @@ -212,8 +221,25 @@ func (opts *Options) Validate(logger logger.Interface, c *cli.Command, runtime s return nil } -func Setup(c *cli.Command, opts *Options, runtime string) error { - switch runtime { +type Configurer interface { + Cleanup(*cli.Command, *Options) error + GetLowlevelRuntimePaths(*Options) ([]string, error) + Setup(*cli.Command, *Options) error +} + +type runtime string +type noopRuntimeConfigurer struct{} + +// NewConfigurer is a factory method for creating a runtime configurer. +func NewConfigurer(name string, noConfigureRuntime bool, nriEnabled bool) Configurer { + if noConfigureRuntime || nriEnabled { + return &noopRuntimeConfigurer{} + } + return runtime(name) +} + +func (r runtime) Setup(c *cli.Command, opts *Options) error { + switch string(r) { case containerd.Name: return containerd.Setup(c, &opts.Options, &opts.containerdOptions) case crio.Name: @@ -221,12 +247,12 @@ func Setup(c *cli.Command, opts *Options, runtime string) error { case docker.Name: return docker.Setup(c, &opts.Options) default: - return fmt.Errorf("undefined runtime %v", runtime) + return fmt.Errorf("undefined runtime %v", r) } } -func Cleanup(c *cli.Command, opts *Options, runtime string) error { - switch runtime { +func (r runtime) Cleanup(c *cli.Command, opts *Options) error { + switch string(r) { case containerd.Name: return containerd.Cleanup(c, &opts.Options, &opts.containerdOptions) case crio.Name: @@ -234,12 +260,12 @@ func Cleanup(c *cli.Command, opts *Options, runtime string) error { case docker.Name: return docker.Cleanup(c, &opts.Options) default: - return fmt.Errorf("undefined runtime %v", runtime) + return fmt.Errorf("undefined runtime %v", r) } } -func GetLowlevelRuntimePaths(opts *Options, runtime string) ([]string, error) { - switch runtime { +func (r runtime) GetLowlevelRuntimePaths(opts *Options) ([]string, error) { + switch string(r) { case containerd.Name: return containerd.GetLowlevelRuntimePaths(&opts.Options, &opts.containerdOptions) case crio.Name: @@ -247,6 +273,18 @@ func GetLowlevelRuntimePaths(opts *Options, runtime string) ([]string, error) { case docker.Name: return docker.GetLowlevelRuntimePaths(&opts.Options) default: - return nil, fmt.Errorf("undefined runtime %v", runtime) + return nil, fmt.Errorf("undefined runtime %v", r) } } + +func (r noopRuntimeConfigurer) Cleanup(_ *cli.Command, _ *Options) error { + return nil +} + +func (r noopRuntimeConfigurer) GetLowlevelRuntimePaths(_ *Options) ([]string, error) { + return nil, nil +} + +func (r noopRuntimeConfigurer) Setup(_ *cli.Command, _ *Options) error { + return nil +} diff --git a/cmd/nvidia-ctk-installer/main.go b/cmd/nvidia-ctk-installer/main.go index 8b4cd3629..ece435737 100644 --- a/cmd/nvidia-ctk-installer/main.go +++ b/cmd/nvidia-ctk-installer/main.go @@ -33,7 +33,6 @@ const ( defaultNRIPluginIdx uint = 10 ) -var availableRuntimes = map[string]struct{}{"docker": {}, "crio": {}, "containerd": {}} var defaultLowLevelRuntimes = []string{"runc", "crun"} var waitingForSignal = make(chan bool, 1) @@ -44,7 +43,6 @@ type options struct { toolkitInstallDir string noDaemon bool - runtime string pidFile string sourceRoot string packageType string @@ -54,7 +52,10 @@ type options struct { nriSocket string toolkitOptions toolkit.Options - runtimeOptions runtime.Options + + noRuntimeConfig bool + runtime string + runtimeOptions runtime.Options } func (o options) toolkitRoot() string { @@ -113,6 +114,14 @@ func (a app) build() *cli.Command { Destination: &options.noDaemon, Sources: cli.EnvVars("NO_DAEMON"), }, + &cli.BoolFlag{ + Name: "no-runtime-config", + Usage: "Disables the configuration of a container runtime. This is used in cases where the runtime has " + + "already been configured for use with the toolkit, and the installer is only used to deploy the " + + "components of the NVIDIA Container Toolkit.", + Destination: &options.noRuntimeConfig, + Sources: cli.EnvVars("NO_RUNTIME_CONFIG"), + }, &cli.BoolFlag{ Name: "enable-nri-plugin", Aliases: []string{"p"}, @@ -142,9 +151,10 @@ func (a app) build() *cli.Command { Sources: cli.EnvVars("NRI_SOCKET"), }, &cli.StringFlag{ - Name: "runtime", - Aliases: []string{"r"}, - Usage: "the runtime to setup on this node. One of {'docker', 'crio', 'containerd'}", + Name: "runtime", + Aliases: []string{"r"}, + Usage: "the runtime to setup on this node. One of {'docker', 'crio', 'containerd'}. " + + "This setting is ignored if --no-runtime-config is specified.", Value: defaultRuntime, Destination: &options.runtime, Sources: cli.EnvVars("RUNTIME"), @@ -212,9 +222,6 @@ func (a *app) validateFlags(c *cli.Command, o *options) error { if o.toolkitInstallDir == "" { return fmt.Errorf("the install root must be specified") } - if _, exists := availableRuntimes[o.runtime]; !exists { - return fmt.Errorf("unknown runtime: %v", o.runtime) - } if filepath.Base(o.pidFile) != toolkitPidFilename { return fmt.Errorf("invalid toolkit.pid path %v", o.pidFile) } @@ -222,9 +229,11 @@ func (a *app) validateFlags(c *cli.Command, o *options) error { if err := a.toolkit.ValidateOptions(&o.toolkitOptions); err != nil { return err } + if err := o.runtimeOptions.Validate(a.logger, c, o.runtime, o.toolkitRoot(), &o.toolkitOptions); err != nil { return err } + return nil } @@ -238,8 +247,10 @@ func (a *app) Run(ctx context.Context, c *cli.Command, o *options) error { } defer a.shutdown(o.pidFile) + runtimeConfigurer := runtime.NewConfigurer(o.runtime, o.noRuntimeConfig, o.enableNRIPlugin) + if len(o.toolkitOptions.ContainerRuntimeRuntimes) == 0 { - lowlevelRuntimePaths, err := runtime.GetLowlevelRuntimePaths(&o.runtimeOptions, o.runtime) + lowlevelRuntimePaths, err := runtimeConfigurer.GetLowlevelRuntimePaths(&o.runtimeOptions) if err != nil { return fmt.Errorf("unable to determine runtime options: %w", err) } @@ -253,33 +264,31 @@ func (a *app) Run(ctx context.Context, c *cli.Command, o *options) error { return fmt.Errorf("unable to install toolkit: %v", err) } - if !o.enableNRIPlugin { - err = runtime.Setup(c, &o.runtimeOptions, o.runtime) - if err != nil { - return fmt.Errorf("unable to setup runtime: %w", err) - } + err = runtimeConfigurer.Setup(c, &o.runtimeOptions) + if err != nil { + return fmt.Errorf("unable to setup runtime: %w", err) } - if !o.noDaemon { - if o.enableNRIPlugin { - nriPlugin, err := a.startNRIPluginServer(ctx, o) - if err != nil { - a.logger.Errorf("unable to start NRI plugin server: %v", err) - } - defer nriPlugin.Stop() - } + if o.noDaemon { + return nil + } - err = a.waitForSignal() + if o.enableNRIPlugin { + nriPlugin, err := a.startNRIPluginServer(ctx, o) if err != nil { - return fmt.Errorf("unable to wait for signal: %v", err) + a.logger.Errorf("unable to start NRI plugin server: %v", err) } + defer nriPlugin.Stop() + } - if !o.enableNRIPlugin { - err = runtime.Cleanup(c, &o.runtimeOptions, o.runtime) - if err != nil { - return fmt.Errorf("unable to cleanup runtime: %v", err) - } - } + err = a.waitForSignal() + if err != nil { + return fmt.Errorf("unable to wait for signal: %v", err) + } + + err = runtimeConfigurer.Cleanup(c, &o.runtimeOptions) + if err != nil { + return fmt.Errorf("unable to cleanup runtime: %v", err) } return nil