Skip to content

Commit cce7ffd

Browse files
committed
Add support for drop-in config
Signed-off-by: Harshal Patil <[email protected]>
1 parent 1fd0dc1 commit cce7ffd

8 files changed

+225
-19
lines changed

types/options.go

+98-2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ var (
4949
defaultConfigFile = SystemConfigFile
5050
// DefaultStoreOptions is a reasonable default set of options.
5151
defaultStoreOptions StoreOptions
52+
53+
// defaultOverrideConfigFile path to override the default system wide storage.conf file
54+
defaultOverrideConfigFile = "/etc/containers/storage.conf"
55+
56+
// defaultDropInConfigDir path to the folder containing drop in config files
57+
defaultDropInConfigDir = "/etc/containers/storage.conf.d"
5258
)
5359

5460
func loadDefaultStoreOptions() {
@@ -114,11 +120,101 @@ func loadDefaultStoreOptions() {
114120

115121
// loadStoreOptions returns the default storage ops for containers
116122
func loadStoreOptions() (StoreOptions, error) {
117-
storageConf, err := DefaultConfigFile()
123+
baseConf, err := DefaultConfigFile()
124+
if err != nil {
125+
return defaultStoreOptions, err
126+
}
127+
128+
// Load the base config file
129+
baseOptions, err := loadStoreOptionsFromConfFile(baseConf)
130+
if err != nil {
131+
return defaultStoreOptions, err
132+
}
133+
134+
if _, err := os.Stat(defaultDropInConfigDir); err != nil && os.IsNotExist(err) {
135+
return defaultStoreOptions, err
136+
}
137+
138+
baseOptions, err = mergeConfigFromDirectory(baseOptions, defaultDropInConfigDir)
118139
if err != nil {
119140
return defaultStoreOptions, err
120141
}
121-
return loadStoreOptionsFromConfFile(storageConf)
142+
143+
return baseOptions, nil
144+
}
145+
146+
func mergeConfigFromDirectory(baseOptions StoreOptions, configDir string) (StoreOptions, error) {
147+
err := filepath.Walk(configDir, func(path string, info os.FileInfo, err error) error {
148+
if err != nil {
149+
return err
150+
}
151+
if info.IsDir() {
152+
return nil
153+
}
154+
155+
// Load drop-in options from the current file
156+
dropInOptions, err := loadStoreOptionsFromConfFile(path)
157+
if err != nil {
158+
return err
159+
}
160+
161+
// Merge the drop-in options into the base options
162+
baseOptions = mergeStoreOptions(baseOptions, dropInOptions)
163+
return nil
164+
})
165+
if err != nil {
166+
return baseOptions, err
167+
}
168+
return baseOptions, nil
169+
}
170+
171+
func mergeStoreOptions(base, dropIn StoreOptions) StoreOptions {
172+
if dropIn.RunRoot != "" {
173+
base.RunRoot = dropIn.RunRoot
174+
}
175+
if dropIn.GraphRoot != "" {
176+
base.GraphRoot = dropIn.GraphRoot
177+
}
178+
if dropIn.ImageStore != "" {
179+
base.ImageStore = dropIn.ImageStore
180+
}
181+
if dropIn.RootlessStoragePath != "" {
182+
base.RootlessStoragePath = dropIn.RootlessStoragePath
183+
}
184+
if dropIn.GraphDriverName != "" {
185+
base.GraphDriverName = dropIn.GraphDriverName
186+
}
187+
if dropIn.RootAutoNsUser != "" {
188+
base.RootAutoNsUser = dropIn.RootAutoNsUser
189+
}
190+
191+
base.GraphDriverPriority = appendUniqueStrings(base.GraphDriverPriority, dropIn.GraphDriverPriority)
192+
base.GraphDriverOptions = appendUniqueStrings(base.GraphDriverOptions, dropIn.GraphDriverOptions)
193+
base.UIDMap = appendUniqueIDMaps(base.UIDMap, dropIn.UIDMap)
194+
base.GIDMap = appendUniqueIDMaps(base.GIDMap, dropIn.GIDMap)
195+
196+
// For map fields, simply merge the key-value pairs.
197+
for key, value := range dropIn.PullOptions {
198+
base.PullOptions[key] = value
199+
}
200+
201+
// For boolean fields, use the drop-in value if it changes the default (assumed false).
202+
if dropIn.DisableVolatile {
203+
base.DisableVolatile = dropIn.DisableVolatile
204+
}
205+
if dropIn.TransientStore {
206+
base.TransientStore = dropIn.TransientStore
207+
}
208+
209+
// For numeric fields, use non-zero values from drop-in.
210+
if dropIn.AutoNsMinSize != 0 {
211+
base.AutoNsMinSize = dropIn.AutoNsMinSize
212+
}
213+
if dropIn.AutoNsMaxSize != 0 {
214+
base.AutoNsMaxSize = dropIn.AutoNsMaxSize
215+
}
216+
217+
return base
122218
}
123219

124220
// usePerUserStorage returns whether the user private storage must be used.

types/options_darwin.go

-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ const (
88
SystemConfigFile = "/usr/share/containers/storage.conf"
99
)
1010

11-
var defaultOverrideConfigFile = "/etc/containers/storage.conf"
12-
1311
// canUseRootlessOverlay returns true if the overlay driver can be used for rootless containers
1412
func canUseRootlessOverlay(home, runhome string) bool {
1513
return false

types/options_freebsd.go

-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ const (
88
SystemConfigFile = "/usr/local/share/containers/storage.conf"
99
)
1010

11-
// defaultConfigFile path to the system wide storage.conf file
12-
var (
13-
defaultOverrideConfigFile = "/usr/local/etc/containers/storage.conf"
14-
)
15-
1611
// canUseRootlessOverlay returns true if the overlay driver can be used for rootless containers
1712
func canUseRootlessOverlay(home, runhome string) bool {
1813
return false

types/options_linux.go

-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ const (
1616
SystemConfigFile = "/usr/share/containers/storage.conf"
1717
)
1818

19-
// defaultConfigFile path to the system wide storage.conf file
20-
var (
21-
defaultOverrideConfigFile = "/etc/containers/storage.conf"
22-
)
23-
2419
// canUseRootlessOverlay returns true if the overlay driver can be used for rootless containers
2520
func canUseRootlessOverlay(home, runhome string) bool {
2621
// we check first for fuse-overlayfs since it is cheaper.

types/options_test.go

+74
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,77 @@ func TestReloadConfigurationFile(t *testing.T) {
214214

215215
assert.Equal(t, strings.Contains(content.String(), "Failed to decode the keys [\\\"foo\\\" \\\"storage.options.graphroot\\\"] from \\\"./storage_broken.conf\\\"\""), true)
216216
}
217+
218+
func TestMergeStoreOptions(t *testing.T) {
219+
base := StoreOptions{
220+
RunRoot: "base/run",
221+
GraphDriverPriority: []string{"overlay", "aufs"},
222+
PullOptions: map[string]string{"rate": "low"},
223+
DisableVolatile: false,
224+
}
225+
dropIn := StoreOptions{
226+
RunRoot: "dropin/run",
227+
GraphDriverPriority: []string{"btrfs"},
228+
PullOptions: map[string]string{"rate": "high", "secure": "yes"},
229+
DisableVolatile: true,
230+
}
231+
232+
expected := StoreOptions{
233+
RunRoot: "dropin/run",
234+
GraphDriverPriority: []string{"overlay", "aufs", "btrfs"},
235+
PullOptions: map[string]string{"rate": "high", "secure": "yes"},
236+
DisableVolatile: true,
237+
}
238+
239+
result := mergeStoreOptions(base, dropIn)
240+
if !reflect.DeepEqual(result, expected) {
241+
t.Errorf("Expected %+v, got %+v", expected, result)
242+
}
243+
}
244+
245+
func TestMergeConfigFromDirectory(t *testing.T) {
246+
tempDir, err := os.MkdirTemp("", "testConfigDir")
247+
if err != nil {
248+
t.Fatalf("Failed to create temp dir: %v", err)
249+
}
250+
defer os.RemoveAll(tempDir)
251+
252+
fileNames := []string{"config1.toml", "config2.toml"}
253+
contents := []string{
254+
`[storage]
255+
runroot = 'temp/run1'
256+
graphroot = 'temp/graph1'`,
257+
`[storage]
258+
runroot = 'temp/run2'
259+
graphroot = 'temp/graph2'`,
260+
}
261+
for i, fileName := range fileNames {
262+
filePath := filepath.Join(tempDir, fileName)
263+
if err := os.WriteFile(filePath, []byte(contents[i]), 0o666); err != nil {
264+
t.Fatalf("Failed to write to temp file: %v", err)
265+
}
266+
}
267+
268+
// Set base options
269+
baseOptions := StoreOptions{
270+
RunRoot: "initial/run",
271+
GraphRoot: "initial/graph",
272+
}
273+
274+
// Expected results after merging configurations
275+
expectedOptions := StoreOptions{
276+
RunRoot: "temp/run2", // Assuming the last file read overrides earlier values
277+
GraphRoot: "temp/graph2",
278+
}
279+
280+
// Run the merging function
281+
mergedOptions, err := mergeConfigFromDirectory(baseOptions, tempDir)
282+
if err != nil {
283+
t.Fatalf("Error merging config from directory: %v", err)
284+
}
285+
286+
// Assert the expected result
287+
if mergedOptions.RunRoot != expectedOptions.RunRoot || mergedOptions.GraphRoot != expectedOptions.GraphRoot {
288+
t.Errorf("Expected RunRoot to be %q and GraphRoot to be %q, got RunRoot %q and GraphRoot %q", expectedOptions.RunRoot, expectedOptions.GraphRoot, mergedOptions.RunRoot, mergedOptions.GraphRoot)
289+
}
290+
}

types/options_windows.go

-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ const (
88
SystemConfigFile = "/usr/share/containers/storage.conf"
99
)
1010

11-
// defaultConfigFile path to the system wide storage.conf file
12-
var (
13-
defaultOverrideConfigFile = "/etc/containers/storage.conf"
14-
)
15-
1611
// canUseRootlessOverlay returns true if the overlay driver can be used for rootless containers
1712
func canUseRootlessOverlay(home, runhome string) bool {
1813
return false

types/utils.go

+29
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/containers/storage/pkg/fileutils"
1111
"github.com/containers/storage/pkg/homedir"
12+
"github.com/containers/storage/pkg/idtools"
1213
"github.com/sirupsen/logrus"
1314
)
1415

@@ -72,3 +73,31 @@ func reloadConfigurationFileIfNeeded(configFile string, storeOptions *StoreOptio
7273
prevReloadConfig.mod = mtime
7374
prevReloadConfig.configFile = configFile
7475
}
76+
77+
// Helper function to append unique strings to a slice.
78+
func appendUniqueStrings(slice []string, elements []string) []string {
79+
existing := make(map[string]bool)
80+
for _, item := range slice {
81+
existing[item] = true
82+
}
83+
for _, elem := range elements {
84+
if !existing[elem] {
85+
slice = append(slice, elem)
86+
}
87+
}
88+
return slice
89+
}
90+
91+
// Helper function to append unique IDMaps to a slice of IDMaps.
92+
func appendUniqueIDMaps(slice []idtools.IDMap, elements []idtools.IDMap) []idtools.IDMap {
93+
seen := make(map[idtools.IDMap]bool)
94+
for _, item := range slice {
95+
seen[item] = true
96+
}
97+
for _, elem := range elements {
98+
if !seen[elem] {
99+
slice = append(slice, elem)
100+
}
101+
}
102+
return slice
103+
}

types/utils_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"reflect"
78
"testing"
89

10+
"github.com/containers/storage/pkg/idtools"
911
"github.com/containers/storage/pkg/unshare"
1012
"gotest.tools/assert"
1113
)
@@ -42,3 +44,25 @@ func TestStorageConfOverrideEnvironmentDefaultConfigFileRoot(t *testing.T) {
4244
assert.NilError(t, err)
4345
assert.Equal(t, defaultFile, expectedPath)
4446
}
47+
48+
func TestAppendUniqueStrings(t *testing.T) {
49+
slice := []string{"one", "two"}
50+
elements := []string{"two", "three"}
51+
expected := []string{"one", "two", "three"}
52+
53+
result := appendUniqueStrings(slice, elements)
54+
if !reflect.DeepEqual(result, expected) {
55+
t.Errorf("Expected %v, got %v", expected, result)
56+
}
57+
}
58+
59+
func TestAppendUniqueIDMaps(t *testing.T) {
60+
slice := []idtools.IDMap{{ContainerID: 100, HostID: 1000, Size: 1}}
61+
elements := []idtools.IDMap{{ContainerID: 100, HostID: 1000, Size: 1}, {ContainerID: 101, HostID: 1001, Size: 1}}
62+
expected := []idtools.IDMap{{ContainerID: 100, HostID: 1000, Size: 1}, {ContainerID: 101, HostID: 1001, Size: 1}}
63+
64+
result := appendUniqueIDMaps(slice, elements)
65+
if !reflect.DeepEqual(result, expected) {
66+
t.Errorf("Expected %+v, got %+v", expected, result)
67+
}
68+
}

0 commit comments

Comments
 (0)