Implement API forwarding for podman machine on Windows

Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
Jason T. Greene
2022-01-18 14:39:48 -06:00
parent 094b11cbcb
commit 2d0b5ebb5b
5 changed files with 206 additions and 4 deletions

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows
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 MachineVM struct {
@ -705,8 +709,6 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return errors.Errorf("%q is already running", name)
}
fmt.Println("Starting machine...")
dist := toDist(name)
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")
}
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)
}
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 {
cmd := exec.Command("wsl", "--status")
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)
}
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.Stdin = strings.NewReader(waitTerm)
if err = cmd.Start(); err != nil {
@ -840,6 +944,59 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
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
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
var files []string