mirror of
https://github.com/containers/podman.git
synced 2025-06-26 04:46:57 +08:00
Implement API forwarding for podman machine on Windows
Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
22
Makefile
22
Makefile
@ -186,6 +186,13 @@ ifdef HOMEBREW_PREFIX
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# win-sshproxy is checked out manually to keep from pulling in gvisor and it's transitive
|
||||||
|
# dependencies. This is only used for the Windows installer task (podman.msi), which must
|
||||||
|
# include this lightweight helper binary.
|
||||||
|
#
|
||||||
|
GV_GITURL=git://github.com/containers/gvisor-tap-vsock.git
|
||||||
|
GV_SHA=e943b1806d94d387c4c38d96719432d50a84bbd0
|
||||||
|
|
||||||
###
|
###
|
||||||
### Primary entry-point targets
|
### Primary entry-point targets
|
||||||
###
|
###
|
||||||
@ -695,7 +702,7 @@ podman-remote-release-%.zip: test/version/version ## Build podman-remote for %=$
|
|||||||
.PHONY: podman.msi
|
.PHONY: podman.msi
|
||||||
podman.msi: test/version/version ## Build podman-remote, package for installation on Windows
|
podman.msi: test/version/version ## Build podman-remote, package for installation on Windows
|
||||||
$(MAKE) podman-v$(RELEASE_NUMBER).msi
|
$(MAKE) podman-v$(RELEASE_NUMBER).msi
|
||||||
podman-v$(RELEASE_NUMBER).msi: podman-remote-windows podman-remote-windows-docs podman-winpath
|
podman-v$(RELEASE_NUMBER).msi: podman-remote-windows podman-remote-windows-docs podman-winpath win-sshproxy
|
||||||
$(eval DOCFILE := docs/build/remote/windows)
|
$(eval DOCFILE := docs/build/remote/windows)
|
||||||
find $(DOCFILE) -print | \
|
find $(DOCFILE) -print | \
|
||||||
wixl-heat --var var.ManSourceDir --component-group ManFiles \
|
wixl-heat --var var.ManSourceDir --component-group ManFiles \
|
||||||
@ -704,6 +711,19 @@ podman-v$(RELEASE_NUMBER).msi: podman-remote-windows podman-remote-windows-docs
|
|||||||
wixl -D VERSION=$(call err_if_empty,RELEASE_VERSION) -D ManSourceDir=$(DOCFILE) \
|
wixl -D VERSION=$(call err_if_empty,RELEASE_VERSION) -D ManSourceDir=$(DOCFILE) \
|
||||||
-o $@ contrib/msi/podman.wxs $(DOCFILE)/pages.wsx --arch x64
|
-o $@ contrib/msi/podman.wxs $(DOCFILE)/pages.wsx --arch x64
|
||||||
|
|
||||||
|
# Checks out and builds win-sshproxy helper. See comment on GV_GITURL declaration
|
||||||
|
.PHONY: win-sshproxy
|
||||||
|
win-sshproxy: test/version/version
|
||||||
|
rm -rf tmp-gv; mkdir tmp-gv
|
||||||
|
(cd tmp-gv; \
|
||||||
|
git init; \
|
||||||
|
git remote add origin $(GV_GITURL); \
|
||||||
|
git fetch --depth 1 origin $(GV_SHA); \
|
||||||
|
git checkout FETCH_HEAD; make win-sshproxy)
|
||||||
|
mkdir -p bin/windows/
|
||||||
|
cp tmp-gv/bin/win-sshproxy.exe bin/windows/
|
||||||
|
rm -rf tmp-gv
|
||||||
|
|
||||||
.PHONY: package
|
.PHONY: package
|
||||||
package: ## Build rpm packages
|
package: ## Build rpm packages
|
||||||
## TODO(ssbarnea): make version number predictable, it should not change
|
## TODO(ssbarnea): make version number predictable, it should not change
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build amd64 || arm64
|
||||||
// +build amd64 arm64
|
// +build amd64 arm64
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
@ -64,5 +65,6 @@ func start(cmd *cobra.Command, args []string) error {
|
|||||||
if err := vm.Start(vmName, machine.StartOptions{}); err != nil {
|
if err := vm.Start(vmName, machine.StartOptions{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Machine %q started successfully\n", vmName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@
|
|||||||
<Component Id="WinPathExecutable" Guid="00F5B731-D4A6-4B69-87B0-EA4EBAB89F95" Win64="Yes">
|
<Component Id="WinPathExecutable" Guid="00F5B731-D4A6-4B69-87B0-EA4EBAB89F95" Win64="Yes">
|
||||||
<File Id="8F507E28-A61D-4E64-A92B-B5A00F023AE8" Name="winpath.exe" Source="bin/windows/winpath.exe" KeyPath="yes"/>
|
<File Id="8F507E28-A61D-4E64-A92B-B5A00F023AE8" Name="winpath.exe" Source="bin/windows/winpath.exe" KeyPath="yes"/>
|
||||||
</Component>
|
</Component>
|
||||||
|
<Component Id="WinSshProxyExecutable" Guid="0DA730AB-2F97-40E8-A8FC-356E88EAA4D2" Win64="Yes">
|
||||||
|
<File Id="4A2AD125-34E7-4BD8-BE28-B2A9A5EDBEB5" Name="win-sshproxy.exe" Source="bin/windows/win-sshproxy.exe" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
</Directory>
|
</Directory>
|
||||||
</Directory>
|
</Directory>
|
||||||
</Directory>
|
</Directory>
|
||||||
@ -41,6 +44,7 @@
|
|||||||
<ComponentRef Id="INSTALLDIR_Component"/>
|
<ComponentRef Id="INSTALLDIR_Component"/>
|
||||||
<ComponentRef Id="MainExecutable"/>
|
<ComponentRef Id="MainExecutable"/>
|
||||||
<ComponentRef Id="WinPathExecutable"/>
|
<ComponentRef Id="WinPathExecutable"/>
|
||||||
|
<ComponentRef Id="WinSshProxyExecutable"/>
|
||||||
<ComponentGroupRef Id="ManFiles"/>
|
<ComponentGroupRef Id="ManFiles"/>
|
||||||
</Feature>
|
</Feature>
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package wsl
|
package wsl
|
||||||
@ -143,6 +142,11 @@ http://docs.microsoft.com/en-us/windows/wsl/install\
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const (
|
||||||
|
winSShProxy = "win-sshproxy.exe"
|
||||||
|
winSshProxyTid = "win-sshproxy.tid"
|
||||||
|
)
|
||||||
|
|
||||||
type Provider struct{}
|
type Provider struct{}
|
||||||
|
|
||||||
type MachineVM struct {
|
type MachineVM struct {
|
||||||
@ -705,8 +709,6 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
|
|||||||
return errors.Errorf("%q is already running", name)
|
return errors.Errorf("%q is already running", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Starting machine...")
|
|
||||||
|
|
||||||
dist := toDist(name)
|
dist := toDist(name)
|
||||||
|
|
||||||
err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap")
|
err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap")
|
||||||
@ -714,9 +716,107 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
|
|||||||
return errors.Wrap(err, "WSL bootstrap script failed")
|
return errors.Wrap(err, "WSL bootstrap script failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalName, pipeName, err := launchWinProxy(v)
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return markStart(name)
|
return markStart(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func launchWinProxy(v *MachineVM) (bool, string, error) {
|
||||||
|
globalName := true
|
||||||
|
pipeName := "docker_engine"
|
||||||
|
if !pipeAvailable(pipeName) {
|
||||||
|
pipeName = toDist(v.Name)
|
||||||
|
globalName = false
|
||||||
|
if !pipeAvailable(pipeName) {
|
||||||
|
return globalName, "", errors.Errorf("could not start api proxy since expected pipe is not available: %s", pipeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fullPipeName := "npipe:////./pipe/" + pipeName
|
||||||
|
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return globalName, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
exe, err = filepath.EvalSymlinks(exe)
|
||||||
|
if err != nil {
|
||||||
|
return globalName, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
command := filepath.Join(filepath.Dir(exe), winSShProxy)
|
||||||
|
stateDir, err := getWinProxyStateDir(v)
|
||||||
|
if err != nil {
|
||||||
|
return globalName, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := fmt.Sprintf("ssh://root@localhost:%d/run/podman/podman.sock", v.Port)
|
||||||
|
cmd := exec.Command(command, v.Name, stateDir, fullPipeName, dest, v.IdentityPath)
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return globalName, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return globalName, fullPipeName, waitPipeExists(pipeName, 30, func() error {
|
||||||
|
active, exitCode := getProcessState(cmd.Process.Pid)
|
||||||
|
if !active {
|
||||||
|
return errors.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 pipeAvailable(pipeName string) bool {
|
||||||
|
_, err := os.Stat(`\\.\pipe\` + pipeName)
|
||||||
|
return os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitPipeExists(pipeName string, retries int, checkFailure func() error) error {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
_, err = os.Stat(`\\.\pipe\` + pipeName)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if fail := checkFailure(); fail != nil {
|
||||||
|
return fail
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func isWSLInstalled() bool {
|
func isWSLInstalled() bool {
|
||||||
cmd := exec.Command("wsl", "--status")
|
cmd := exec.Command("wsl", "--status")
|
||||||
out, err := cmd.StdoutPipe()
|
out, err := cmd.StdoutPipe()
|
||||||
@ -817,6 +917,10 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
|||||||
return errors.Errorf("%q is not running", v.Name)
|
return errors.Errorf("%q is not running", v.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := stopWinProxy(v); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command("wsl", "-d", dist, "sh")
|
cmd := exec.Command("wsl", "-d", dist, "sh")
|
||||||
cmd.Stdin = strings.NewReader(waitTerm)
|
cmd.Stdin = strings.NewReader(waitTerm)
|
||||||
if err = cmd.Start(); err != nil {
|
if err = cmd.Start(); err != nil {
|
||||||
@ -840,6 +944,59 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
|||||||
return nil
|
return 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 := ioutil.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
|
//nolint:cyclop
|
||||||
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
|
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
|
||||||
var files []string
|
var files []string
|
||||||
|
@ -67,6 +67,7 @@ const (
|
|||||||
TOKEN_QUERY = 0x0008
|
TOKEN_QUERY = 0x0008
|
||||||
SE_PRIVILEGE_ENABLED = 0x00000002
|
SE_PRIVILEGE_ENABLED = 0x00000002
|
||||||
SE_ERR_ACCESSDENIED = 0x05
|
SE_ERR_ACCESSDENIED = 0x05
|
||||||
|
WM_QUIT = 0x12
|
||||||
)
|
)
|
||||||
|
|
||||||
func winVersionAtLeast(major uint, minor uint, build uint) bool {
|
func winVersionAtLeast(major uint, minor uint, build uint) bool {
|
||||||
@ -279,6 +280,18 @@ func obtainShutdownPrivilege() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
if err != nil {
|
||||||
|
return false, int(syscall.ERROR_PROC_NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
var code uint32
|
||||||
|
syscall.GetExitCodeProcess(handle, &code)
|
||||||
|
return code == 259, int(code)
|
||||||
|
}
|
||||||
|
|
||||||
func addRunOnceRegistryEntry(command string) error {
|
func addRunOnceRegistryEntry(command string) error {
|
||||||
k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE)
|
k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -336,3 +349,9 @@ func buildCommandArgs(elevate bool) string {
|
|||||||
}
|
}
|
||||||
return strings.Join(args, " ")
|
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