Skip to content

Commit

Permalink
Windows: Run 'crc daemon' as task for 'crc setup'
Browse files Browse the repository at this point in the history
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, PowerShell/PowerShell#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.
  • Loading branch information
praveenkumar committed Mar 28, 2022
1 parent aa7e94f commit c4f1dfe
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 5 deletions.
1 change: 1 addition & 0 deletions pkg/crc/constants/constants_windows.go
Expand Up @@ -5,4 +5,5 @@ const (
PodmanRemoteExecutableName = "podman.exe"
TapSocketPath = ""
DaemonHTTPNamedPipe = `\\.\pipe\crc-http`
DaemonTaskName = "crcDaemon"
)
133 changes: 133 additions & 0 deletions 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 = `<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>Run crc daemon as a task</Description>
<Version>%s</Version>
</RegistrationInfo>
<Settings>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<Hidden>true</Hidden>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<IdleSettings>
<Duration>PT10M</Duration>
<WaitTimeout>PT1H</WaitTimeout>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
</Settings>
<Triggers />
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-WindowStyle Hidden -Command %s</Arguments>
</Exec>
</Actions>
</Task>
`
)

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
}
24 changes: 24 additions & 0 deletions pkg/crc/preflight/preflight_windows.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down
10 changes: 5 additions & 5 deletions pkg/crc/preflight/preflight_windows_test.go
Expand Up @@ -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)
}

0 comments on commit c4f1dfe

Please sign in to comment.