@@ -292,24 +292,41 @@ func (c *K0sController) hasControllerConfigChanged(bootstrapConfigs map[string]b
292292 return false
293293 }
294294
295+ kcpK0sConfigSpecCopy := kcp .Spec .K0sConfigSpec .DeepCopy ()
296+ bootstrapConfigCopy := bootstrapConfig .DeepCopy ()
297+ kcpK0sConfigSpecCopy .Args = uniqueArgs (kcpK0sConfigSpecCopy .Args )
298+
295299 // remove data that should not be taken into account to check if the configuration has changed.
296- normalizeK0sConfigSpec (kcp , bootstrapConfig )
300+ normalizeK0sConfigSpec (kcp , bootstrapConfigCopy )
301+ bootstrapConfigSpecCopy := bootstrapConfigCopy .Spec .K0sConfigSpec .DeepCopy ()
297302
298303 // k0s config will be reconciled using dynamic config, so leave it out of the comparison
299- bootstrapAPIConfig , _ , _ := unstructured .NestedMap (bootstrapConfig .Spec .K0sConfigSpec .K0s .Object , "spec" , "api" )
300- kcpAPIConfig , _ , _ := unstructured .NestedMap (kcp .Spec .K0sConfigSpec .K0s .Object , "spec" , "api" )
301- bootstrapStorageConfig , _ , _ := unstructured .NestedMap (bootstrapConfig .Spec .K0sConfigSpec .K0s .Object , "spec" , "storage" )
302- kcpStorageConfig , _ , _ := unstructured .NestedMap (kcp .Spec .K0sConfigSpec .K0s .Object , "spec" , "storage" )
304+ bootstrapAPIConfig , _ , _ := unstructured .NestedMap (bootstrapConfigSpecCopy .K0s .Object , "spec" , "api" )
305+ kcpAPIConfig , _ , _ := unstructured .NestedMap (kcpK0sConfigSpecCopy .K0s .Object , "spec" , "api" )
306+ bootstrapStorageConfig , _ , _ := unstructured .NestedMap (bootstrapConfigSpecCopy .K0s .Object , "spec" , "storage" )
307+ kcpStorageConfig , _ , _ := unstructured .NestedMap (kcpK0sConfigSpecCopy .K0s .Object , "spec" , "storage" )
308+
309+ // Handle nil cases consistently - convert nil to empty map for comparison
310+ if bootstrapStorageConfig == nil {
311+ bootstrapStorageConfig = make (map [string ]interface {})
312+ }
313+ if kcpStorageConfig == nil {
314+ kcpStorageConfig = make (map [string ]interface {})
315+ }
316+
303317 // Bootstrap controller did set etcd name to the K0sControllerConfig, so we need to compare it with the name set in the K0sControlPlane
304- kcpStorageConfigEtcdWithName , _ , _ := unstructured .NestedMap (kcp . Spec . K0sConfigSpec .K0s .Object , "spec" , "storage" )
318+ kcpStorageConfigEtcdWithName , _ , _ := unstructured .NestedMap (kcpK0sConfigSpecCopy .K0s .Object , "spec" , "storage" )
305319 if kcpStorageConfigEtcdWithName == nil {
306320 kcpStorageConfigEtcdWithName = make (map [string ]interface {})
307321 }
308322 _ = unstructured .SetNestedField (kcpStorageConfigEtcdWithName , machine .Name , "etcd" , "extraArgs" , "name" )
309- bootstrapConfig .Spec .K0sConfigSpec .K0s = kcp .Spec .K0sConfigSpec .K0s
323+
324+ bootstrapConfigCopy .Spec .K0sConfigSpec .K0s = kcpK0sConfigSpecCopy .K0s
325+
310326 // leave out the tunneling spec for the bootstrap config
311- bootstrapConfig .Spec .K0sConfigSpec .Tunneling = kcp .Spec .K0sConfigSpec .Tunneling
312- return ! reflect .DeepEqual (kcp .Spec .K0sConfigSpec , * bootstrapConfig .Spec .K0sConfigSpec ) ||
327+ bootstrapConfigCopy .Spec .K0sConfigSpec .Tunneling = kcpK0sConfigSpecCopy .Tunneling
328+
329+ return ! reflect .DeepEqual (kcpK0sConfigSpecCopy , bootstrapConfigCopy .Spec .K0sConfigSpec ) ||
313330 ! reflect .DeepEqual (kcpAPIConfig , bootstrapAPIConfig ) ||
314331 (! reflect .DeepEqual (kcpStorageConfig , bootstrapStorageConfig ) && ! reflect .DeepEqual (kcpStorageConfigEtcdWithName , bootstrapStorageConfig ))
315332}
@@ -587,7 +604,7 @@ func minVersion(machines collections.Machines) (string, error) {
587604// when comparing if the k0s configuration has changed.
588605// TODO: This method should be replaced with a more robust mechanism to prevent unexpected updates from
589606// the bootstrap controller.
590- func normalizeK0sConfigSpec (kcp * cpv1beta1.K0sControlPlane , bootstrapConfig bootstrapv1.K0sControllerConfig ) {
607+ func normalizeK0sConfigSpec (kcp * cpv1beta1.K0sControlPlane , bootstrapConfig * bootstrapv1.K0sControllerConfig ) {
591608 isK0sConfigYAMLSet := false
592609 for _ , arg := range kcp .Spec .K0sConfigSpec .Args {
593610 if arg == "/etc/k0s.yaml" {
@@ -609,12 +626,6 @@ func normalizeK0sConfigSpec(kcp *cpv1beta1.K0sControlPlane, bootstrapConfig boot
609626}
610627
611628func uniqueArgs (args []string ) []string {
612- // DO NOT REMOVE THIS CHECK
613- // If the slice is empty, we return the slice as is to avoid any modifications.
614- // In callers, we may compare slices and in some cases it may end up in comparing empty and nil slices.
615- if len (args ) == 0 {
616- return args
617- }
618629 uniqueArgsSlice := []string {}
619630 uniqueArgsMap := make (map [string ]struct {})
620631 for _ , arg := range args {
0 commit comments