Skip to content

Commit 5844300

Browse files
committed
MCO-1961: Rework MC's OSImageURL merge logic
Template-generated MachineConfigs (e.g., 01-master-kubelet, 01-master-container-runtime) were setting OSImageURL on the final rendered MachineConfig, even though only user-provided MachineConfigs should be able to override this field. The root cause was that template generation explicitly sets OSImageURL on all generated MCs, and the merge logic (MergeMachineConfigs) was treating all MCs equally when determining the final OSImageURL value. This meant template-generated MCs would always propagate the base OS image URL to the rendered MC, making it impossible for the system to distinguish between a default value and an intentional override. This commit fixes the issue by modifying MergeMachineConfigs to skip any MachineConfig with the machineconfiguration.openshift.io/generated-by-controller-version annotation when evaluating OSImageURL and BaseOSExtensionsContainerImage overrides. This ensures that only user-provided MachineConfigs can override these fields, while still allowing template-generated MCs to have the field populated (which is necessary due to resourcemerge not blanking out previously-set values during upgrades). The same logic is applied to BaseOSExtensionsContainerImage for consistency.
1 parent e2f6042 commit 5844300

File tree

6 files changed

+67
-11
lines changed

6 files changed

+67
-11
lines changed

devex/cmd/onclustertesting/helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func deleteAllMachineConfigsForPool(cs *framework.ClientSet, mcp *mcfgv1.Machine
181181
for _, mc := range machineConfigs.Items {
182182
mc := mc
183183
eg.Go(func() error {
184-
if _, ok := mc.Annotations[helpers.MCPNameToRole(mcp.Name)]; ok && !strings.HasPrefix(mc.Name, "rendered-") {
184+
if _, ok := mc.Annotations[helpers.MCPNameToRole(mcp.Name)]; ok && !strings.HasPrefix(mc.Name, ctrlcommon.RenderedMachineConfigPrefix) {
185185
if err := cs.MachineConfigs().Delete(context.TODO(), mc.Name, metav1.DeleteOptions{}); err != nil {
186186
return err
187187
}

pkg/controller/common/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ const (
3030
// OSImageURLOverriddenKey is used to tag a rendered machineconfig when OSImageURL has been overridden from default using machineconfig
3131
OSImageURLOverriddenKey = "machineconfiguration.openshift.io/os-image-url-overridden"
3232

33+
// RenderedMachineConfigPrefix is the name prefix for rendered MachineConfigs
34+
RenderedMachineConfigPrefix = "rendered-"
35+
3336
// ControllerConfigName is the name of the ControllerConfig object that controllers use
3437
ControllerConfigName = "machine-config-controller"
3538

pkg/controller/common/helpers.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ func MergeMachineConfigs(configs []*mcfgv1.MachineConfig, cconfig *mcfgv1.Contro
189189
// so the only way we get an override here is if the user adds something different
190190
osImageURL := GetDefaultBaseImageContainer(&cconfig.Spec)
191191
for _, cfg := range configs {
192+
// Ignore generated MCs, only the rendered MC or a user provided MC can set this field
193+
if cfg.Annotations[GeneratedByControllerVersionAnnotationKey] != "" {
194+
continue
195+
}
192196
if cfg.Spec.OSImageURL != "" {
193197
osImageURL = cfg.Spec.OSImageURL
194198
}
@@ -197,6 +201,10 @@ func MergeMachineConfigs(configs []*mcfgv1.MachineConfig, cconfig *mcfgv1.Contro
197201
// Allow overriding the extensions container
198202
baseOSExtensionsContainerImage := cconfig.Spec.BaseOSExtensionsContainerImage
199203
for _, cfg := range configs {
204+
// Ignore generated MCs, only the rendered MC or a user provided MC can set this field
205+
if cfg.Annotations[GeneratedByControllerVersionAnnotationKey] != "" {
206+
continue
207+
}
200208
if cfg.Spec.BaseOSExtensionsContainerImage != "" {
201209
baseOSExtensionsContainerImage = cfg.Spec.BaseOSExtensionsContainerImage
202210
}

pkg/controller/render/hash.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/ghodss/yaml"
99
mcfgv1 "github.com/openshift/api/machineconfiguration/v1"
10+
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
1011
)
1112

1213
var (
@@ -37,7 +38,7 @@ func getMachineConfigHashedName(pool *mcfgv1.MachineConfigPool, config *mcfgv1.M
3738
if err != nil {
3839
return "", err
3940
}
40-
return fmt.Sprintf("rendered-%s-%x", pool.GetName(), h), nil
41+
return fmt.Sprintf("%s%s-%x", ctrlcommon.RenderedMachineConfigPrefix, pool.GetName(), h), nil
4142
}
4243

4344
func hashData(data []byte) ([]byte, error) {

pkg/controller/template/render.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -361,13 +361,11 @@ func generateMachineConfigForName(config *RenderConfig, role, name, templateDir,
361361

362362
mcfg.Spec.Extensions = append(mcfg.Spec.Extensions, slices.Sorted(maps.Keys(extensions))...)
363363

364-
// TODO(jkyros): you might think you can remove this since we override later when we merge
365-
// config, but resourcemerge doesn't blank this field out once it's populated
366-
// so if you end up on a cluster where it was ever populated in this machineconfig, it
367-
// will keep that last value forever once you upgrade...which is a problen now that we allow OSImageURL overrides
368-
// because it will look like an override when it shouldn't be. So don't take this out until you've solved that.
369-
// And inject the osimageurl here
370-
mcfg.Spec.OSImageURL = ctrlcommon.GetDefaultBaseImageContainer(config.ControllerConfigSpec)
364+
// Note: Previously, the OSImageURL was set here (as well as in the rendered MC) to facilitate
365+
// overrides. Now, all the OSImageURL's from generated MCs are ignored and only user provided
366+
// MCs with the OSImageURL set are considered during the merge process.
367+
// Now the image URL is explicitly cleared to avoid confusion. Its content is never consumed.
368+
mcfg.Spec.OSImageURL = ""
371369

372370
return mcfg, nil
373371
}

pkg/controller/template/template_controller.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import (
66
"crypto/x509"
77
"encoding/json"
88
"encoding/pem"
9+
"errors"
910
"fmt"
1011
"os"
1112
"path/filepath"
1213
"reflect"
1314
"sort"
15+
"strings"
16+
"sync"
1417
"time"
1518

1619
mcfgv1 "github.com/openshift/api/machineconfiguration/v1"
@@ -26,7 +29,6 @@ import (
2629
configinformersv1 "github.com/openshift/client-go/config/informers/externalversions/config/v1"
2730
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
2831
corev1 "k8s.io/api/core/v1"
29-
"k8s.io/apimachinery/pkg/api/errors"
3032
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3133
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3234
"k8s.io/apimachinery/pkg/util/wait"
@@ -74,6 +76,8 @@ type Controller struct {
7476
secretsInformerSynced cache.InformerSynced
7577

7678
queue workqueue.TypedRateLimitingInterface[string]
79+
80+
urlClearOnce sync.Once
7781
}
7882

7983
// New returns a new template controller.
@@ -588,7 +592,7 @@ func (ctrl *Controller) syncControllerConfig(key string) error {
588592
return err
589593
}
590594
controllerconfig, err := ctrl.ccLister.Get(name)
591-
if errors.IsNotFound(err) {
595+
if apierrors.IsNotFound(err) {
592596
klog.V(2).Infof("ControllerConfig %v has been deleted", key)
593597
return nil
594598
}
@@ -633,6 +637,15 @@ func (ctrl *Controller) syncControllerConfig(key string) error {
633637
return ctrl.syncFailingStatus(cfg, err)
634638
}
635639

640+
// TODO: To be removed when 4.22 is EOL
641+
// Since 4.22 templated/generated (non-rendered) MCs no longer set the
642+
// OS and Extensions URLs and if given, the render controller ignores their
643+
// values. To improve UX we remove the URLs from existing MCs once.
644+
if clearErr := ctrl.clearImageURLs(); err == nil && apiServer != nil {
645+
// Best effort, do not fail
646+
klog.Warningf("Clearing MCs URLs has failed: %v", clearErr)
647+
}
648+
636649
mcs, err := getMachineConfigsForControllerConfig(ctrl.templatesDir, cfg, clusterPullSecretRaw, apiServer)
637650
if err != nil {
638651
return ctrl.syncFailingStatus(cfg, err)
@@ -652,6 +665,39 @@ func (ctrl *Controller) syncControllerConfig(key string) error {
652665
return ctrl.syncCompletedStatus(cfg)
653666
}
654667

668+
// clearImageURLs clears OSImageURL and BaseOSExtensionsContainerImage from template-generated
669+
// MachineConfigs. This is a one-time migration operation to remove these fields from generated
670+
// MachineConfigs, as they should only be set on rendered or user-provided MachineConfigs.
671+
// The function uses sync.Once to ensure it runs only once per controller lifecycle.
672+
func (ctrl *Controller) clearImageURLs() error {
673+
var err error
674+
ctrl.urlClearOnce.Do(func() {
675+
var mcList *mcfgv1.MachineConfigList
676+
mcList, err = ctrl.client.MachineconfigurationV1().MachineConfigs().List(context.TODO(), metav1.ListOptions{})
677+
if err != nil {
678+
return
679+
}
680+
for _, mc := range mcList.Items {
681+
if mc.Annotations[ctrlcommon.GeneratedByControllerVersionAnnotationKey] == "" || strings.HasPrefix(mc.Name, ctrlcommon.RenderedMachineConfigPrefix) {
682+
// It's a rendered MC or a user provided one.
683+
// This update code only works with templated/generated MCs
684+
continue
685+
}
686+
687+
if mc.Spec.OSImageURL != "" || mc.Spec.BaseOSExtensionsContainerImage != "" {
688+
mc.Spec.OSImageURL = ""
689+
mc.Spec.BaseOSExtensionsContainerImage = ""
690+
691+
if _, updateErr := ctrl.client.MachineconfigurationV1().MachineConfigs().Update(context.TODO(), &mc, metav1.UpdateOptions{}); updateErr != nil {
692+
err = errors.Join(err, updateErr)
693+
}
694+
klog.Infof("Removed old imageURLs from MachineConfig %s", mc.Name)
695+
}
696+
}
697+
})
698+
return err
699+
}
700+
655701
func getMachineConfigsForControllerConfig(templatesDir string, config *mcfgv1.ControllerConfig, clusterPullSecretRaw []byte, apiServer *configv1.APIServer) ([]*mcfgv1.MachineConfig, error) {
656702
buf := &bytes.Buffer{}
657703
if err := json.Compact(buf, clusterPullSecretRaw); err != nil {

0 commit comments

Comments
 (0)