Add test and doc for processing all the sub-directories in drop-in folder

This commit is contained in:
Neeraj Krishna Gopalakrishna
2026-01-12 09:22:13 +05:30
parent a90cf50004
commit 001dcf63ba
3 changed files with 108 additions and 2 deletions

View File

@@ -89,6 +89,7 @@ type KubeletFlags struct {
// KubeletDropinConfigDirectory is the path to a directory containing drop-in configuration files that override settings from defaults and the --config file.
// The path may be absolute or relative; relative paths are under the Kubelet's current working directory.
// Drop-in files must have a '.conf' suffix and are processed in lexical order. Only '.conf' files are loaded; all other files are ignored.
// All .conf files in the directory and its subdirectories are processed in lexical order.
KubeletDropinConfigDirectory string
// WindowsService should be set to true if kubelet is running as a service on Windows.
@@ -278,7 +279,7 @@ func (f *KubeletFlags) AddFlags(mainfs *pflag.FlagSet) {
f.addOSFlags(fs)
fs.StringVar(&f.KubeletConfigFile, "config", f.KubeletConfigFile, "The Kubelet will load its initial configuration from this file. The path may be absolute or relative; relative paths start at the Kubelet's current working directory. Omit this flag to use the built-in default configuration values. Command-line flags override configuration from this file.")
fs.StringVar(&f.KubeletDropinConfigDirectory, "config-dir", "", "Path to a directory containing drop-in configuration files that override settings from defaults and the --config file. The path may be absolute or relative; relative paths start at the Kubelet's current working directory. Drop-in files must have a '.conf' suffix (e.g., '99-kubelet-address.conf') and are processed in lexical order. Only '.conf' files are loaded; all other files are ignored. [default='']")
fs.StringVar(&f.KubeletDropinConfigDirectory, "config-dir", "", "Path to a directory containing drop-in configuration files that override settings from defaults and the --config file. The path may be absolute or relative; relative paths start at the Kubelet's current working directory. Drop-in files must have a '.conf' suffix (e.g., '99-kubelet-address.conf') and are processed in lexical order. Only '.conf' files are loaded; all other files are ignored. All .conf files in the directory and its subdirectories are processed in lexical order. [default='']")
fs.StringVar(&f.KubeConfig, "kubeconfig", f.KubeConfig, "Path to a kubeconfig file, specifying how to connect to the API server. Providing --kubeconfig enables API server mode, omitting --kubeconfig enables standalone mode.")
fs.StringVar(&f.BootstrapKubeconfig, "bootstrap-kubeconfig", f.BootstrapKubeconfig, "Path to a kubeconfig file that will be used to get client certificate for kubelet. "+

View File

@@ -330,7 +330,7 @@ is checked every 20 seconds (also configurable with a flag).`,
// configurations in files with lower numeric prefixes are applied first, followed by higher numeric prefixes.
// For example, if the drop-in directory contains files named "10-config.conf" and "20-config.conf",
// the configurations in "10-config.conf" will be applied first, and then the configurations in "20-config.conf" will be applied,
// potentially overriding the previous values.
// potentially overriding the previous values. Files in subdirectories are also processed in lexical order.
// Returns a list of skipped files and an error.
func mergeKubeletConfigurations(kubeletConfig *kubeletconfiginternal.KubeletConfiguration, kubeletDropInConfigDir string) ([]string, error) {
const dropinFileExtension = ".conf"

View File

@@ -416,3 +416,108 @@ port: [this is not valid
})
}
}
func TestMergeKubeletConfigsWithSubdirs(t *testing.T) {
testCases := []struct {
name string
kubeletConfig *kubeletconfiginternal.KubeletConfiguration
dropins map[string]string // map[relativePath]content
overwrittenConfigFields map[string]interface{}
}{
{
name: "subdirectories are processed in lexical order",
kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "KubeletConfiguration",
APIVersion: "kubelet.config.k8s.io/v1beta1",
},
Port: int32(9090),
ReadOnlyPort: int32(10257),
},
dropins: map[string]string{
"10-kubelet.conf": `
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 8080
`,
"subdir/20-kubelet.conf": `
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 7777
readOnlyPort: 9999
`,
},
overwrittenConfigFields: map[string]interface{}{
"Port": int32(7777),
"ReadOnlyPort": int32(9999),
},
},
{
name: "same filename in multiple directories - lexical order applies",
kubeletConfig: &kubeletconfiginternal.KubeletConfiguration{
TypeMeta: metav1.TypeMeta{
Kind: "KubeletConfiguration",
APIVersion: "kubelet.config.k8s.io/v1beta1",
},
Port: int32(9090),
ReadOnlyPort: int32(10255),
},
dropins: map[string]string{
"10-kubelet.conf": `
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 8080
`,
"sub-dir1/10-kubelet.conf": `
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 7070
readOnlyPort: 8888
`,
"sub-dir2/10-kubelet.conf": `
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 6060
readOnlyPort: 9999
`,
},
overwrittenConfigFields: map[string]interface{}{
"Port": int32(6060),
"ReadOnlyPort": int32(9999),
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
tempDir := t.TempDir()
kubeletConfDir := filepath.Join(tempDir, "kubelet.conf.d")
err := os.Mkdir(kubeletConfDir, 0755)
require.NoError(t, err, "Failed to create kubelet.conf.d directory")
// Create drop-in files including those in subdirectories
for relPath, content := range test.dropins {
fullPath := filepath.Join(kubeletConfDir, relPath)
// Create subdirectory if needed
dir := filepath.Dir(fullPath)
if dir != kubeletConfDir {
err = os.MkdirAll(dir, 0755)
require.NoError(t, err, "Failed to create subdirectory: "+dir)
}
err = os.WriteFile(fullPath, []byte(content), 0644)
require.NoError(t, err, "Failed to create drop-in file: "+relPath)
}
// Merge the kubelet configurations
_, err = mergeKubeletConfigurations(test.kubeletConfig, kubeletConfDir)
require.NoError(t, err, "failed to merge kubelet drop-in configs")
// Verify the merged configuration fields
for fieldName, expectedValue := range test.overwrittenConfigFields {
value := reflect.ValueOf(test.kubeletConfig).Elem()
field := value.FieldByName(fieldName)
require.Equal(t, expectedValue, field.Interface(), "Field mismatch: "+fieldName)
}
})
}
}