From c4f1dfe3436e3489dbb40a4a3482094ea495da43 Mon Sep 17 00:00:00 2001 From: Praveen Kumar Date: Mon, 28 Mar 2022 15:48:17 +0530 Subject: [PATCH] Windows: Run 'crc daemon' as task for 'crc setup' This PR is uses windows powershell to create `crc daemon` as task and start it on demand for the current user. This doesn't need any elevated privillages to run `crc daemon` for user context. It try to perform following tasks - Check if there is already crcDaemon task + If present does it the latest one using version info - Install the task using predefined xml definition - Start the task which run the `crc daemon` in background. There is a known issue with windows powershell when you start a task in background using `powershell.exe` then for a second the windows pops up and disappear, https://github.com/PowerShell/PowerShell/issues/3028 have more details around it but as of now this is only the way for interactive user context otherwise the task will need the admin privillages which we want to avoid. --- pkg/crc/constants/constants_windows.go | 1 + .../preflight_daemon_task_check_windows.go | 133 ++++++++++++++++++ pkg/crc/preflight/preflight_windows.go | 24 ++++ pkg/crc/preflight/preflight_windows_test.go | 10 +- 4 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 pkg/crc/preflight/preflight_daemon_task_check_windows.go diff --git a/pkg/crc/constants/constants_windows.go b/pkg/crc/constants/constants_windows.go index b213c2eb36..b321107401 100644 --- a/pkg/crc/constants/constants_windows.go +++ b/pkg/crc/constants/constants_windows.go @@ -5,4 +5,5 @@ const ( PodmanRemoteExecutableName = "podman.exe" TapSocketPath = "" DaemonHTTPNamedPipe = `\\.\pipe\crc-http` + DaemonTaskName = "crcDaemon" ) diff --git a/pkg/crc/preflight/preflight_daemon_task_check_windows.go b/pkg/crc/preflight/preflight_daemon_task_check_windows.go new file mode 100644 index 0000000000..1a8ab5ed2e --- /dev/null +++ b/pkg/crc/preflight/preflight_daemon_task_check_windows.go @@ -0,0 +1,133 @@ +package preflight + +import ( + "fmt" + "os" + "strings" + + "github.com/code-ready/crc/pkg/crc/constants" + "github.com/code-ready/crc/pkg/crc/logging" + "github.com/code-ready/crc/pkg/crc/version" + "github.com/code-ready/crc/pkg/os/windows/powershell" +) + +var ( + // https://docs.microsoft.com/en-us/windows/win32/taskschd/daily-trigger-example--xml- + daemonTaskTemplate = ` + + + Run crc daemon as a task + %s + + + false + false + true + IgnoreNew + + PT10M + PT1H + true + false + + true + + + + + powershell.exe + -WindowStyle Hidden -Command %s + + + +` +) + +func genDaemonTaskInstallTemplate(crcVersion, daemonServiceName string) string { + return fmt.Sprintf(daemonTaskTemplate, + crcVersion, + daemonServiceName, + ) +} + +func checkIfDaemonTaskInstalled() error { + _, stderr, err := powershell.Execute("Get-ScheduledTask", "-TaskName", constants.DaemonTaskName) + if err != nil { + logging.Debugf("%s task is not installed: %v : %s", constants.DaemonTaskName, err, stderr) + return err + } + if err := checkIfOlderTask(); err != nil { + return err + } + return nil +} + +func fixDaemonTaskInstalled() error { + // prepare the task script + binPath, err := os.Executable() + if err != nil { + return fmt.Errorf("unable to find the current executable location: %v", err) + } + binPathWithArgs := fmt.Sprintf("%s daemon", binPath) + + taskContent := genDaemonTaskInstallTemplate( + version.GetCRCVersion(), + binPathWithArgs, + ) + + if _, stderr, err := powershell.Execute("Register-ScheduledTask", "-Xml", fmt.Sprintf(`'%s'`, taskContent), "-TaskName", constants.DaemonTaskName); err != nil { + return fmt.Errorf("failed to register %s task, %v: %s", constants.DaemonTaskName, err, stderr) + } + + return nil +} + +func removeDaemonTask() error { + if err := checkIfDaemonTaskRunning(); err == nil { + _, stderr, err := powershell.Execute("Stop-ScheduledTask", "-TaskName", constants.DaemonTaskName) + if err != nil { + logging.Debugf("unable to stop the %s task: %v : %s", constants.DaemonTaskName, err, stderr) + return err + } + } + if err := checkIfDaemonTaskInstalled(); err == nil { + _, stderr, err := powershell.Execute("Unregister-ScheduledTask", "-TaskName", constants.DaemonTaskName, "-Confirm:$false") + if err != nil { + logging.Debugf("unable to unregister the %s task: %v : %s", constants.DaemonTaskName, err, stderr) + return err + } + } + return nil +} + +func checkIfDaemonTaskRunning() error { + stdout, stderr, err := powershell.Execute(fmt.Sprintf(`(Get-ScheduledTask -TaskName "%s").State`, constants.DaemonTaskName)) + if err != nil { + logging.Debugf("%s task is not running: %v : %s", constants.DaemonTaskName, err, stderr) + return err + } + if strings.TrimSpace(stdout) != "Running" { + return fmt.Errorf("expected %s task to be in 'Running' but got '%s'", constants.DaemonTaskName, stdout) + } + return nil +} + +func fixDaemonTaskRunning() error { + _, stderr, err := powershell.Execute("Start-ScheduledTask", "-TaskName", constants.DaemonTaskName) + if err != nil { + logging.Debugf("unable to run the %s task: %v : %s", constants.DaemonTaskName, err, stderr) + return err + } + return nil +} + +func checkIfOlderTask() error { + stdout, stderr, err := powershell.Execute(fmt.Sprintf(`(Get-ScheduledTask -TaskName "%s").Version`, constants.DaemonTaskName)) + if err != nil { + return fmt.Errorf("%s task is not running: %v : %s", constants.DaemonTaskName, err, stderr) + } + if strings.TrimSpace(stdout) != version.GetCRCVersion() { + return fmt.Errorf("expected %s task to be on version '%s' but got '%s'", constants.DaemonTaskName, version.GetCRCVersion(), stdout) + } + return nil +} diff --git a/pkg/crc/preflight/preflight_windows.go b/pkg/crc/preflight/preflight_windows.go index f3a01a121f..e5c68b2e5f 100644 --- a/pkg/crc/preflight/preflight_windows.go +++ b/pkg/crc/preflight/preflight_windows.go @@ -112,6 +112,29 @@ var vsockChecks = []Check{ }, } +var daemonServiceChecks = []Check{ + { + configKeySuffix: "check-daemon-task-install", + checkDescription: "Checking if daemon task is installed", + check: checkIfDaemonTaskInstalled, + fixDescription: "Installing the daemon task", + fix: fixDaemonTaskInstalled, + cleanupDescription: "Removing daemon task", + cleanup: removeDaemonTask, + + labels: labels{Os: Windows}, + }, + { + configKeySuffix: "check-daemon-task-running", + checkDescription: "Checking if daemon task is running", + check: checkIfDaemonTaskRunning, + fixDescription: "Running the daemon task", + fix: fixDaemonTaskRunning, + + labels: labels{Os: Windows}, + }, +} + var errReboot = errors.New("Please reboot your system and run 'crc setup' to complete the setup process") func username() string { @@ -157,6 +180,7 @@ func getChecks(bundlePath string, preset crcpreset.Preset) []Check { checks = append(checks, vsockChecks...) checks = append(checks, bundleCheck(bundlePath, preset)) checks = append(checks, genericCleanupChecks...) + checks = append(checks, daemonServiceChecks...) return checks } diff --git a/pkg/crc/preflight/preflight_windows_test.go b/pkg/crc/preflight/preflight_windows_test.go index b4c745aae7..37eadd1087 100644 --- a/pkg/crc/preflight/preflight_windows_test.go +++ b/pkg/crc/preflight/preflight_windows_test.go @@ -13,13 +13,13 @@ import ( func TestCountConfigurationOptions(t *testing.T) { cfg := config.New(config.NewEmptyInMemoryStorage()) RegisterSettings(cfg) - assert.Len(t, cfg.AllConfigs(), 10) + assert.Len(t, cfg.AllConfigs(), 12) } func TestCountPreflights(t *testing.T) { - assert.Len(t, getPreflightChecks(false, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 15) - assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 15) + assert.Len(t, getPreflightChecks(false, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 17) + assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 17) - assert.Len(t, getPreflightChecks(false, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 16) - assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 16) + assert.Len(t, getPreflightChecks(false, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 18) + assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 18) }