Skip to content

Commit a65255c

Browse files
committed
fetch current container runtime config through the command line
Signed-off-by: Tariq Ibrahim <[email protected]> add default runtime binary path to runtimes field of toolkit config toml Signed-off-by: Tariq Ibrahim <[email protected]>
1 parent 4604e3b commit a65255c

File tree

18 files changed

+558
-27
lines changed

18 files changed

+558
-27
lines changed

cmd/nvidia-ctk/runtime/configure/configure.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ const (
4444
defaultContainerdConfigFilePath = "/etc/containerd/config.toml"
4545
defaultCrioConfigFilePath = "/etc/crio/crio.conf"
4646
defaultDockerConfigFilePath = "/etc/docker/daemon.json"
47+
48+
defaultConfigSource = configSourceFile
49+
configSourceCommand = "command"
50+
configSourceFile = "file"
4751
)
4852

4953
type command struct {
@@ -64,6 +68,7 @@ type config struct {
6468
dryRun bool
6569
runtime string
6670
configFilePath string
71+
configSource string
6772
mode string
6873
hookFilePath string
6974

@@ -120,6 +125,12 @@ func (m command) build() *cli.Command {
120125
Usage: "the config mode for runtimes that support multiple configuration mechanisms",
121126
Destination: &config.mode,
122127
},
128+
&cli.StringFlag{
129+
Name: "config-source",
130+
Usage: "the source to retrieve the container runtime configuration; one of [command, file]\"",
131+
Destination: &config.configSource,
132+
Value: defaultConfigSource,
133+
},
123134
&cli.StringFlag{
124135
Name: "oci-hook-path",
125136
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.",
@@ -202,6 +213,18 @@ func (m command) validateFlags(c *cli.Context, config *config) error {
202213
config.runtimeConfigOverrideJSON = ""
203214
}
204215

216+
switch config.configSource {
217+
case configSourceCommand:
218+
if config.runtime == "docker" {
219+
m.logger.Warningf("A %v Config Source is not supported for %v; using %v", config.configSource, config.runtime, configSourceFile)
220+
config.configSource = configSourceFile
221+
}
222+
case configSourceFile:
223+
break
224+
default:
225+
return fmt.Errorf("unrecognized Config Source: %v", config.configSource)
226+
}
227+
205228
return nil
206229
}
207230

@@ -220,20 +243,25 @@ func (m command) configureWrapper(c *cli.Context, config *config) error {
220243
func (m command) configureConfigFile(c *cli.Context, config *config) error {
221244
configFilePath := config.resolveConfigFilePath()
222245

223-
var cfg engine.Interface
224246
var err error
247+
configSource, err := config.resolveConfigSource()
248+
if err != nil {
249+
return err
250+
}
251+
252+
var cfg engine.Interface
225253
switch config.runtime {
226254
case "containerd":
227255
cfg, err = containerd.New(
228256
containerd.WithLogger(m.logger),
229257
containerd.WithPath(configFilePath),
230-
containerd.WithConfigSource(toml.FromFile(configFilePath)),
258+
containerd.WithConfigSource(configSource),
231259
)
232260
case "crio":
233261
cfg, err = crio.New(
234262
crio.WithLogger(m.logger),
235263
crio.WithPath(configFilePath),
236-
crio.WithConfigSource(toml.FromFile(configFilePath)),
264+
crio.WithConfigSource(configSource),
237265
)
238266
case "docker":
239267
cfg, err = docker.New(
@@ -295,6 +323,29 @@ func (c *config) resolveConfigFilePath() string {
295323
return ""
296324
}
297325

326+
// resolveConfigSource returns the default config source or the user provided config source
327+
func (c *config) resolveConfigSource() (toml.Loader, error) {
328+
switch c.configSource {
329+
case configSourceCommand:
330+
return c.getCommandConfigSource(), nil
331+
case configSourceFile:
332+
return toml.FromFile(c.configFilePath), nil
333+
default:
334+
return nil, fmt.Errorf("unrecognized config source: %s", c.configSource)
335+
}
336+
}
337+
338+
// getConfigSourceCommand returns the default cli command to fetch the current runtime config
339+
func (c *config) getCommandConfigSource() toml.Loader {
340+
switch c.runtime {
341+
case "containerd":
342+
return containerd.CommandLineSource("")
343+
case "crio":
344+
return crio.CommandLineSource("")
345+
}
346+
return toml.Empty
347+
}
348+
298349
// getOuputConfigPath returns the configured config path or "" if dry-run is enabled
299350
func (c *config) getOuputConfigPath() string {
300351
if c.dryRun {

internal/config/toml.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,26 @@ func (t *Toml) Get(key string) interface{} {
170170
return (*toml.Tree)(t).Get(key)
171171
}
172172

173+
// GetDefault returns the value for the specified key and falls back to the default value if the Get call fails
174+
func (t *Toml) GetDefault(key string, def interface{}) interface{} {
175+
val := t.Get(key)
176+
if val == nil {
177+
return def
178+
}
179+
return val
180+
}
181+
173182
// Set sets the specified key to the specified value in the TOML config.
174183
func (t *Toml) Set(key string, value interface{}) {
175184
(*toml.Tree)(t).Set(key, value)
176185
}
177186

187+
// WriteTo encode the Tree as Toml and writes it to the writer w.
188+
// Returns the number of bytes written in case of success, or an error if anything happened.
189+
func (t *Toml) WriteTo(w io.Writer) (int64, error) {
190+
return (*toml.Tree)(t).WriteTo(w)
191+
}
192+
178193
// commentDefaults applies the required comments for default values to the Toml.
179194
func (t *Toml) commentDefaults() *Toml {
180195
asToml := (*toml.Tree)(t)

pkg/config/engine/api.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,31 @@ type Interface interface {
2323
Set(string, interface{})
2424
RemoveRuntime(string) error
2525
Save(string) (int64, error)
26+
GetRuntimeConfig(string) (RuntimeConfig, error)
27+
}
28+
29+
// RuntimeConfig defines the interface to query container runtime handler configuration
30+
type RuntimeConfig interface {
31+
GetBinaryPath() string
32+
}
33+
34+
func GetBinaryPathsForRuntimes(cfg Interface, runtimes ...string) []string {
35+
var binaryPaths []string
36+
37+
seen := make(map[string]bool)
38+
for _, runtime := range runtimes {
39+
runtimeConfig, err := cfg.GetRuntimeConfig(runtime)
40+
if err != nil {
41+
// TODO: It will be useful to log the error when GetRuntimeConfig fails for a runtime
42+
continue
43+
}
44+
binaryPath := runtimeConfig.GetBinaryPath()
45+
if binaryPath == "" || seen[binaryPath] {
46+
continue
47+
}
48+
seen[binaryPath] = true
49+
binaryPaths = append(binaryPaths, binaryPath)
50+
}
51+
52+
return binaryPaths
2653
}

pkg/config/engine/containerd/config_v1.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ package containerd
1919
import (
2020
"fmt"
2121

22-
"github.com/pelletier/go-toml"
23-
2422
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine"
23+
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
2524
)
2625

2726
// ConfigV1 represents a version 1 containerd config
@@ -157,3 +156,14 @@ func (c *ConfigV1) Set(key string, value interface{}) {
157156
func (c ConfigV1) Save(path string) (int64, error) {
158157
return (Config)(c).Save(path)
159158
}
159+
160+
func (c *ConfigV1) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
161+
if c == nil || c.Tree == nil {
162+
return nil, fmt.Errorf("config is nil")
163+
}
164+
runtimeData := c.GetSubtreeByPath([]string{"plugins", "cri", "containerd", "runtimes", name})
165+
166+
return &containerdCfgRuntime{
167+
tree: runtimeData,
168+
}, nil
169+
}

pkg/config/engine/containerd/config_v2_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,99 @@ func TestAddRuntime(t *testing.T) {
216216
})
217217
}
218218
}
219+
220+
func TestGetRuntimeConfig(t *testing.T) {
221+
logger, _ := testlog.NewNullLogger()
222+
config := `
223+
version = 2
224+
[plugins]
225+
[plugins."io.containerd.grpc.v1.cri"]
226+
[plugins."io.containerd.grpc.v1.cri".containerd]
227+
default_runtime_name = "nvidia"
228+
disable_snapshot_annotations = true
229+
discard_unpacked_layers = false
230+
ignore_blockio_not_enabled_errors = false
231+
ignore_rdt_not_enabled_errors = false
232+
no_pivot = false
233+
snapshotter = "overlayfs"
234+
235+
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
236+
base_runtime_spec = ""
237+
cni_conf_dir = ""
238+
cni_max_conf_num = 0
239+
container_annotations = []
240+
pod_annotations = []
241+
privileged_without_host_devices = false
242+
privileged_without_host_devices_all_devices_allowed = false
243+
runtime_engine = ""
244+
runtime_path = ""
245+
runtime_root = ""
246+
runtime_type = ""
247+
sandbox_mode = ""
248+
snapshotter = ""
249+
250+
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
251+
252+
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
253+
base_runtime_spec = ""
254+
cni_conf_dir = ""
255+
cni_max_conf_num = 0
256+
container_annotations = []
257+
pod_annotations = []
258+
privileged_without_host_devices = false
259+
privileged_without_host_devices_all_devices_allowed = false
260+
runtime_engine = ""
261+
runtime_path = ""
262+
runtime_root = ""
263+
runtime_type = "io.containerd.runc.v2"
264+
sandbox_mode = "podsandbox"
265+
snapshotter = ""
266+
267+
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
268+
BinaryName = "/usr/bin/runc"
269+
CriuImagePath = ""
270+
CriuPath = ""
271+
CriuWorkPath = ""
272+
IoGid = 0
273+
IoUid = 0
274+
NoNewKeyring = false
275+
NoPivotRoot = false
276+
Root = ""
277+
ShimCgroup = ""
278+
SystemdCgroup = false
279+
`
280+
testCases := []struct {
281+
description string
282+
runtime string
283+
expected string
284+
expectedError error
285+
}{
286+
{
287+
description: "valid runtime config, existing runtime",
288+
runtime: "runc",
289+
expected: "/usr/bin/runc",
290+
expectedError: nil,
291+
},
292+
{
293+
description: "valid runtime config, non-existing runtime",
294+
runtime: "some-other-runtime",
295+
expected: "",
296+
expectedError: nil,
297+
},
298+
}
299+
300+
for _, tc := range testCases {
301+
t.Run(tc.description, func(t *testing.T) {
302+
cfg, err := toml.Load(config)
303+
require.NoError(t, err)
304+
305+
c := &Config{
306+
Logger: logger,
307+
Tree: cfg,
308+
}
309+
rc, err := c.GetRuntimeConfig(tc.runtime)
310+
require.Equal(t, tc.expectedError, err)
311+
require.Equal(t, tc.expected, rc.GetBinaryPath())
312+
})
313+
}
314+
}

pkg/config/engine/containerd/containerd.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ type Config struct {
3535

3636
var _ engine.Interface = (*Config)(nil)
3737

38+
type containerdCfgRuntime struct {
39+
tree *toml.Tree
40+
}
41+
42+
var _ engine.RuntimeConfig = (*containerdCfgRuntime)(nil)
43+
44+
// GetBinaryPath retrieves the path to the actual low-level runtime binary invoked by the runtime handler
45+
func (c *containerdCfgRuntime) GetBinaryPath() string {
46+
if c == nil || c.tree == nil {
47+
return ""
48+
}
49+
50+
binPath, _ := c.tree.GetPath([]string{"options", "BinaryName"}).(string)
51+
return binPath
52+
}
53+
3854
// New creates a containerd config with the specified options
3955
func New(opts ...Option) (engine.Interface, error) {
4056
b := &builder{
@@ -98,3 +114,27 @@ func (c *Config) parseVersion(useLegacyConfig bool) (int, error) {
98114
return -1, fmt.Errorf("unsupported type for version field: %v", v)
99115
}
100116
}
117+
118+
func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
119+
if c == nil || c.Tree == nil {
120+
return nil, fmt.Errorf("config is nil")
121+
}
122+
runtimeData := c.GetSubtreeByPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name})
123+
return &containerdCfgRuntime{
124+
tree: runtimeData,
125+
}, nil
126+
}
127+
128+
// CommandLineSource returns the CLI-based containerd config loader
129+
func CommandLineSource(hostRoot string) toml.Loader {
130+
commandLine := chrootIfRequired(hostRoot, "containerd", "config", "dump")
131+
return toml.FromCommandLine(commandLine[0], commandLine[1:]...)
132+
}
133+
134+
func chrootIfRequired(hostRoot string, commandLine ...string) []string {
135+
if hostRoot == "" || hostRoot == "/" {
136+
return commandLine
137+
}
138+
139+
return append([]string{"chroot", hostRoot}, commandLine...)
140+
}

0 commit comments

Comments
 (0)