fix: align resources-to-ignore=configMaps with renamed ResourceMap key

PR #1061 renamed the configmap key in kube.ResourceMap from "configMaps"
(camelCase) to "configmaps" (lowercase) to fix controllers not being
able to mark themselves as initialized. However, two callers were not
updated to match the new canonical key:

1. The Helm chart's deployment template still emits
   `--resources-to-ignore=configMaps` (camelCase) when
   `reloader.ignoreConfigMaps: true` is set.
2. The validation in `GetIgnoredResourcesList` only accepts the legacy
   camelCase spelling.

Because `ignoredResourcesList.Contains(k)` uses case-sensitive string
equality, the lookup against the new lowercase ResourceMap key never
matches. The configmaps controller is created and starts watching
ConfigMaps cluster-wide, even though the chart's ClusterRole template
(also gated on `ignoreConfigMaps`) does not grant permission for it.

The resulting pod logs are full of:

  configmaps is forbidden: User "system:serviceaccount:reloader:reloader-reloader"
  cannot list resource "configmaps" in API group "" at the cluster scope

This change:

- Updates the chart deployment template to emit the canonical lowercase
  `configmaps` value.
- Normalizes the input in `GetIgnoredResourcesList`, accepting both
  `configMaps` (legacy, for backward compatibility with users who pass
  the flag directly) and `configmaps` (canonical), and emitting the
  canonical form to the caller.
- Updates the flag help text and adds tests covering both spellings.
This commit is contained in:
Jesse Stephens
2026-05-20 14:19:13 -05:00
parent 9a2e39fe67
commit d527ae8d5a
3 changed files with 95 additions and 7 deletions
@@ -222,7 +222,7 @@ spec:
- "--resources-to-ignore=secrets"
{{- end }}
{{- if .Values.reloader.ignoreConfigMaps }}
- "--resources-to-ignore=configMaps"
- "--resources-to-ignore=configmaps"
{{- end }}
{{- if and (.Values.reloader.ignoreJobs) (.Values.reloader.ignoreCronJobs) }}
- "--ignored-workload-types=jobs,cronjobs"
+15 -6
View File
@@ -93,7 +93,7 @@ func ConfigureReloaderFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&options.LogFormat, "log-format", "", "Log format to use (empty string for text, or JSON)")
cmd.PersistentFlags().StringVar(&options.LogLevel, "log-level", "info", "Log level to use (trace, debug, info, warning, error, fatal and panic)")
cmd.PersistentFlags().StringVar(&options.WebhookUrl, "webhook-url", "", "webhook to trigger instead of performing a reload")
cmd.PersistentFlags().StringSliceVar(&options.ResourcesToIgnore, "resources-to-ignore", options.ResourcesToIgnore, "list of resources to ignore (valid options 'configMaps' or 'secrets')")
cmd.PersistentFlags().StringSliceVar(&options.ResourcesToIgnore, "resources-to-ignore", options.ResourcesToIgnore, "list of resources to ignore (valid options 'configmaps' or 'secrets'; 'configMaps' is also accepted for backward compatibility)")
cmd.PersistentFlags().StringSliceVar(&options.WorkloadTypesToIgnore, "ignored-workload-types", options.WorkloadTypesToIgnore, "list of workload types to ignore (valid options: 'jobs', 'cronjobs', or both)")
cmd.PersistentFlags().StringSliceVar(&options.NamespacesToIgnore, "namespaces-to-ignore", options.NamespacesToIgnore, "list of namespaces to ignore")
cmd.PersistentFlags().StringSliceVar(&options.NamespaceSelectors, "namespace-selector", options.NamespaceSelectors, "list of key:value labels to filter on for namespaces")
@@ -113,17 +113,26 @@ func GetIgnoredResourcesList() (List, error) {
ignoredResourcesList := options.ResourcesToIgnore // getStringSliceFromFlags(cmd, "resources-to-ignore")
// Normalize to the canonical lowercase keys used in kube.ResourceMap.
// Accept the legacy "configMaps" spelling for backward compatibility with
// charts that still emit the camelCase form.
normalized := make(List, 0, len(ignoredResourcesList))
for _, v := range ignoredResourcesList {
if v != "configMaps" && v != "secrets" {
return nil, fmt.Errorf("'resources-to-ignore' only accepts 'configMaps' or 'secrets', not '%s'", v)
switch v {
case "configMaps", "configmaps":
normalized = append(normalized, "configmaps")
case "secrets":
normalized = append(normalized, "secrets")
default:
return nil, fmt.Errorf("'resources-to-ignore' only accepts 'configmaps' or 'secrets', not '%s'", v)
}
}
if len(ignoredResourcesList) > 1 {
return nil, errors.New("'resources-to-ignore' only accepts 'configMaps' or 'secrets', not both")
if len(normalized) > 1 {
return nil, errors.New("'resources-to-ignore' only accepts 'configmaps' or 'secrets', not both")
}
return ignoredResourcesList, nil
return normalized, nil
}
func GetIgnoredWorkloadTypesList() (List, error) {
+79
View File
@@ -136,6 +136,85 @@ func TestGetIgnoredWorkloadTypesList(t *testing.T) {
}
}
func TestGetIgnoredResourcesList(t *testing.T) {
// Save original state
originalResources := options.ResourcesToIgnore
defer func() {
options.ResourcesToIgnore = originalResources
}()
tests := []struct {
name string
resources []string
expectError bool
expected []string
}{
{
name: "Lowercase configmaps (canonical) normalizes to configmaps",
resources: []string{"configmaps"},
expectError: false,
expected: []string{"configmaps"},
},
{
name: "Legacy camelCase configMaps normalizes to configmaps",
resources: []string{"configMaps"},
expectError: false,
expected: []string{"configmaps"},
},
{
name: "secrets",
resources: []string{"secrets"},
expectError: false,
expected: []string{"secrets"},
},
{
name: "Empty list",
resources: []string{},
expectError: false,
expected: []string{},
},
{
name: "Invalid resource",
resources: []string{"deployments"},
expectError: true,
expected: nil,
},
{
name: "Both configmaps and secrets rejected",
resources: []string{"configmaps", "secrets"},
expectError: true,
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
options.ResourcesToIgnore = tt.resources
result, err := GetIgnoredResourcesList()
if tt.expectError && err == nil {
t.Errorf("Expected error but got none")
}
if !tt.expectError && err != nil {
t.Errorf("Expected no error but got: %v", err)
}
if !tt.expectError {
if len(result) != len(tt.expected) {
t.Errorf("Expected %v, got %v", tt.expected, result)
return
}
for i, expected := range tt.expected {
if result[i] != expected {
t.Errorf("Expected %v, got %v", tt.expected, result)
break
}
}
}
})
}
}
func TestListContains(t *testing.T) {
tests := []struct {
name string