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 402eb95
Show file tree
Hide file tree
Showing 4 changed files with 168 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"
)
138 changes: 138 additions & 0 deletions pkg/crc/preflight/preflight_daemon_task_check_windows.go
@@ -0,0 +1,138 @@
package preflight

import (
"bytes"
"encoding/xml"
"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)
var buf bytes.Buffer
if err := xml.EscapeText(&buf, []byte(binPathWithArgs)); err != nil {
return err
}
taskContent := genDaemonTaskInstallTemplate(
version.GetCRCVersion(),
buf.String(),
)

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 daemonTaskChecks = []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, daemonTaskChecks...)
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 402eb95

Please sign in to comment.