diff --git a/cmd/nvidia-ctk/README.md b/cmd/nvidia-ctk/README.md index 1080e2f5..6a94a082 100644 --- a/cmd/nvidia-ctk/README.md +++ b/cmd/nvidia-ctk/README.md @@ -1,3 +1,17 @@ # NVIDIA Container Toolkit CLI The NVIDIA Container Toolkit CLI `nvidia-ctk` provides a number of utilities that are useful for working with the NVIDIA Container Toolkit. + +## Functionality + +### Configure runtimes + +The `runtime` command of the `nvidia-ctk` CLI provides a set of utilities to related to the configuration +and management of supported container engines. + +For example, running the following command: +```bash +nvidia-ctk runtime configure --set-as-default +``` +will ensure that the NVIDIA Container Runtime is added as the default runtime to the default container +engine. diff --git a/cmd/nvidia-ctk/main.go b/cmd/nvidia-ctk/main.go index c635e3cc..ba07a719 100644 --- a/cmd/nvidia-ctk/main.go +++ b/cmd/nvidia-ctk/main.go @@ -20,6 +20,7 @@ import ( "os" "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/hook" + "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/runtime" "github.com/NVIDIA/nvidia-container-toolkit/internal/info" log "github.com/sirupsen/logrus" cli "github.com/urfave/cli/v2" @@ -70,6 +71,7 @@ func main() { // Define the subcommands c.Commands = []*cli.Command{ hook.NewCommand(logger), + runtime.NewCommand(logger), } // Run the CLI diff --git a/cmd/nvidia-ctk/runtime/configure/configure.go b/cmd/nvidia-ctk/runtime/configure/configure.go new file mode 100644 index 00000000..6788b751 --- /dev/null +++ b/cmd/nvidia-ctk/runtime/configure/configure.go @@ -0,0 +1,154 @@ +/** +# 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 ( + "encoding/json" + "fmt" + "os" + + "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/runtime/nvidia" + "github.com/NVIDIA/nvidia-container-toolkit/internal/config/docker" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +const ( + defaultRuntime = "docker" + + defaultDockerConfigFilePath = "/etc/docker/daemon.json" +) + +type command struct { + logger *logrus.Logger +} + +// NewCommand constructs an configure command with the specified logger +func NewCommand(logger *logrus.Logger) *cli.Command { + 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 + nvidiaOptions nvidia.Options +} + +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", + 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", + Usage: "the target runtime engine. One of [docker]", + Value: defaultRuntime, + Destination: &config.runtime, + }, + &cli.StringFlag{ + Name: "config", + Usage: "path to the config file for the target runtime", + Destination: &config.configFilePath, + }, + &cli.StringFlag{ + Name: "nvidia-runtime-name", + Usage: "specify the name of the NVIDIA runtime that will be added", + Value: nvidia.RuntimeName, + Destination: &config.nvidiaOptions.RuntimeName, + }, + &cli.StringFlag{ + Name: "runtime-path", + Usage: "specify the path to the NVIDIA runtime executable", + Value: nvidia.RuntimeExecutable, + Destination: &config.nvidiaOptions.RuntimePath, + }, + &cli.BoolFlag{ + Name: "set-as-default", + Usage: "set the specified runtime as the default runtime", + Destination: &config.nvidiaOptions.SetAsDefault, + }, + } + + return &configure +} + +func (m command) configureWrapper(c *cli.Context, config *config) error { + switch config.runtime { + case "docker": + return m.configureDocker(c, config) + } + + return fmt.Errorf("unrecognized runtime '%v'", config.runtime) +} + +// configureDocker updates the docker config to enable the NVIDIA Container Runtime +func (m command) configureDocker(c *cli.Context, config *config) error { + configFilePath := config.configFilePath + if configFilePath == "" { + configFilePath = defaultDockerConfigFilePath + } + + cfg, err := docker.LoadConfig(configFilePath) + if err != nil { + return fmt.Errorf("unable to load config: %v", err) + } + + defaultRuntime := config.nvidiaOptions.DefaultRuntime() + runtimeConfig := config.nvidiaOptions.Runtime().DockerRuntimesConfig() + err = docker.UpdateConfig(cfg, defaultRuntime, runtimeConfig) + if err != nil { + return fmt.Errorf("unable to update config: %v", err) + } + + if config.dryRun { + output, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return fmt.Errorf("unable to convert to JSON: %v", err) + } + os.Stdout.WriteString(fmt.Sprintf("%s\n", output)) + return nil + } + err = docker.FlushConfig(cfg, configFilePath) + if err != nil { + return fmt.Errorf("unable to flush config: %v", err) + } + + m.logger.Infof("Wrote updated config to %v", configFilePath) + m.logger.Infof("It is recommended that the docker daemon be restarted.") + + return nil +} diff --git a/cmd/nvidia-ctk/runtime/nvidia/nvidia.go b/cmd/nvidia-ctk/runtime/nvidia/nvidia.go new file mode 100644 index 00000000..1f425146 --- /dev/null +++ b/cmd/nvidia-ctk/runtime/nvidia/nvidia.go @@ -0,0 +1,75 @@ +/* +# 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 nvidia + +const ( + // RuntimeName is the default name to use in configs for the NVIDIA Container Runtime + RuntimeName = "nvidia" + // RuntimeExecutable is the default NVIDIA Container Runtime executable file name + RuntimeExecutable = "nvidia-container-runtime" +) + +// Options specifies the options for the NVIDIA Container Runtime w.r.t a container engine such as docker. +type Options struct { + SetAsDefault bool + RuntimeName string + RuntimePath string +} + +// Runtime defines an NVIDIA runtime with a name and a executable +type Runtime struct { + Name string + Path string +} + +// DefaultRuntime returns the default runtime for the configured options. +// If the configuration is invalid or the default runtimes should not be set +// the empty string is returned. +func (o Options) DefaultRuntime() string { + if !o.SetAsDefault { + return "" + } + + return o.RuntimeName +} + +// Runtime creates a runtime struct based on the options. +func (o Options) Runtime() Runtime { + path := o.RuntimePath + + if o.RuntimePath == "" { + path = RuntimeExecutable + } + + r := Runtime{ + Name: o.RuntimeName, + Path: path, + } + + return r +} + +// DockerRuntimesConfig generatest the expected docker config for the specified runtime +func (r Runtime) DockerRuntimesConfig() map[string]interface{} { + runtimes := make(map[string]interface{}) + runtimes[r.Name] = map[string]interface{}{ + "path": r.Path, + "args": []string{}, + } + + return runtimes +} diff --git a/cmd/nvidia-ctk/runtime/runtime.go b/cmd/nvidia-ctk/runtime/runtime.go new file mode 100644 index 00000000..e760f30a --- /dev/null +++ b/cmd/nvidia-ctk/runtime/runtime.go @@ -0,0 +1,49 @@ +/** +# 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 runtime + +import ( + "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/runtime/configure" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +type runtimeCommand struct { + logger *logrus.Logger +} + +// NewCommand constructs a runtime command with the specified logger +func NewCommand(logger *logrus.Logger) *cli.Command { + c := runtimeCommand{ + logger: logger, + } + return c.build() +} + +func (m runtimeCommand) build() *cli.Command { + // Create the 'runtime' command + runtime := cli.Command{ + Name: "runtime", + Usage: "A collection of runtime-related utilities for the NVIDIA Container Toolkit", + } + + runtime.Subcommands = []*cli.Command{ + configure.NewCommand(m.logger), + } + + return &runtime +}