Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ const (
// currentConfigPath is where we store the current config on disk to validate
// against annotations changes
currentConfigPath = "/etc/machine-config-daemon/currentconfig"
// postOSUpdateEtcFilesMarkerPath is written before a reboot that follows an
// OS update with MC-managed /etc files. On the next first-run it signals that
// those files must be re-applied, because the ostree 3-way merge may have
// overwritten them when their content matched the old OS base.
postOSUpdateEtcFilesMarkerPath = "/etc/machine-config-daemon/post-os-update-etc-files"
// bootstrapConfigDiffPath is where we store the current config on disk to validate
// against annotations changes
bootstrapConfigDiffPath = "/etc/machine-config-daemon/bootstrapconfigdiff"
Expand Down Expand Up @@ -2273,6 +2278,36 @@ func (dn *Daemon) checkStateOnFirstRun() error {
return err
}

// If a previous OS update left a marker, re-apply MC-managed /etc files
// before validation. The ostree 3-way merge silently overwrites MC-managed
// files whose content matches the old OS base, causing a spurious content
// mismatch. Re-applying here, after the merge has run, restores the correct
// content so validation passes.
if _, err := os.Stat(postOSUpdateEtcFilesMarkerPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("checking post-OS-update marker: %w", err)
} else if err == nil {
klog.Infof("Post-OS-update marker found; re-applying MC /etc files to correct 3-way merge overwrites")
if err := os.Remove(postOSUpdateEtcFilesMarkerPath); err != nil {
klog.Errorf("Failed to remove post-OS-update marker: %v", err)
}
newIgnConfig, err := ctrlcommon.ParseAndConvertConfig(state.currentConfig.Spec.Config.Raw)
if err != nil {
return fmt.Errorf("parsing current config for /etc re-apply after OS update: %w", err)
}
var etcFiles []ign3types.File
for _, f := range newIgnConfig.Storage.Files {
if strings.HasPrefix(f.Path, "/etc/") {
etcFiles = append(etcFiles, f)
}
}
if len(etcFiles) > 0 {
klog.Infof("Re-applying %d MC /etc files after OS update", len(etcFiles))
if err := dn.writeFiles(etcFiles, false); err != nil {
return fmt.Errorf("re-applying /etc files after OS update: %w", err)
}
}
}

if err := dn.validateOnDiskStateOrImage(state.currentConfig, state.currentImage); err != nil {
dn.nodeWriter.Eventf(corev1.EventTypeWarning, "OnDiskStateValidationFailed", err.Error())
// Start the config drift monitor even when there's pre-existing drift
Expand Down
17 changes: 17 additions & 0 deletions pkg/daemon/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,23 @@ func (dn *Daemon) update(oldConfig, newConfig *mcfgv1.MachineConfig, skipCertifi
}
}
}()
var etcFiles []ign3types.File
for _, f := range newIgnConfig.Storage.Files {
if strings.HasPrefix(f.Path, "/etc/") {
etcFiles = append(etcFiles, f)
}
}
if osUpdate && len(etcFiles) > 0 {
// Signal checkStateOnFirstRun to re-apply these files after the reboot.
// The ostree 3-way /etc merge runs at boot time and can overwrite
// MC-managed files whose content matches the old OS base; writing to
// the staged deployment before the reboot would not survive the merge.
// The marker causes a post-reboot re-apply that corrects any overwrites.
if err := writeFileAtomicallyWithDefaults(postOSUpdateEtcFilesMarkerPath, []byte{}); err != nil {
return fmt.Errorf("writing post-OS-update marker: %w", err)
}
klog.Infof("Wrote post-OS-update marker; %d managed /etc files will be re-applied on next boot", len(etcFiles))
}
} else {
klog.Info("updating the OS on non-CoreOS nodes is not supported")
}
Expand Down