mirror of
https://github.com/containers/podman.git
synced 2025-06-19 08:09:12 +08:00
Add API forwarding support for HyperV
Provides Docker API client access, allowing compose to work by default for HyperV. Basically the HyperV equiv of the work done here by #12916. [NO NEW TESTS NEEDED] Signed-off-by: Ashley Cui <acui@redhat.com>
This commit is contained in:
@ -187,7 +187,7 @@ func composeDockerHost() (string, error) {
|
||||
if info.State != define.Running {
|
||||
return "", fmt.Errorf("machine %s is not running but in state %s", item.Name, info.State)
|
||||
}
|
||||
if machineProvider.VMType() == define.WSLVirt {
|
||||
if machineProvider.VMType() == define.WSLVirt || machineProvider.VMType() == define.HyperVVirt {
|
||||
if info.ConnectionInfo.PodmanPipe == nil {
|
||||
return "", errors.New("pipe of machine is not set")
|
||||
}
|
||||
|
@ -343,11 +343,14 @@ func (m *HyperVMachine) Inspect() (*machine.InspectInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
machinePipe := machine.ToDist(m.Name)
|
||||
podmanPipe := &define.VMFile{Path: `\\.\pipe\` + machinePipe}
|
||||
|
||||
return &machine.InspectInfo{
|
||||
ConfigPath: m.ConfigPath,
|
||||
ConnectionInfo: machine.ConnectionConfig{
|
||||
PodmanSocket: podmanSocket,
|
||||
PodmanPipe: podmanPipe,
|
||||
},
|
||||
Created: m.Created,
|
||||
Image: machine.ImageConfig{
|
||||
@ -592,6 +595,15 @@ func (m *HyperVMachine) Start(name string, opts machine.StartOptions) error {
|
||||
m.HostUser.Modified = false
|
||||
}
|
||||
}
|
||||
winProxyOpts := machine.WinProxyOpts{
|
||||
Name: m.Name,
|
||||
IdentityPath: m.IdentityPath,
|
||||
Port: m.Port,
|
||||
RemoteUsername: m.RemoteUsername,
|
||||
Rootful: m.Rootful,
|
||||
VMType: vmtype,
|
||||
}
|
||||
machine.LaunchWinProxy(winProxyOpts, opts.NoInfo)
|
||||
|
||||
// Write the config with updated starting status and hostuser modification
|
||||
if err := m.writeConfig(); err != nil {
|
||||
@ -653,6 +665,10 @@ func (m *HyperVMachine) Stop(name string, opts machine.StopOptions) error {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
if err := machine.StopWinProxy(m.Name, vmtype); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
|
||||
}
|
||||
|
||||
if err := vm.Stop(); err != nil {
|
||||
return fmt.Errorf("stopping virtual machine: %w", err)
|
||||
}
|
||||
|
@ -3,13 +3,38 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containers/podman/v4/pkg/machine/define"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
pipePrefix = "npipe:////./pipe/"
|
||||
globalPipe = "docker_engine"
|
||||
winSShProxy = "win-sshproxy.exe"
|
||||
winSshProxyTid = "win-sshproxy.tid"
|
||||
rootfulSock = "/run/podman/podman.sock"
|
||||
rootlessSock = "/run/user/1000/podman/podman.sock"
|
||||
)
|
||||
|
||||
const WM_QUIT = 0x12 //nolint
|
||||
|
||||
type WinProxyOpts struct {
|
||||
Name string
|
||||
IdentityPath string
|
||||
Port int
|
||||
RemoteUsername string
|
||||
Rootful bool
|
||||
VMType define.VMType
|
||||
}
|
||||
|
||||
func GetProcessState(pid int) (active bool, exitCode int) {
|
||||
const da = syscall.STANDARD_RIGHTS_READ | syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
|
||||
handle, err := syscall.OpenProcess(da, false, uint32(pid))
|
||||
@ -45,3 +70,172 @@ func WaitPipeExists(pipeName string, retries int, checkFailure func() error) err
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func LaunchWinProxy(opts WinProxyOpts, noInfo bool) {
|
||||
globalName, pipeName, err := launchWinProxy(opts)
|
||||
if !noInfo {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n", err.Error())
|
||||
fmt.Fprintln(os.Stderr, "\nPodman clients are still able to connect.")
|
||||
} else {
|
||||
fmt.Printf("API forwarding listening on: %s\n", pipeName)
|
||||
if globalName {
|
||||
fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n")
|
||||
} else {
|
||||
fmt.Printf("\nAnother process was listening on the default Docker API pipe address.\n")
|
||||
fmt.Printf("You can still connect Docker API clients by setting DOCKER HOST using the\n")
|
||||
fmt.Printf("following powershell command in your terminal session:\n")
|
||||
fmt.Printf("\n\t$Env:DOCKER_HOST = '%s'\n", pipeName)
|
||||
fmt.Printf("\nOr in a classic CMD prompt:\n")
|
||||
fmt.Printf("\n\tset DOCKER_HOST=%s\n", pipeName)
|
||||
fmt.Printf("\nAlternatively, terminate the other process and restart podman machine.\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func launchWinProxy(opts WinProxyOpts) (bool, string, error) {
|
||||
machinePipe := ToDist(opts.Name)
|
||||
if !PipeNameAvailable(machinePipe) {
|
||||
return false, "", fmt.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe)
|
||||
}
|
||||
|
||||
globalName := false
|
||||
if PipeNameAvailable(globalPipe) {
|
||||
globalName = true
|
||||
}
|
||||
|
||||
command, err := FindExecutablePeer(winSShProxy)
|
||||
if err != nil {
|
||||
return globalName, "", err
|
||||
}
|
||||
|
||||
stateDir, err := GetWinProxyStateDir(opts.Name, opts.VMType)
|
||||
if err != nil {
|
||||
return globalName, "", err
|
||||
}
|
||||
|
||||
destSock := rootlessSock
|
||||
forwardUser := opts.RemoteUsername
|
||||
|
||||
if opts.Rootful {
|
||||
destSock = rootfulSock
|
||||
forwardUser = "root"
|
||||
}
|
||||
|
||||
dest := fmt.Sprintf("ssh://%s@localhost:%d%s", forwardUser, opts.Port, destSock)
|
||||
args := []string{opts.Name, stateDir, pipePrefix + machinePipe, dest, opts.IdentityPath}
|
||||
waitPipe := machinePipe
|
||||
if globalName {
|
||||
args = append(args, pipePrefix+globalPipe, dest, opts.IdentityPath)
|
||||
waitPipe = globalPipe
|
||||
}
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return globalName, "", err
|
||||
}
|
||||
|
||||
return globalName, pipePrefix + waitPipe, WaitPipeExists(waitPipe, 80, func() error {
|
||||
active, exitCode := GetProcessState(cmd.Process.Pid)
|
||||
if !active {
|
||||
return fmt.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func StopWinProxy(name string, vmtype define.VMType) error {
|
||||
pid, tid, tidFile, err := readWinProxyTid(name, vmtype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(int(pid))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
sendQuit(tid)
|
||||
_ = waitTimeout(proc, 20*time.Second)
|
||||
_ = os.Remove(tidFile)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readWinProxyTid(name string, vmtype define.VMType) (uint32, uint32, string, error) {
|
||||
stateDir, err := GetWinProxyStateDir(name, vmtype)
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
tidFile := filepath.Join(stateDir, winSshProxyTid)
|
||||
contents, err := os.ReadFile(tidFile)
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
var pid, tid uint32
|
||||
fmt.Sscanf(string(contents), "%d:%d", &pid, &tid)
|
||||
return pid, tid, tidFile, nil
|
||||
}
|
||||
|
||||
func waitTimeout(proc *os.Process, timeout time.Duration) bool {
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
proc.Wait()
|
||||
done <- true
|
||||
}()
|
||||
ret := false
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
proc.Kill()
|
||||
<-done
|
||||
case <-done:
|
||||
ret = true
|
||||
break
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func sendQuit(tid uint32) {
|
||||
user32 := syscall.NewLazyDLL("user32.dll")
|
||||
postMessage := user32.NewProc("PostThreadMessageW")
|
||||
postMessage.Call(uintptr(tid), WM_QUIT, 0, 0)
|
||||
}
|
||||
|
||||
func FindExecutablePeer(name string) (string, error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
exe, err = filepath.EvalSymlinks(exe)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(filepath.Dir(exe), name), nil
|
||||
}
|
||||
|
||||
func GetWinProxyStateDir(name string, vmtype define.VMType) (string, error) {
|
||||
dir, err := GetDataDir(vmtype)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stateDir := filepath.Join(dir, name)
|
||||
if err = os.MkdirAll(stateDir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return stateDir, nil
|
||||
}
|
||||
|
||||
func ToDist(name string) string {
|
||||
if !strings.HasPrefix(name, "podman") {
|
||||
name = "podman-" + name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
@ -273,14 +273,13 @@ http://docs.microsoft.com/en-us/windows/wsl/install\
|
||||
`
|
||||
|
||||
const (
|
||||
gvProxy = "gvproxy.exe"
|
||||
winSShProxy = "win-sshproxy.exe"
|
||||
winSshProxyTid = "win-sshproxy.tid"
|
||||
pipePrefix = "npipe:////./pipe/"
|
||||
globalPipe = "docker_engine"
|
||||
userModeDist = "podman-net-usermode"
|
||||
rootfulSock = "/run/podman/podman.sock"
|
||||
rootlessSock = "/run/user/1000/podman/podman.sock"
|
||||
gvProxy = "gvproxy.exe"
|
||||
winSShProxy = "win-sshproxy.exe"
|
||||
pipePrefix = "npipe:////./pipe/"
|
||||
globalPipe = "docker_engine"
|
||||
userModeDist = "podman-net-usermode"
|
||||
rootfulSock = "/run/podman/podman.sock"
|
||||
rootlessSock = "/run/user/1000/podman/podman.sock"
|
||||
)
|
||||
|
||||
type MachineVM struct {
|
||||
@ -1208,28 +1207,15 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error {
|
||||
}
|
||||
fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
|
||||
}
|
||||
|
||||
globalName, pipeName, err := launchWinProxy(v)
|
||||
if !opts.NoInfo {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n", err.Error())
|
||||
fmt.Fprintln(os.Stderr, "\nPodman clients are still able to connect.")
|
||||
} else {
|
||||
fmt.Printf("API forwarding listening on: %s\n", pipeName)
|
||||
if globalName {
|
||||
fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n")
|
||||
} else {
|
||||
fmt.Printf("\nAnother process was listening on the default Docker API pipe address.\n")
|
||||
fmt.Printf("You can still connect Docker API clients by setting DOCKER HOST using the\n")
|
||||
fmt.Printf("following powershell command in your terminal session:\n")
|
||||
fmt.Printf("\n\t$Env:DOCKER_HOST = '%s'\n", pipeName)
|
||||
fmt.Printf("\nOr in a classic CMD prompt:\n")
|
||||
fmt.Printf("\n\tset DOCKER_HOST=%s\n", pipeName)
|
||||
fmt.Printf("\nAlternatively, terminate the other process and restart podman machine.\n")
|
||||
}
|
||||
}
|
||||
winProxyOpts := machine.WinProxyOpts{
|
||||
Name: v.Name,
|
||||
IdentityPath: v.IdentityPath,
|
||||
Port: v.Port,
|
||||
RemoteUsername: v.RemoteUsername,
|
||||
Rootful: v.Rootful,
|
||||
VMType: vmtype,
|
||||
}
|
||||
machine.LaunchWinProxy(winProxyOpts, opts.NoInfo)
|
||||
|
||||
_, _, err = v.updateTimeStamps(true)
|
||||
return err
|
||||
@ -1303,85 +1289,6 @@ func (v *MachineVM) reassignSshPort() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func findExecutablePeer(name string) (string, error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
exe, err = filepath.EvalSymlinks(exe)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(filepath.Dir(exe), name), nil
|
||||
}
|
||||
|
||||
func launchWinProxy(v *MachineVM) (bool, string, error) {
|
||||
machinePipe := toDist(v.Name)
|
||||
if !machine.PipeNameAvailable(machinePipe) {
|
||||
return false, "", fmt.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe)
|
||||
}
|
||||
|
||||
globalName := false
|
||||
if machine.PipeNameAvailable(globalPipe) {
|
||||
globalName = true
|
||||
}
|
||||
|
||||
command, err := findExecutablePeer(winSShProxy)
|
||||
if err != nil {
|
||||
return globalName, "", err
|
||||
}
|
||||
|
||||
stateDir, err := getWinProxyStateDir(v)
|
||||
if err != nil {
|
||||
return globalName, "", err
|
||||
}
|
||||
|
||||
destSock := rootlessSock
|
||||
forwardUser := v.RemoteUsername
|
||||
|
||||
if v.Rootful {
|
||||
destSock = rootfulSock
|
||||
forwardUser = "root"
|
||||
}
|
||||
|
||||
dest := fmt.Sprintf("ssh://%s@localhost:%d%s", forwardUser, v.Port, destSock)
|
||||
args := []string{v.Name, stateDir, pipePrefix + machinePipe, dest, v.IdentityPath}
|
||||
waitPipe := machinePipe
|
||||
if globalName {
|
||||
args = append(args, pipePrefix+globalPipe, dest, v.IdentityPath)
|
||||
waitPipe = globalPipe
|
||||
}
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return globalName, "", err
|
||||
}
|
||||
|
||||
return globalName, pipePrefix + waitPipe, machine.WaitPipeExists(waitPipe, 80, func() error {
|
||||
active, exitCode := machine.GetProcessState(cmd.Process.Pid)
|
||||
if !active {
|
||||
return fmt.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func getWinProxyStateDir(v *MachineVM) (string, error) {
|
||||
dir, err := machine.GetDataDir(vmtype)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stateDir := filepath.Join(dir, v.Name)
|
||||
if err = os.MkdirAll(stateDir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return stateDir, nil
|
||||
}
|
||||
|
||||
func IsWSLFeatureEnabled() bool {
|
||||
return wutil.SilentExec("wsl", "--set-default-version", "2") == nil
|
||||
}
|
||||
@ -1487,7 +1394,7 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
||||
|
||||
_, _, _ = v.updateTimeStamps(true)
|
||||
|
||||
if err := stopWinProxy(v); err != nil {
|
||||
if err := machine.StopWinProxy(v.Name, vmtype); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
|
||||
}
|
||||
|
||||
@ -1527,59 +1434,6 @@ func (v *MachineVM) State(bypass bool) (define.Status, error) {
|
||||
return define.Stopped, nil
|
||||
}
|
||||
|
||||
func stopWinProxy(v *MachineVM) error {
|
||||
pid, tid, tidFile, err := readWinProxyTid(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(int(pid))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
sendQuit(tid)
|
||||
_ = waitTimeout(proc, 20*time.Second)
|
||||
_ = os.Remove(tidFile)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitTimeout(proc *os.Process, timeout time.Duration) bool {
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
proc.Wait()
|
||||
done <- true
|
||||
}()
|
||||
ret := false
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
proc.Kill()
|
||||
<-done
|
||||
case <-done:
|
||||
ret = true
|
||||
break
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func readWinProxyTid(v *MachineVM) (uint32, uint32, string, error) {
|
||||
stateDir, err := getWinProxyStateDir(v)
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
tidFile := filepath.Join(stateDir, winSshProxyTid)
|
||||
contents, err := os.ReadFile(tidFile)
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
var pid, tid uint32
|
||||
fmt.Sscanf(string(contents), "%d:%d", &pid, &tid)
|
||||
return pid, tid, tidFile, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
|
||||
var files []string
|
||||
|
@ -74,7 +74,7 @@ func (v *MachineVM) startUserModeNetworking() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
exe, err := findExecutablePeer(gvProxy)
|
||||
exe, err := machine.FindExecutablePeer(gvProxy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not locate %s, which is necessary for user-mode networking, please reinstall", gvProxy)
|
||||
}
|
||||
|
@ -67,7 +67,6 @@ const (
|
||||
TOKEN_QUERY = 0x0008
|
||||
SE_PRIVILEGE_ENABLED = 0x00000002
|
||||
SE_ERR_ACCESSDENIED = 0x05
|
||||
WM_QUIT = 0x12
|
||||
)
|
||||
|
||||
func winVersionAtLeast(major uint, minor uint, build uint) bool {
|
||||
@ -337,9 +336,3 @@ func buildCommandArgs(elevate bool) string {
|
||||
}
|
||||
return strings.Join(args, " ")
|
||||
}
|
||||
|
||||
func sendQuit(tid uint32) {
|
||||
user32 := syscall.NewLazyDLL("user32.dll")
|
||||
postMessage := user32.NewProc("PostThreadMessageW")
|
||||
postMessage.Call(uintptr(tid), WM_QUIT, 0, 0)
|
||||
}
|
||||
|
Reference in New Issue
Block a user