Skip to content

Commit 20ff4e2

Browse files
author
Evan Lezar
committed
Merge branch 'generate-default-config-post-install' into 'main'
Ensure that default config is created on the file system as a post-install step See merge request nvidia/container-toolkit/container-toolkit!431
2 parents 2299c95 + f78d3a8 commit 20ff4e2

File tree

11 files changed

+366
-123
lines changed

11 files changed

+366
-123
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* Add option to load kernel modules when creating device nodes
1515
* Add option to create device nodes when creating `/dev/char` symlinks
1616
* Create ouput folders if required when running `nvidia-ctk runtime configure`
17-
17+
* Generate default config as post-install step.
1818

1919
* [libnvidia-container] Support OpenSSL 3 with the Encrypt/Decrypt library
2020

cmd/nvidia-ctk/config/create-default/create-default.go

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
package defaultsubcommand
1818

1919
import (
20+
"bytes"
2021
"fmt"
2122
"io"
2223
"os"
24+
"path/filepath"
25+
"regexp"
2326

24-
nvctkConfig "github.com/NVIDIA/nvidia-container-toolkit/internal/config"
27+
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
2528
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
2629
"github.com/urfave/cli/v2"
2730
)
@@ -32,7 +35,9 @@ type command struct {
3235

3336
// options stores the subcommand options
3437
type options struct {
35-
output string
38+
config string
39+
output string
40+
inPlace bool
3641
}
3742

3843
// NewCommand constructs a default command with the specified logger
@@ -61,9 +66,20 @@ func (m command) build() *cli.Command {
6166
}
6267

6368
c.Flags = []cli.Flag{
69+
&cli.StringFlag{
70+
Name: "config",
71+
Usage: "Specify the config file to process; The contents of this file overrides the default config",
72+
Destination: &opts.config,
73+
},
74+
&cli.BoolFlag{
75+
Name: "in-place",
76+
Aliases: []string{"i"},
77+
Usage: "Modify the config file in-place",
78+
Destination: &opts.inPlace,
79+
},
6480
&cli.StringFlag{
6581
Name: "output",
66-
Usage: "Specify the file to output the generated configuration for to. If this is '' the configuration is ouput to STDOUT.",
82+
Usage: "Specify the output file to write to; If not specified, the output is written to stdout",
6783
Destination: &opts.output,
6884
},
6985
}
@@ -72,31 +88,96 @@ func (m command) build() *cli.Command {
7288
}
7389

7490
func (m command) validateFlags(c *cli.Context, opts *options) error {
91+
if opts.inPlace {
92+
if opts.output != "" {
93+
return fmt.Errorf("cannot specify both --in-place and --output")
94+
}
95+
opts.output = opts.config
96+
}
7597
return nil
7698
}
7799

78100
func (m command) run(c *cli.Context, opts *options) error {
79-
defaultConfig, err := nvctkConfig.GetDefaultConfigToml()
101+
if err := opts.ensureOutputFolder(); err != nil {
102+
return fmt.Errorf("unable to create output directory: %v", err)
103+
}
104+
105+
contents, err := opts.getFormattedConfig()
80106
if err != nil {
81-
return fmt.Errorf("unable to get default config: %v", err)
107+
return fmt.Errorf("unable to fix comments: %v", err)
82108
}
83109

110+
if _, err := opts.Write(contents); err != nil {
111+
return fmt.Errorf("unable to write to output: %v", err)
112+
}
113+
114+
return nil
115+
}
116+
117+
// getFormattedConfig returns the default config formatted as required from the specified config file.
118+
// The config is then formatted as required.
119+
// No indentation is used and comments are modified so that there is no space
120+
// after the '#' character.
121+
func (opts options) getFormattedConfig() ([]byte, error) {
122+
cfg, err := config.Load(opts.config)
123+
if err != nil {
124+
return nil, fmt.Errorf("unable to load or create config: %v", err)
125+
}
126+
127+
buffer := bytes.NewBuffer(nil)
128+
129+
if _, err := cfg.Save(buffer); err != nil {
130+
return nil, fmt.Errorf("unable to save config: %v", err)
131+
}
132+
return fixComments(buffer.Bytes())
133+
}
134+
135+
func fixComments(contents []byte) ([]byte, error) {
136+
r, err := regexp.Compile(`(\n*)\s*?#\s*(\S.*)`)
137+
if err != nil {
138+
return nil, fmt.Errorf("unable to compile regexp: %v", err)
139+
}
140+
replaced := r.ReplaceAll(contents, []byte("$1#$2"))
141+
142+
return replaced, nil
143+
}
144+
145+
func (opts options) outputExists() (bool, error) {
146+
if opts.output == "" {
147+
return false, nil
148+
}
149+
_, err := os.Stat(opts.output)
150+
if err == nil {
151+
return true, nil
152+
} else if !os.IsNotExist(err) {
153+
return false, fmt.Errorf("unable to stat output file: %v", err)
154+
}
155+
return false, nil
156+
}
157+
158+
func (opts options) ensureOutputFolder() error {
159+
if opts.output == "" {
160+
return nil
161+
}
162+
if dir := filepath.Dir(opts.output); dir != "" {
163+
return os.MkdirAll(dir, 0755)
164+
}
165+
return nil
166+
}
167+
168+
// Write writes the contents to the output file specified in the options.
169+
func (opts options) Write(contents []byte) (int, error) {
84170
var output io.Writer
85171
if opts.output == "" {
86172
output = os.Stdout
87173
} else {
88174
outputFile, err := os.Create(opts.output)
89175
if err != nil {
90-
return fmt.Errorf("unable to create output file: %v", err)
176+
return 0, fmt.Errorf("unable to create output file: %v", err)
91177
}
92178
defer outputFile.Close()
93179
output = outputFile
94180
}
95181

96-
_, err = defaultConfig.WriteTo(output)
97-
if err != nil {
98-
return fmt.Errorf("unable to write to output: %v", err)
99-
}
100-
101-
return nil
182+
return output.Write(contents)
102183
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
# Copyright (c) NVIDIA CORPORATION. All rights reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
**/
16+
17+
package defaultsubcommand
18+
19+
import (
20+
"strings"
21+
"testing"
22+
23+
"github.com/stretchr/testify/require"
24+
)
25+
26+
func TestFixComment(t *testing.T) {
27+
testCases := []struct {
28+
input string
29+
expected string
30+
}{
31+
{
32+
input: "# comment",
33+
expected: "#comment",
34+
},
35+
{
36+
input: " #comment",
37+
expected: "#comment",
38+
},
39+
{
40+
input: " # comment",
41+
expected: "#comment",
42+
},
43+
{
44+
input: strings.Join([]string{
45+
"some",
46+
"# comment",
47+
" # comment",
48+
" #comment",
49+
"other"}, "\n"),
50+
expected: strings.Join([]string{
51+
"some",
52+
"#comment",
53+
"#comment",
54+
"#comment",
55+
"other"}, "\n"),
56+
},
57+
}
58+
59+
for _, tc := range testCases {
60+
t.Run(tc.input, func(t *testing.T) {
61+
actual, _ := fixComments([]byte(tc.input))
62+
require.Equal(t, tc.expected, string(actual))
63+
})
64+
}
65+
}
66+
67+
func TestGetFormattedConfig(t *testing.T) {
68+
expectedLines := []string{
69+
"#no-cgroups = false",
70+
"#debug = \"/var/log/nvidia-container-toolkit.log\"",
71+
"#debug = \"/var/log/nvidia-container-runtime.log\"",
72+
}
73+
74+
opts := &options{}
75+
contents, err := opts.getFormattedConfig()
76+
require.NoError(t, err)
77+
lines := strings.Split(string(contents), "\n")
78+
79+
for _, line := range expectedLines {
80+
require.Contains(t, lines, line)
81+
}
82+
}

cmd/nvidia-ctk/main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import (
3636
type options struct {
3737
// Debug indicates whether the CLI is started in "debug" mode
3838
Debug bool
39+
// Quiet indicates whether the CLI is started in "quiet" mode
40+
Quiet bool
3941
}
4042

4143
func main() {
@@ -61,6 +63,12 @@ func main() {
6163
Destination: &opts.Debug,
6264
EnvVars: []string{"NVIDIA_CTK_DEBUG"},
6365
},
66+
&cli.BoolFlag{
67+
Name: "quiet",
68+
Usage: "Suppress all output except for errors; overrides --debug",
69+
Destination: &opts.Quiet,
70+
EnvVars: []string{"NVIDIA_CTK_QUIET"},
71+
},
6472
}
6573

6674
// Set log-level for all subcommands
@@ -69,6 +77,9 @@ func main() {
6977
if opts.Debug {
7078
logLevel = logrus.DebugLevel
7179
}
80+
if opts.Quiet {
81+
logLevel = logrus.ErrorLevel
82+
}
7283
logger.SetLevel(logLevel)
7384
return nil
7485
}

internal/config/cli.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ package config
1818

1919
// ContainerCLIConfig stores the options for the nvidia-container-cli
2020
type ContainerCLIConfig struct {
21-
Root string `toml:"root"`
21+
Root string `toml:"root"`
22+
LoadKmods bool `toml:"load-kmods"`
23+
Ldconfig string `toml:"ldconfig"`
2224
}

0 commit comments

Comments
 (0)