Merge pull request #28522 from l0rd/avoid-regkey-creation

Allow skipping Windows registry key operations for first Hyper-V `machine init` and for last Hyper-V `machine rm`
This commit is contained in:
Jan Rodák 2026-04-30 15:48:31 +02:00 • committed by GitHub
commit 1cae4a9d63
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1008 additions and 86 deletions

View file

@ -663,7 +663,7 @@ localunit: test/goecho/goecho test/version/version
UNIT=1 $(GINKGO) \
-r \
$(TESTFLAGS) \
--skip-package test/e2e,pkg/bindings,hack,pkg/machine/e2e,pkg/machine/wsl \
--skip-package test/e2e,pkg/bindings,hack,pkg/machine/e2e,pkg/machine/wsl,pkg/machine/hyperv \
--cover \
--covermode atomic \
--coverprofile coverprofile \

View file

@ -0,0 +1,267 @@
//go:build windows && remote
package system
import (
"bufio"
"errors"
"fmt"
"os"
"os/user"
"strings"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/completion"
"go.podman.io/podman/v6/cmd/podman/registry"
"go.podman.io/podman/v6/cmd/podman/validate"
"go.podman.io/podman/v6/pkg/machine/hyperv"
"go.podman.io/podman/v6/pkg/machine/hyperv/vsock"
"go.podman.io/podman/v6/pkg/machine/windows"
)
const (
mountsFlag = "mounts"
defaultMounts = 2
)
var (
hypervPrepDescription = `Command for Windows administrators who need to configure an host for running Hyper-V-based Podman machines.
This command creates the required registry entries in HKEY_LOCAL_MACHINE for Hyper-V vsock communication and adds the current user to the Hyper-V Administrators group.
After this command execution, a non-admin user can manage Hyper-V-based Podman machines (create,start,stop,delete).
This command requires administrator privileges except when using the flag --status.`
hypervPrepCommand = &cobra.Command{
Use: "hyperv-prep [options]",
Args: validate.NoArgs,
Short: "Prepare the host to run Hyper-V-based Podman machines",
Long: hypervPrepDescription,
PersistentPreRunE: validate.NoOp,
RunE: hypervPrep,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman system hyperv-prep
podman system hyperv-prep --status
podman system hyperv-prep --reset --force`,
}
showStatus bool
resetEntries bool
mounts int
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: hypervPrepCommand,
Parent: systemCmd,
})
flags := hypervPrepCommand.Flags()
flags.BoolVar(&showStatus, "status", false, "Show vsock registry entries and Hyper-V group membership status")
flags.BoolVar(&resetEntries, "reset", false, "Remove all Podman vsock registry entries and optionally remove user from Hyper-V Administrators group")
flags.BoolVarP(&force, "force", "f", false, "Don't ask for confirmation during reset. Valid only when used with --reset.")
flags.IntVar(&mounts, mountsFlag, defaultMounts, "Number of vsock entries to create for mount purpose")
hypervPrepCommand.MarkFlagsMutuallyExclusive("status", "reset", mountsFlag)
hypervPrepCommand.MarkFlagsMutuallyExclusive("status", "force")
_ = hypervPrepCommand.RegisterFlagCompletionFunc(mountsFlag, completion.AutocompleteNone)
}
func hypervPrep(_ *cobra.Command, _ []string) error {
// --status can run without administrator privileges
if showStatus {
if err := doStatusForRegistries(); err != nil {
return err
}
doStatusForGroupMembership()
return nil
}
if !windows.HasAdminRights() {
return fmt.Errorf("this command requires administrator privileges, please run in an elevated terminal")
}
if resetEntries {
if err := doResetForRegistries(); err != nil {
return err
}
return doResetForGroupMembership()
}
if err := doPreparationForRegistries(); err != nil {
return err
}
return doPreparationForGroupMembership()
}
func doPreparationForRegistries() error {
// Every VSock has a purpose (Network, Events or Fileserver) and the
// VSocks with purpose Fileserver need as many as the machine mounts.
entriesPerPurpose := map[vsock.HVSockPurpose]int{
vsock.Network: 1,
vsock.Events: 1,
vsock.Fileserver: mounts,
}
var created []string
for purpose, entries := range entriesPerPurpose {
for range entries {
if entry, err := vsock.NewHVSockRegistryEntry(purpose, true); err != nil {
if errors.Is(err, vsock.ErrVSockRegistryEntryExists) {
logrus.Infof("Registry entry for %s already exists, skipping", purpose)
continue
}
return err
} else {
created = append(created, fmt.Sprintf("%s (port %d)", purpose.String(), entry.Port))
}
}
}
if len(created) == 0 {
fmt.Println("All required registry entries already exist")
} else {
fmt.Println("Successfully created registry entries for:")
for _, entry := range created {
fmt.Printf(" - %s\n", entry)
}
fmt.Println("These entries will persist even when all machines are removed.")
}
return nil
}
func doPreparationForGroupMembership() error {
if hyperv.IsHyperVAdminsGroupMember() {
fmt.Println("User is already a member of the Hyper-V Administrators group")
return nil
}
u, err := user.Current()
if err != nil {
return fmt.Errorf("failed to get current user: %w", err)
}
if err := hyperv.AddUserToHyperVAdminGroup(u.Username); err != nil {
return fmt.Errorf("failed to add user to Hyper-V Administrators group: %w", err)
}
fmt.Printf("\nAdded user %s to the Hyper-V Administrators group\n", u.Username)
fmt.Println("Note: You may need to log out and log back in for the new group membership to take effect.")
return nil
}
func doStatusForRegistries() error {
purposes := []vsock.HVSockPurpose{vsock.Network, vsock.Events, vsock.Fileserver}
fmt.Printf("Hyper-V vsock registry entries:\n")
foundAny := false
for _, purpose := range purposes {
entries, err := vsock.LoadAllHVSockRegistryEntriesByPurpose(purpose)
if err != nil {
logrus.Debugf("Error loading registry entries for %s: %v", purpose.String(), err)
continue
}
if len(entries) > 0 {
foundAny = true
for _, entry := range entries {
fmt.Print(entry)
}
}
}
if !foundAny {
fmt.Println(" No vsock registry entries found.")
}
return nil
}
func doStatusForGroupMembership() {
fmt.Println("Hyper-V Administrators group membership:")
if hyperv.IsHyperVAdminsGroupMember() {
fmt.Println(" Current user is a member")
} else {
fmt.Println(" Current user is NOT a member")
}
}
func doResetForRegistries() error {
purposes := []vsock.HVSockPurpose{vsock.Network, vsock.Events, vsock.Fileserver}
var allEntries []*vsock.HVSockRegistryEntry
// Load all entries for each purpose
for _, purpose := range purposes {
entries, err := vsock.LoadAllHVSockRegistryEntriesByPurpose(purpose)
if err != nil {
logrus.Debugf("Error loading registry entries for %s: %v", purpose.String(), err)
continue
}
allEntries = append(allEntries, entries...)
}
if len(allEntries) == 0 {
fmt.Println("No vsock registry entries found to remove.")
return nil
}
if !force {
fmt.Println("Existing VSock registry entries for Podman:")
for _, entry := range allEntries {
fmt.Print(entry)
}
reader := bufio.NewReader(os.Stdin)
fmt.Print("Do you want to delete these entries from the Windows registry? [y/N] ")
answer, err := reader.ReadString('\n')
if err != nil {
return err
}
if strings.ToLower(answer)[0] != 'y' {
return nil
}
}
var removed []string
var removalErrors []error
for _, entry := range allEntries {
if err := entry.Remove(); err != nil {
logrus.Errorf("Failed to remove registry entry %s: %v", entry.KeyName, err)
removalErrors = append(removalErrors, fmt.Errorf("failed to remove %s: %w", entry.KeyName, err))
} else {
removed = append(removed, fmt.Sprintf("%s (port %d)", entry.Purpose.String(), entry.Port))
}
}
if len(removed) > 0 {
fmt.Println("Successfully removed registry entries for:")
for _, entry := range removed {
fmt.Printf(" - %s\n", entry)
}
}
if len(removalErrors) > 0 {
fmt.Println("\nSome entries could not be removed. See logs for details.")
return errors.Join(removalErrors...)
}
return nil
}
func doResetForGroupMembership() error {
inGroup, err := hyperv.IsCurrentUserInHyperVAdminGroup()
if err != nil {
return fmt.Errorf("failed to check Hyper-V Administrators group membership: %w", err)
}
if !inGroup {
fmt.Println("Current user isn't a member of the Hyper-V Administrators group, it won't be removed.")
return nil
}
if !force {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Do you want to remove the current user from the Hyper-V Administrators group? [y/N] ")
answer, err := reader.ReadString('\n')
if err != nil {
return err
}
if strings.ToLower(answer)[0] != 'y' {
return nil
}
}
u, err := user.Current()
if err != nil {
return fmt.Errorf("failed to get current user: %w", err)
}
if err := hyperv.RemoveUserFromHyperVAdminGroup(u.Username); err != nil {
return fmt.Errorf("failed to remove user from Hyper-V Administrators group: %w", err)
}
fmt.Printf("Removed user %s from the Hyper-V Administrators group\n", u.Username)
return nil
}

View file

@ -10,4 +10,10 @@ if ($Env:CI -eq "true") {
Run-Command ".\winmake.ps1 localunit"
Run-Command ".\winmake.ps1 ginkgo-run" # non-machine e2e integration
# test. Currently it's only
# checking the hyperv-prep
# command and it doesn't deserve
# a specific task.
Pop-Location

View file

@ -0,0 +1,71 @@
% podman-system-hyperv-prep 1
## NAME
podman\-system\-hyperv\-prep - A Windows administrator command to prepare a host that is going to run Hyper-V based Podman machines
## SYNOPSIS
**podman system hyperv-prep** [*options*]
## DESCRIPTION
Prepare a Windows host to run Hyper-V based Podman machines by creating the
required registry entries in HKEY_LOCAL_MACHINE for Hyper-V VSock communication
and adding the current user to the Hyper-V Administrators group.
The registry entries are marked to persist even when all machines are removed,
and the group membership allows the user to manage Hyper-V virtual machines.
Together, these avoid the need for administrator privileges during normal machine
operations.
This command requires administrator privileges and is only available on Windows.
The **--status** option can be used without administrator privileges.
## OPTIONS
#### **--mounts**=*number*
Number of VSock entries for mount purpose. Every mounted host folder of a running machine needs a dedicated VSock. There should be enough VSock to satisfy the needs of running Podman machines on the host. The default is **2**.
#### **--force**, **-f**
Skip confirmation prompts during reset. Only valid with **--reset**.
#### **--reset**
Remove all Podman VSock registry entries and remove the current user from the
Hyper-V Administrators group. Prompts for confirmation before each action unless
**--force** is specified.
#### **--status**
Show the list of VSock registry entries and the current user's Hyper-V
Administrators group membership status.
## EXAMPLE
Create the required registry entries and add the current user to the Hyper-V
Administrators group:
```
podman system hyperv-prep
```
Show existing registry entries and group membership status:
```
podman system hyperv-prep --status
```
Remove all Podman VSock registry entries and the current user from the Hyper-V
Administrators group (with confirmation prompts):
```
podman system hyperv-prep --reset
```
Reset without confirmation prompts:
```
podman system hyperv-prep --reset --force
```
## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-system(1)](podman-system.1.md)**
## HISTORY
April 2026

View file

@ -15,6 +15,7 @@ The system command allows management of the podman systems
| ------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
| check | [podman-system-check(1)](podman-system-check.1.md) | Perform consistency checks on image and container storage.
| connection | [podman-system-connection(1)](podman-system-connection.1.md) | Manage the destination(s) for Podman service(s) |
| hyperv-prep| [podman-system-hyperv-prep(1)](podman-system-hyperv-prep.1.md) | A Windows administrator command to prepare a host that is going to run Hyper-V based Podman machines |
| df | [podman-system-df(1)](podman-system-df.1.md) | Show podman disk usage. |
| events | [podman-events(1)](podman-events.1.md) | Monitor Podman events |
| info | [podman-info(1)](podman-info.1.md) | Display Podman related system information. |

View file

@ -55,9 +55,13 @@ a VM, you must have a VM that supports nested virtualization.
Hyper-V is only available on Windows Enterprise, Pro, or Education editions (not
Home). The command to initialize the first Hyper-V Podman machine, and the command
to remove the last one, both require administrator privileges. Other commands for
machine management (start, stop, etc...) require that the current user is a member
of the Hyper-V administrators group.
to remove the last one, both require administrator privileges because they create
and delete machine-scope registry keys. Additionally, all Hyper-V machine commands
require that the current user is a member of the Hyper-V Administrators group. To
avoid these requirements, an administrator can run `podman system hyperv-prep`
once to pre-create the registry entries and add the current user to the Hyper-V
Administrators group, after which no machine operation requires administrator
privileges.
It is also recommended to install the modern "Windows Terminal," which
provides a superior user experience to the standard PowerShell and CMD
@ -176,10 +180,14 @@ PS C:\Users\User> podman machine init --provider hyperv
**Note:** Hyper-V requires administrator privileges to initialize the first
podman machine. Similarly it requires administrator privileges to remove the
last machine. That's because these operation create and delete
machine-scope registry keys, required to support the communication between the
guest OS and the host. Other commands such as machine start and stop require
that the current user is a member of the Hyper-V Administrator group.
last machine. That's because these operations create and delete machine-scope
registry keys, required to support the communication between the guest OS and
the host. Additionally, all Hyper-V machine commands require that the current
user is a member of the Hyper-V Administrators group. To avoid these
requirements, run `podman system hyperv-prep` once from an administrator
terminal to pre-create persistent registry entries and add the current user
to the Hyper-V Administrators group, so that all subsequent machine operations
can run without administrator privileges.
Starting Machine
----------------

View file

@ -53,6 +53,11 @@ for md in $(ls -1 *-*.1.md | grep -v remote);do
# dashes.
parent="podman.1.md"
fi
if [[ $parent =~ "podman-system-hyperv" ]]; then
# podman-system-hyperv-prep.1.md: "hyperv-prep" is a single
# hyphenated subcommand of "podman system", not a sub-subcommand.
parent="podman-system.1.md"
fi
x=3
if expr -- "$parent" : ".*-" >/dev/null; then
x=4
@ -79,7 +84,8 @@ function compare_usage() {
local from_man="$2"
# Special case: this is an external call to docker
if [[ $cmd = "podman compose" ]] || [[ $cmd = "podmansh" ]]; then
# or a Windows-only command not available on Linux
if [[ $cmd = "podman compose" ]] || [[ $cmd = "podmansh" ]] || [[ $cmd = "podman system hyperv-prep" ]]; then
return
fi
@ -158,6 +164,10 @@ for md in *.1.md;do
# special case: the command is "auto-update", with a hyphen
md_nodash='podman auto-update'
fi
if [[ $md_nodash = 'podman system hyperv prep' ]]; then
# special case: the command is "hyperv-prep", with a hyphen
md_nodash='podman system hyperv-prep'
fi
if [[ "$cmd" != "$md_nodash" ]] && [[ "$cmd" != "podman-remote" ]]; then
echo
printf "Inconsistent program name in SYNOPSIS in %s:\n" $md

View file

@ -24,6 +24,7 @@ var (
// Lazily load the NetAPI32 DLL and the function we need
modnetapi32 = syswindows.NewLazySystemDLL("netapi32.dll")
procNetLocalGroupAddMembers = modnetapi32.NewProc("NetLocalGroupAddMembers")
procNetLocalGroupDelMembers = modnetapi32.NewProc("NetLocalGroupDelMembers")
procNetLocalGroupGetMembers = modnetapi32.NewProc("NetLocalGroupGetMembers")
procNetApiBufferFree = modnetapi32.NewProc("NetApiBufferFree")
)
@ -90,6 +91,40 @@ func AddUserToHyperVAdminGroup(username string) error {
return nil
}
// RemoveUserFromHyperVAdminGroup removes the specified user from the local Hyper-V Administrators group.
func RemoveUserFromHyperVAdminGroup(username string) error {
groupPtr, err := getHyperVAdminGroupNamePtr()
if err != nil {
return err
}
userSid, _, _, err := syswindows.LookupSID("", username)
if err != nil {
return fmt.Errorf("failed to look up SID for user %q: %w", username, err)
}
info := localGroupMembersInfo0{
Sid: userSid,
}
// C Signature: NET_API_STATUS NetLocalGroupDelMembers(LPCWSTR servername, LPCWSTR groupname, DWORD level, LPBYTE buf, DWORD totalentries)
ret, _, _ := procNetLocalGroupDelMembers.Call(
0, // ServerName (0 = local machine)
uintptr(unsafe.Pointer(groupPtr)), // GroupName
0, // Level 0 (we are passing a SID)
uintptr(unsafe.Pointer(&info)), // Buffer containing our struct
1, // Total Entries being removed
)
errno := syswindows.Errno(ret)
// ERROR_MEMBER_NOT_IN_ALIAS = "The specified account name is not a member of the group."
if errno != syswindows.NO_ERROR && errno != syswindows.ERROR_MEMBER_NOT_IN_ALIAS {
return fmt.Errorf("NetLocalGroupDelMembers failed with error: %w", errno)
}
return nil
}
func getHyperVAdminGroupNamePtr() (*uint16, error) {
hypervAdminsSid, err := syswindows.CreateWellKnownSid(syswindows.WinBuiltinHyperVAdminsSid)
if err != nil {
@ -175,6 +210,24 @@ func isUserInGroup(groupPtr *uint16, userSid *syswindows.SID) (bool, error) {
return false, nil
}
// IsCurrentUserInHyperVAdminGroup checks the group database directly
// (via NetLocalGroupGetMembers) rather than the process token, so it
// reflects membership changes that haven't taken effect in the current
// logon session yet.
func IsCurrentUserInHyperVAdminGroup() (bool, error) {
userSid, err := getCurrentUserSID()
if err != nil {
return false, fmt.Errorf("failed to look up SID for current user: %w", err)
}
groupPtr, err := getHyperVAdminGroupNamePtr()
if err != nil {
return false, fmt.Errorf("failed to get localized Hyper-V Admin group name: %w", err)
}
return isUserInGroup(groupPtr, userSid)
}
// VerifyHyperVPermissions returns `nil` if the user has the privileges to manage
// Hyper-V VMs. This can be either because the command is run
// as an administrator or because the user is a member of the Hyper-V Admins group.

View file

@ -31,6 +31,22 @@ type HyperVStubber struct {
vmconfigs.HyperVConfig
}
type permissionChecks struct {
isElevatedProcess func() bool
isHyperVAdminMember func() bool
vsockEntriesExist func(int) bool
existingMachinesNum func() (int, error)
}
func (h HyperVStubber) defaultPermissionChecks() permissionChecks {
return permissionChecks{
isElevatedProcess: windows.HasAdminRights,
isHyperVAdminMember: IsHyperVAdminsGroupMember,
vsockEntriesExist: vsock.CheckIfHVSockRegistryEntriesExist,
existingMachinesNum: h.countMachinesWithToolname,
}
}
func (h HyperVStubber) UserModeNetworkEnabled(_ *vmconfigs.MachineConfig) bool {
return true
}
@ -59,15 +75,17 @@ func (h HyperVStubber) CreateVM(_ define.CreateVMOpts, mc *vmconfigs.MachineConf
// Allow creation in these two cases:
// 1. if the user is Admin
// 2. if the user has Hyper-V admin rights and there is at least one *NEW* machine.
// 2. if the user has Hyper-V admin rights and a vsock registry entry exists
// *NEW* machines are those created with the vsock entry having the ToolName field.
//
// This is to prevent a non-admin user from creating the first machine
// which would require adding vsock entries into the Windows Registry.
if err := h.canCreate(); err != nil {
// This is to prevent to prevent an error for trying to create a vsock entry
// in the Windows registry if the user doesn't have the privileges.
if err := h.canCreate(len(mc.Mounts)); err != nil {
// If it returns ErrHypervRegistryInitRequiresElevation and we're not already re-executing,
// offer to elevate automatically if user is in admin group
if err == ErrHypervRegistryInitRequiresElevation && !windows.IsReExecuting() && windows.IsInAdministratorsGroup() {
if errors.Is(err, ErrHypervRegistryInitRequiresElevation) &&
!windows.IsReExecuting() &&
windows.IsInAdministratorsGroup() {
message := fmt.Sprintf("%s.\n\n%s", ErrHypervPrepareHostForHyperV.Error(), windows.UACConfirmationPrompt)
return launchElevate(message)
}
@ -121,7 +139,7 @@ func (h HyperVStubber) CreateVM(_ define.CreateVMOpts, mc *vmconfigs.MachineConf
if !windows.HasAdminRights() {
return ErrHypervRegistryInitRequiresElevation
}
networkHVSock, err = vsock.NewHVSockRegistryEntry(vsock.Network)
networkHVSock, err = vsock.NewHVSockRegistryEntry(vsock.Network, false)
if err != nil {
return err
}
@ -210,7 +228,9 @@ func (h HyperVStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() err
if err := h.canRemove(mc); err != nil {
// If we get ErrHypervRegistryRemoveRequiresElevation and we're not already re-executing,
// and the user has admin rights (is in admin group), offer to elevate automatically
if err == ErrHypervRegistryRemoveRequiresElevation && !windows.IsReExecuting() && windows.IsInAdministratorsGroup() {
if errors.Is(err, ErrHypervRegistryRemoveRequiresElevation) &&
!windows.IsReExecuting() &&
windows.IsInAdministratorsGroup() {
message := "Removing this Hyper-V machine requires admin rights to clean up the Windows Registry.\n\n" +
windows.UACConfirmationPrompt
return nil, nil, launchElevate(message)
@ -250,24 +270,36 @@ func (h HyperVStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() err
return []string{}, rmFunc, nil
}
func (h HyperVStubber) canCreate() error {
// if admin, can always create
if windows.HasAdminRights() {
// canCreate checks if the user can create an Hyper-V machine.
// It returns `nil` if the user can create it. The conditions are:
// - the command is running as Administrator (elevated mode)
// OR
// - vsock entries in the Windows registry already exist and
// the user is a member of the Hyper-V Administrators group
//
// The `mounts` parameter is the number of Mounts of the machine. A
// specific Windows registry entry is necessary for every single mount.
//
// It returns `ErrHypervRegistryInitRequiresElevation` if the vsock
// entries in the Windows registry don't exist.
//
// It returns `ErrHypervUserNotInAdminGroup` if the user doesn't
// belong to the Hyper-V administrators group.
func (h HyperVStubber) canCreate(mounts int) error {
return checkCanCreate(h.defaultPermissionChecks(), mounts)
}
func checkCanCreate(checks permissionChecks, mounts int) error {
if checks.isElevatedProcess() {
return nil
}
// not admin, check if there is at least one existing machine
// if so, user could create more machines just by being member of the hyperv admin group
machines, err := h.countMachinesWithToolname()
if err != nil {
return err
}
// no existing machines, require to be admin
if machines == 0 {
if !checks.vsockEntriesExist(mounts) {
return ErrHypervRegistryInitRequiresElevation
}
// at least 1 machine exists
// if user is member of the hyperv admin group, allow creation
return VerifyHyperVPermissions()
if !checks.isHyperVAdminMember() {
return ErrHypervUserNotInAdminGroup
}
return nil
}
// launchElevate attempts to automatically re-run the command as administrator
@ -303,31 +335,59 @@ func isLegacyMachine(mc *vmconfigs.MachineConfig) bool {
return mc.HyperVHypervisor != nil && mc.HyperVHypervisor.ReadyVsock.MachineName != ""
}
// canRemove checks if the user can remove an Hyper-V machine.
// It returns `nil` when the user can remove it. The conditions are:
// - the command is running as Administrator (elevated mode)
// OR
// - the machine isn't a legacy one (one that doesn't share the vsock) AND
// the user is a member of the Hyper-V Administrators group AND
// it's not the last machine OR the vsock entries have `KeepAfterMachineRemove=true`
//
// It returns `ErrHypervRegistryRemoveRequiresElevation` if the machine is legacy one OR
// if the vsock entry in the Windows registry doesn't have `KeepAfterMachineRemove=true`.
//
// It returns `ErrHypervUserNotInAdminGroup` if the user doesn't
// belogn to the Hyper-V administrators group.
func (h HyperVStubber) canRemove(mc *vmconfigs.MachineConfig) error {
// if admin, can always remove
if windows.HasAdminRights() {
return checkCanRemove(mc, h.defaultPermissionChecks())
}
func checkCanRemove(mc *vmconfigs.MachineConfig, checks permissionChecks) error {
if checks.isElevatedProcess() {
return nil
}
// if machine is legacy (machineName field), require admin rights to remove
if isLegacyMachine(mc) {
return ErrHypervRegistryRemoveRequiresElevation
return ErrHypervLegacyMachineRequiresElevation
}
// not admin, check if there are multiple machines
// if so, user could remove the machine just by being member of the hyperv admin group
// (only the last machine removal requires Registry changes)
machines, err := h.countMachinesWithToolname()
if !checks.isHyperVAdminMember() {
return ErrHypervUserNotInAdminGroup
}
machines, err := checks.existingMachinesNum()
if err != nil {
return err
}
// more than 1 machine exists, allow removal if user has hyperv admin rights
if machines > 1 {
return VerifyHyperVPermissions()
if canSkipVSockEntriesRemoval(machines, *mc.HyperVHypervisor) {
return nil
}
return ErrHypervRegistryRemoveRequiresElevation
}
func canSkipVSockEntriesRemoval(machines int, hv vmconfigs.HyperVConfig) bool {
if machines > 1 {
return true
}
if !hv.NetworkVSock.KeepAfterMachineRemove ||
!hv.ReadyVsock.KeepAfterMachineRemove {
return false
}
for _, m := range hv.FileserverVSocks {
if !m.KeepAfterMachineRemove {
return false
}
}
return true
}
// canStartOrStop checks if the machine can be started or stopped.
// Legacy machines require admin rights to start or stop.
func (h HyperVStubber) canStartOrStop(mc *vmconfigs.MachineConfig) error {
@ -630,7 +690,7 @@ func (h HyperVStubber) PrepareIgnition(mc *vmconfigs.MachineConfig, _ *ignition.
}
return nil, ErrHypervRegistryInitRequiresElevation
}
readySock, err = vsock.NewHVSockRegistryEntry(vsock.Events)
readySock, err = vsock.NewHVSockRegistryEntry(vsock.Events, false)
if err != nil {
return nil, err
}

View file

@ -0,0 +1,180 @@
//go:build windows
package hyperv
import (
"testing"
"github.com/stretchr/testify/assert"
"go.podman.io/podman/v6/pkg/machine/hyperv/vsock"
"go.podman.io/podman/v6/pkg/machine/vmconfigs"
)
func TestCanCreate(t *testing.T) {
t.Parallel()
tests := []struct {
name string
isElevated bool
vsockEntriesExist bool
isHyperVAdmin bool
mounts int
expectedErr error
}{
{
name: "in elevated process can always create",
isElevated: true,
vsockEntriesExist: false,
isHyperVAdmin: false,
mounts: 2,
expectedErr: nil,
},
{
name: "not elevated, no vsock entries",
isElevated: false,
vsockEntriesExist: false,
isHyperVAdmin: true,
expectedErr: ErrHypervRegistryInitRequiresElevation,
},
{
name: "not elevated, vsock entries exist, not hyperv admin",
isElevated: false,
vsockEntriesExist: true,
isHyperVAdmin: false,
expectedErr: ErrHypervUserNotInAdminGroup,
},
{
name: "not elevated, vsock entries exist, hyperv admin",
isElevated: false,
vsockEntriesExist: true,
isHyperVAdmin: true,
expectedErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
checks := permissionChecks{
isElevatedProcess: func() bool { return tt.isElevated },
isHyperVAdminMember: func() bool { return tt.isHyperVAdmin },
vsockEntriesExist: func(int) bool { return tt.vsockEntriesExist },
}
err := checkCanCreate(checks, tt.mounts)
assert.ErrorIs(t, err, tt.expectedErr)
})
}
}
func TestCanRemove(t *testing.T) {
t.Parallel()
tests := []struct {
name string
isElevatedProcess bool
isHyperVAdminMember bool
isLegacyMachine bool
skipVsockEntriesRemoval bool
isLastMachine bool
expectedErr error
}{
{
name: "elevated process can always remove",
isElevatedProcess: true,
isHyperVAdminMember: false,
isLegacyMachine: false,
skipVsockEntriesRemoval: false,
isLastMachine: true,
expectedErr: nil,
},
{
name: "not elevated, legacy machine requires elevation",
isElevatedProcess: false,
isHyperVAdminMember: false,
isLegacyMachine: true,
skipVsockEntriesRemoval: false,
isLastMachine: true,
expectedErr: ErrHypervLegacyMachineRequiresElevation,
},
{
name: "not elevated, not hyperv admins member",
isElevatedProcess: false,
isHyperVAdminMember: false,
isLegacyMachine: false,
skipVsockEntriesRemoval: false,
isLastMachine: true,
expectedErr: ErrHypervUserNotInAdminGroup,
},
{
name: "not elevated, hyperv admins member, not last machine",
isElevatedProcess: false,
isHyperVAdminMember: true,
isLegacyMachine: false,
skipVsockEntriesRemoval: false,
isLastMachine: false,
expectedErr: nil,
},
{
name: "not elevated, hyperv admins member, last machine, skip vsock entries removal",
isElevatedProcess: false,
isHyperVAdminMember: true,
isLegacyMachine: false,
skipVsockEntriesRemoval: true,
isLastMachine: true,
expectedErr: nil,
},
{
name: "not elevated, hyperv admins member, last machine, don't skip vsock entries removal",
isElevatedProcess: false,
isHyperVAdminMember: true,
isLegacyMachine: false,
skipVsockEntriesRemoval: false,
isLastMachine: true,
expectedErr: ErrHypervRegistryRemoveRequiresElevation,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
checks := permissionChecks{
isElevatedProcess: func() bool { return tt.isElevatedProcess },
isHyperVAdminMember: func() bool { return tt.isHyperVAdminMember },
existingMachinesNum: func() (int, error) {
if tt.isLastMachine {
return 1, nil
} else {
return 2, nil
}
},
}
vsockMachineName := ""
if tt.isLegacyMachine {
vsockMachineName = "podmanmachine"
}
mc := &vmconfigs.MachineConfig{
HyperVHypervisor: &vmconfigs.HyperVConfig{
ReadyVsock: vsock.HVSockRegistryEntry{
MachineName: vsockMachineName,
KeepAfterMachineRemove: tt.skipVsockEntriesRemoval,
},
NetworkVSock: vsock.HVSockRegistryEntry{
KeepAfterMachineRemove: tt.skipVsockEntriesRemoval,
},
},
}
err := checkCanRemove(mc, checks)
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
} else {
assert.NoError(t, err)
}
})
}
}

View file

@ -66,13 +66,14 @@ func createShares(mc *vmconfigs.MachineConfig) (err error) {
}
return ErrHypervRegistryUpdateRequiresElevation
}
testVsock, err = vsock.NewHVSockRegistryEntry(vsock.Fileserver)
testVsock, err = vsock.NewHVSockRegistryEntry(vsock.Fileserver, false)
if err != nil {
return err
}
}
mount.VSockNumber = &testVsock.Port
mc.HyperVHypervisor.FileserverVSocks = append(mc.HyperVHypervisor.FileserverVSocks, *testVsock)
logrus.Debugf("Going to share directory %s via 9p on vsock %d", mount.Source, testVsock.Port)
}
return nil

View file

@ -27,6 +27,11 @@ const (
PodmanToolName = "podman"
// HvsockPurpose is the string identifier for the sock purpose in a registry entry
HvsockPurpose = "Purpose"
// HvsockKeepAfterMachineRemove is a flag to avoid that the entry gets deleted when
// the last machine gets deleted. This is important for use cases where an Administrator
// wants to pre-create the registry key so that the user can create and delete machines
// without requiring admin privileges.
HvsockKeepAfterMachineRemove = "KeepAfterMachineRemove"
// VsockRegistryPath describes the registry path to where the hvsock registry entries live
VsockRegistryPath = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices`
// LinuxVM is the default guid for a Linux VM on Windows
@ -45,7 +50,7 @@ const (
Fileserver
)
func (hv HVSockPurpose) string() string {
func (hv HVSockPurpose) String() string {
switch hv {
case Network:
return "Network"
@ -58,7 +63,7 @@ func (hv HVSockPurpose) string() string {
}
func (hv HVSockPurpose) Equal(purpose string) bool {
return hv.string() == purpose
return hv.String() == purpose
}
func toHVSockPurpose(p string) (HVSockPurpose, error) {
@ -90,8 +95,25 @@ type HVSockRegistryEntry struct {
// ToolName identifies the application that created this registry entry (e.g., Podman).
// This provides information about the entry's origin and can be used for filter entries
// if purpose is not enough.
ToolName string `json:"creator_tool,omitempty"`
Key registry.Key `json:"key,omitempty"`
ToolName string `json:"creator_tool,omitempty"`
// KeepAfterMachineRemove is set to true if the registry entry should not be deleted
// when the user deletes or reset the Podman machines.
// This is useful to let unprivileged users create and delete machines without
// requiring privileges to edit the registry.
KeepAfterMachineRemove bool `json:"keep_after_machine_remove,omitempty"`
}
func (hv *HVSockRegistryEntry) String() string {
keepStatus := ""
if hv.KeepAfterMachineRemove {
keepStatus = " (persistent)"
}
return fmt.Sprintf("- Key: %s\n Purpose: %s\n Port: %d%s\n",
hv.KeyName,
hv.Purpose,
hv.Port,
keepStatus)
}
// Add creates a new Windows registry entry with string values from the
@ -105,31 +127,41 @@ func (hv *HVSockRegistryEntry) Add() error {
return err
}
if exists {
return fmt.Errorf("%q: %s", ErrVSockRegistryEntryExists, hv.KeyName)
return fmt.Errorf("%w: %s", ErrVSockRegistryEntryExists, hv.KeyName)
}
parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, VsockRegistryPath, registry.QUERY_VALUE)
if err != nil {
return err
}
defer func() {
if err := parentKey.Close(); err != nil {
logrus.Error(err)
}
}()
newKey, _, err := registry.CreateKey(parentKey, hv.KeyName, registry.WRITE)
if err != nil {
return err
}
newKey, _, err := registry.CreateKey(parentKey, hv.KeyName, registry.WRITE)
defer func() {
if err := newKey.Close(); err != nil {
logrus.Error(err)
}
}()
if err != nil {
return err
}
if err := newKey.SetStringValue(HvsockPurpose, hv.Purpose.string()); err != nil {
if err := newKey.SetStringValue(HvsockPurpose, hv.Purpose.String()); err != nil {
return err
}
return newKey.SetStringValue(HvsockToolName, hv.ToolName)
if err := newKey.SetStringValue(HvsockToolName, hv.ToolName); err != nil {
return err
}
// Save KeepAfterMachineRemove flag if set
if hv.KeepAfterMachineRemove {
var value uint32 = 1
if err := newKey.SetDWordValue(HvsockKeepAfterMachineRemove, value); err != nil {
return err
}
}
return nil
}
// Remove deletes the registry key and its string values
@ -145,7 +177,7 @@ func (hv *HVSockRegistryEntry) validate() error {
if hv.Port < 1 {
return errors.New("port must be larger than 1")
}
if len(hv.Purpose.string()) < 1 {
if len(hv.Purpose.String()) < 1 {
return errors.New("required field purpose is empty")
}
if len(hv.ToolName) < 1 {
@ -170,9 +202,9 @@ func (hv *HVSockRegistryEntry) exists() (bool, error) {
return false, err
}
// findOpenHVSockPort looks for an open random port. it verifies the port is not
// FindOpenHVSockPort looks for an open random port. it verifies the port is not
// already being used by another hvsock in the Windows registry.
func findOpenHVSockPort() (uint64, error) {
func FindOpenHVSockPort() (uint64, error) {
// If we cannot find a free port in 10 attempts, something is wrong
for range 10 {
port, err := utils.GetRandomPort()
@ -190,29 +222,27 @@ func findOpenHVSockPort() (uint64, error) {
// the port is good to go
return uint64(port), nil
}
if err != nil {
// something went wrong
return 0, err
}
return 0, err
}
return 0, errors.New("unable to find a free port for hvsock use")
}
// NewHVSockRegistryEntry is a constructor to make a new registry entry in Windows. After making the new
// object, you must call the add() method to *actually* add it to the Windows registry.
func NewHVSockRegistryEntry(purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
func NewHVSockRegistryEntry(purpose HVSockPurpose, keep bool) (*HVSockRegistryEntry, error) {
// a so-called wildcard entry ... everything from FACB -> 6D3 is MS special sauce
// for a " linux vm". this first segment is hexi for the hvsock port number
// 00000400-FACB-11E6-BD58-64006A7986D3
port, err := findOpenHVSockPort()
port, err := FindOpenHVSockPort()
if err != nil {
return nil, err
}
r := HVSockRegistryEntry{
KeyName: portToKeyName(port),
Purpose: purpose,
Port: port,
ToolName: PodmanToolName,
KeyName: PortToKeyName(port),
Purpose: purpose,
Port: port,
ToolName: PodmanToolName,
KeepAfterMachineRemove: keep,
}
if err := r.Add(); err != nil {
return nil, err
@ -220,7 +250,7 @@ func NewHVSockRegistryEntry(purpose HVSockPurpose) (*HVSockRegistryEntry, error)
return &r, nil
}
func portToKeyName(port uint64) string {
func PortToKeyName(port uint64) string {
// this could be flattened but given the complexity, I thought it might
// be more difficult to read
hexi := strings.ToUpper(fmt.Sprintf("%08x", port))
@ -228,12 +258,17 @@ func portToKeyName(port uint64) string {
}
func LoadHVSockRegistryEntry(port uint64) (*HVSockRegistryEntry, error) {
keyName := portToKeyName(port)
keyName := PortToKeyName(port)
fqPath := fmt.Sprintf("%s\\%s", VsockRegistryPath, keyName)
k, err := openVSockRegistryEntry(fqPath)
if err != nil {
return nil, err
}
defer func(key registry.Key) {
if err := key.Close(); err != nil {
logrus.Errorf("failed to close registry key: %v", err)
}
}(k)
p, _, err := k.GetStringValue(HvsockPurpose)
if err != nil {
return nil, err
@ -253,7 +288,6 @@ func LoadHVSockRegistryEntry(port uint64) (*HVSockRegistryEntry, error) {
KeyName: keyName,
Purpose: purpose,
Port: port,
Key: k,
MachineName: m,
}, nil
}
@ -290,8 +324,6 @@ func (hv *HVSockRegistryEntry) ListenSetupWait() (func() error, io.Closer, error
// loadAllHVSockRegistryEntries loads HVSock registry entries, filtered by purpose and optionally limited by size.
// If limit is -1, it returns all matching entries. Otherwise, it returns up to 'limit' entries.
// The caller is responsible for closing the registry.Key in each returned HVSockRegistryEntry.
// Non-matching or excess keys are closed within this function.
func loadHVSockRegistryEntries(purpose HVSockPurpose, limit int) ([]*HVSockRegistryEntry, error) {
parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, VsockRegistryPath, registry.ENUMERATE_SUB_KEYS)
if err != nil {
@ -322,22 +354,29 @@ func loadHVSockRegistryEntries(purpose HVSockPurpose, limit int) ([]*HVSockRegis
logrus.Debugf("Could not open registry entry %s: %v", fqPath, err)
continue
}
defer func(key registry.Key) {
if err := key.Close(); err != nil {
logrus.Errorf("failed to close registry key: %v", err)
}
}(k)
p, _, err := k.GetStringValue(HvsockPurpose)
if err != nil {
logrus.Debugf("Could not read purpose from registry entry %s: %v", fqPath, err)
k.Close()
continue
}
toolName, _, err := k.GetStringValue(HvsockToolName)
if err != nil {
logrus.Debugf("Could not read tool name from registry entry %s: %v", fqPath, err)
k.Close()
continue
}
k.Close()
keep, _, err := k.GetIntegerValue(HvsockKeepAfterMachineRemove)
keepBool := false
if err == nil {
keepBool = keep != 0
}
entryPurpose, err := toHVSockPurpose(p)
if err != nil {
@ -345,7 +384,7 @@ func loadHVSockRegistryEntries(purpose HVSockPurpose, limit int) ([]*HVSockRegis
continue
}
if !entryPurpose.Equal(purpose.string()) {
if !entryPurpose.Equal(purpose.String()) {
continue
}
@ -367,24 +406,41 @@ func loadHVSockRegistryEntries(purpose HVSockPurpose, limit int) ([]*HVSockRegis
}
allEntries = append(allEntries, &HVSockRegistryEntry{
KeyName: subKeyName,
Purpose: entryPurpose,
Port: port,
Key: k,
ToolName: PodmanToolName,
KeyName: subKeyName,
Purpose: entryPurpose,
Port: port,
ToolName: PodmanToolName,
KeepAfterMachineRemove: keepBool,
})
}
return allEntries, nil
}
func CheckIfHVSockRegistryEntriesExist(mountsNum int) bool {
// The number or required HVSock registry entries
// depends on the purpose
requiredEntries := map[HVSockPurpose]int{
Network: 1,
Events: 1,
Fileserver: mountsNum,
}
for p, i := range requiredEntries {
entries, err := loadHVSockRegistryEntries(p, i)
if len(entries) < i || err != nil {
return false
}
}
return true
}
func LoadHVSockRegistryEntryByPurpose(purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
entries, err := loadHVSockRegistryEntries(purpose, 1)
if err != nil {
return nil, err
}
if len(entries) != 1 {
return nil, fmt.Errorf("no hvsock registry entry found for purpose: %s", purpose.string())
return nil, fmt.Errorf("no hvsock registry entry found for purpose: %s", purpose.String())
}
return entries[0], nil
@ -429,6 +485,10 @@ func RemoveAllHVSockRegistryEntries() error {
var removalErrors []error
for _, sock := range allSocks {
// Don't remove registry entries if KeepAfterMachineRemove is set to true
if sock.KeepAfterMachineRemove {
continue
}
if err := sock.Remove(); err != nil {
logrus.Errorf("unable to remove registry entry for %s: %q", sock.KeyName, err)
removalErrors = append(removalErrors, fmt.Errorf("failed to remove sock %s: %w", sock.KeyName, err))

View file

@ -12,6 +12,8 @@ type HyperVConfig struct {
ReadyVsock vsock.HVSockRegistryEntry
// NetworkVSock is for the user networking
NetworkVSock vsock.HVSockRegistryEntry
// FileserverVSocks are for machine mounts (one entry per mount)
FileserverVSocks []vsock.HVSockRegistryEntry
}
type WSLConfig struct {

View file

@ -0,0 +1,86 @@
//go:build windows
package integration
import (
"os"
"path/filepath"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.podman.io/podman/v6/pkg/machine/windows"
. "go.podman.io/podman/v6/test/utils"
)
type PodmanTestIntegration struct {
PodmanTest
TempDir string
}
var podmanTest *PodmanTestIntegration
type PodmanSessionIntegration struct {
*PodmanSession
}
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func TestE2E(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "E2E Suite - Windows")
}
var _ = BeforeEach(func() {
tempDir, err := os.MkdirTemp("", "podman_test")
Expect(err).ToNot(HaveOccurred())
podmanTest = PodmanTestCreate(tempDir)
})
var _ = AfterEach(func() {
if podmanTest != nil && podmanTest.TempDir != "" {
os.RemoveAll(podmanTest.TempDir)
}
})
func PodmanTestCreate(tempDir string) *PodmanTestIntegration {
cwd, _ := os.Getwd()
podmanBinary := filepath.Join(cwd, "../../bin/windows/podman.exe")
if envBinary := os.Getenv("PODMAN_BINARY"); envBinary != "" {
podmanBinary = envBinary
}
p := &PodmanTestIntegration{
PodmanTest: PodmanTest{
PodmanBinary: podmanBinary,
},
TempDir: tempDir,
}
p.PodmanMakeOptions = p.makeOptions
return p
}
func skipIfNotAdmin(reason string) {
if !windows.HasAdminRights() {
Skip(reason)
}
}
func (p *PodmanTestIntegration) makeOptions(args []string, _ PodmanExecOptions) []string {
return args
}
func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration {
podmanSession := p.PodmanExecBaseWithOptions(args, PodmanExecOptions{})
return &PodmanSessionIntegration{podmanSession}
}
func (p *PodmanTestIntegration) PodmanExitCleanly(args ...string) *PodmanSessionIntegration {
GinkgoHelper()
session := p.Podman(args)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
return session
}

View file

@ -0,0 +1,96 @@
//go:build windows
package integration
import (
"os/exec"
"os/user"
"strings"
"go.podman.io/podman/v6/pkg/machine/hyperv/vsock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "go.podman.io/podman/v6/test/utils"
)
var _ = Describe("podman system hyperv-prep", func() {
It("rejects incompatible flags together", func() {
session := podmanTest.Podman([]string{"system", "hyperv-prep", "--status", "--reset"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError(125, "none of the others can be"))
session = podmanTest.Podman([]string{"system", "hyperv-prep", "--status", "--mounts", "1"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError(125, "none of the others can be"))
session = podmanTest.Podman([]string{"system", "hyperv-prep", "--reset", "--mounts", "1"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError(125, "none of the others can be"))
})
It("creates registry entries and resets them", func() {
skipIfNotAdmin("test requires an elevated (admin) terminal")
// Preconditions: no existing registry entries and user not in Hyper-V Administrators group
Expect(vsock.CheckIfHVSockRegistryEntriesExist(1)).To(BeFalse(),
"vsock registry entries already exist, cannot run test")
Expect(isCurrentUserHyperVAdmin()).To(BeFalse(),
"user is already a member of the Hyper-V Administrators group, cannot run test")
// Run `--status` a first time to check that it reports correctly that:
// - No vsock registry keys exist
// - User isn't a member of the Hyper-V Administrators group
session := podmanTest.Podman([]string{"system", "hyperv-prep", "--status"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(ContainSubstring("No vsock registry entries found"))
Expect(session.OutputToString()).To(ContainSubstring("Current user is NOT a member"))
// Run hyperv-prep to create registry entries
session = podmanTest.Podman([]string{"system", "hyperv-prep"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(ContainSubstring("Network"))
Expect(session.OutputToString()).To(ContainSubstring("Events"))
Expect(session.OutputToString()).To(ContainSubstring("Fileserver"))
// Check that running hyperv-prep worked:
// - The registry entries for vsock have been created (for 2 mounts)
// - The user is a member of the Hyper-V Administrators group
Expect(vsock.CheckIfHVSockRegistryEntriesExist(2)).To(BeTrue(),
"vsock registry entries don't exist after running hyperv-prep")
Expect(isCurrentUserHyperVAdmin()).To(BeTrue(),
"user isn't a member of the Hyper-V Administrators group after running hyperv-prep")
// Run --status a second time to shows the created entries
session = podmanTest.Podman([]string{"system", "hyperv-prep", "--status"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(ContainSubstring("Network"))
Expect(session.OutputToString()).To(ContainSubstring("Events"))
Expect(session.OutputToString()).To(ContainSubstring("Fileserver"))
// Reset the entries to clean up
session = podmanTest.Podman([]string{"system", "hyperv-prep", "--reset", "--force"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(ContainSubstring("Successfully removed"))
})
})
func isCurrentUserHyperVAdmin() bool {
u, err := user.Current()
if err != nil {
return false
}
out, err := exec.Command("powershell", "-NoProfile", "-Command",
`Get-LocalGroupMember -Name "Hyper-V Administrators"`).Output()
if err != nil {
return false
}
for _, line := range strings.Split(string(out), "\n") {
if strings.Contains(line, u.Username) {
return true
}
}
return false
}

View file

@ -78,6 +78,15 @@ function Local-Machine {
param (
[string]$files
);
Ginkgo-Run -files $files -testPath 'pkg/machine/e2e/.' -defaultTimeout '50m'
}
function Ginkgo-Run {
param (
[string]$files,
[string]$testPath = 'test/e2e/.',
[string]$defaultTimeout = '5m'
);
Build-Ginkgo
if ($files) {
$files = "--focus-file ""$files"""
@ -89,8 +98,8 @@ function Local-Machine {
$focus = "--focus ""$FOCUS"" --silence-skips"
}
if ($null -eq $ENV:GINKGOTIMEOUT) { $ENV:GINKGOTIMEOUT = '--timeout=50m' }
Run-Command "./bin/ginkgo.exe -vv --tags `"$remotetags`" ${ENV:GINKGOTIMEOUT} --trace --no-color $focus $files pkg/machine/e2e/."
if ($null -eq $ENV:GINKGOTIMEOUT) { $ENV:GINKGOTIMEOUT = "--timeout=$defaultTimeout" }
Run-Command "./bin/ginkgo.exe -vv --tags `"$remotetags`" ${ENV:GINKGOTIMEOUT} --trace --no-color $focus $files $testPath"
}
# Expect starting directory to be /podman
@ -379,6 +388,12 @@ switch ($target) {
}
Local-Machine -files $files
}
'ginkgo-run' {
if ($params.Count -gt 1) {
$files = $params[1]
}
Ginkgo-Run -files $files
}
'clean' {
Make-Clean
}
@ -431,6 +446,12 @@ switch ($target) {
Write-Host 'Example: Run specific machine tests '
Write-Host ' .\winmake localmachine 'basic_test.go""
Write-Host
Write-Host 'Example: Run all e2e tests '
Write-Host ' .\winmake ginkgo-run'
Write-Host
Write-Host 'Example: Run specific e2e tests '
Write-Host ' .\winmake ginkgo-run "system_hyperv_prep_test.go"'
Write-Host
Write-Host 'Example: Download win-gvproxy and win-sshproxy helpers'
Write-Host ' .\winmake win-gvproxy'
Write-Host