package patchmanager import ( "context" "fmt" "os" "path/filepath" "github.com/go-logr/logr" "github.com/spf13/afero" file "edge-infra.dev/pkg/sds/lib/os/file" "edge-infra.dev/pkg/sds/patching/common" v1patch "edge-infra.dev/pkg/sds/patching/k8s/apis/ienpatch/v1" k8sclient "sigs.k8s.io/controller-runtime/pkg/client" ) type PatchManager struct { Ctx context.Context K8sClient k8sclient.Client Log logr.Logger HostName string Ienpatch *v1patch.IENPatch CurrentVer string TargetVer string Fs afero.OsFs Cfg common.Config } func (p *PatchManager) PatchIEN() (v1patch.PatchStatus, error) { // Remount boot to RW if err := remount(p.Cfg.MountPath); err != nil { return v1patch.Retry, err } if err := p.installPatchset(); err != nil { return v1patch.Retry, fmt.Errorf("Failed to unpack and install patchset: %w", err) } if p.Ienpatch.Spec.DownloadOnly { p.Log.Info("Download and extraction complete. Set downloadOnly to false to continue patching") return v1patch.DownloadComplete, nil } if !p.isNodeTargetted() { return v1patch.Pending, nil } if err := p.WaitForRebootAllowed(); err != nil { return v1patch.Retry, fmt.Errorf("Error occurred while waiting to see if reboot is possible: %w", err) } if err := generatePatchingEnv(p.CurrentVer, p.TargetVer, p.Cfg.EnvFilePath); err != nil { return v1patch.Retry, fmt.Errorf("Failed to generate patching.env file: %w", err) } // Write to two paths for BWC. Clean up when all OS ver is at 1.14 if err := generatePatchingEnv(p.CurrentVer, p.TargetVer, common.EnvFilePath); err != nil { return v1patch.Retry, fmt.Errorf("Failed to generate patching.env file: %w", err) } if err := requestReboot(p.Ienpatch.Spec.AutoReboot, p.Log, p.Cfg); err != nil { return v1patch.Failed, fmt.Errorf("Failed to schedule reboot: %w", err) } return v1patch.Reboot, nil } func Contains(nodeList []string, nodeName string) bool { for _, v := range nodeList { if v == nodeName { return true } } return false } func generatePatchingEnv(currentVer, targetVer string, path string) error { data := []byte(fmt.Sprintf("CURRENT='%s'\nTARGET='%s'\n", currentVer, targetVer)) return os.WriteFile(path, data, 0600) } func requestReboot(autoreboot bool, log logr.Logger, cfg common.Config) error { if !autoreboot { log.Info("Auto reboot is disabled. Please manually reboot the system") return nil } perms := os.FileMode(0755) if err := os.MkdirAll(filepath.Dir(cfg.RebootPath), perms); err != nil { return err } if _, err := os.Create(cfg.RebootPath); err != nil { return err } if err := os.Chmod(cfg.RebootPath, perms); err != nil { return err } log.Info("Auto reboot scheduled, waiting for reboot daemon") return nil } // Remount path with read write mode func remount(mountPath string) error { fileHandler := file.New() // Remount boot to RW mountInfo, err := fileHandler.GetMountInfo(mountPath) if err != nil { return fmt.Errorf("Failed to get mount info for mountPath %s: %w", mountPath, err) } if remountErr := fileHandler.RemountFsReadWrite(mountPath, mountInfo.Source); remountErr != nil { return fmt.Errorf("Failed to remount %s: %w", mountPath, err) } return nil }