Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support consul connect envoy command on Windows #15114

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion command/connect/envoy/envoy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func New(ui cli.Ui) *cmd {
return c
}

const DefaultAdminAccessLogPath = "/dev/null"
const DefaultAdminAccessLogPath = os.DevNull

type cmd struct {
UI cli.Ui
Expand Down
60 changes: 60 additions & 0 deletions command/connect/envoy/exec_supported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//go:build linux || darwin || windows
// +build linux darwin windows

package envoy

import (
"fmt"
"os"
"strings"
)

// testSelfExecOverride is a way for the tests to no fork-bomb themselves by
// self-executing the whole test suite for each case recursively. It's gross but
// the least gross option I could think of.
var testSelfExecOverride string

func isHotRestartOption(s string) bool {
restartOpts := []string{
"--restart-epoch",
"--hot-restart-version",
"--drain-time-s",
"--parent-shutdown-time-s",
}
for _, opt := range restartOpts {
if s == opt {
return true
}
if strings.HasPrefix(s, opt+"=") {
return true
}
}
return false
}

func hasHotRestartOption(argSets ...[]string) bool {
for _, args := range argSets {
for _, opt := range args {
if isHotRestartOption(opt) {
return true
}
}
}
return false
}

// execArgs returns the command and args used to execute a binary. By default it
// will return a command of os.Executable with the args unmodified. This is a shim
// for testing, and can be overridden to execute using 'go run' instead.
var execArgs = func(args ...string) (string, []string, error) {
execPath, err := os.Executable()
if err != nil {
return "", nil, err
}

if strings.HasSuffix(execPath, "/envoy.test") {
return "", nil, fmt.Errorf("set execArgs to use 'go run' instead of doing a self-exec")
}

return execPath, args, nil
}
50 changes: 0 additions & 50 deletions command/connect/envoy/exec_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,6 @@ import (
"golang.org/x/sys/unix"
)

// testSelfExecOverride is a way for the tests to no fork-bomb themselves by
// self-executing the whole test suite for each case recursively. It's gross but
// the least gross option I could think of.
var testSelfExecOverride string

func isHotRestartOption(s string) bool {
restartOpts := []string{
"--restart-epoch",
"--hot-restart-version",
"--drain-time-s",
"--parent-shutdown-time-s",
}
for _, opt := range restartOpts {
if s == opt {
return true
}
if strings.HasPrefix(s, opt+"=") {
return true
}
}
return false
}

func hasHotRestartOption(argSets ...[]string) bool {
for _, args := range argSets {
for _, opt := range args {
if isHotRestartOption(opt) {
return true
}
}
}
return false
}

// execArgs returns the command and args used to execute a binary. By default it
// will return a command of os.Executable with the args unmodified. This is a shim
// for testing, and can be overridden to execute using 'go run' instead.
var execArgs = func(args ...string) (string, []string, error) {
execPath, err := os.Executable()
if err != nil {
return "", nil, err
}

if strings.HasSuffix(execPath, "/envoy.test") {
return "", nil, fmt.Errorf("set execArgs to use 'go run' instead of doing a self-exec")
}

return execPath, args, nil
}

func makeBootstrapPipe(bootstrapJSON []byte) (string, error) {
pipeFile := filepath.Join(os.TempDir(),
fmt.Sprintf("envoy-%x-bootstrap.json", time.Now().UnixNano()+int64(os.Getpid())))
Expand Down
4 changes: 2 additions & 2 deletions command/connect/envoy/exec_unsupported.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:build !linux && !darwin
// +build !linux,!darwin
//go:build !linux && !darwin && !windows
// +build !linux,!darwin,!windows

package envoy

Expand Down
86 changes: 86 additions & 0 deletions command/connect/envoy/exec_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//go:build windows
joselo85 marked this conversation as resolved.
Show resolved Hide resolved
// +build windows

package envoy

import (
"errors"
"fmt"
"os"
"os/exec"

"path/filepath"

"time"
)

func makeBootstrapTemp(bootstrapJSON []byte) (string, error) {
tempFile := filepath.Join(os.TempDir(),
fmt.Sprintf("envoy-%x-bootstrap.json", time.Now().UnixNano()+int64(os.Getpid())))

f, err := os.Create(tempFile)
if err != nil {
return tempFile, err
}

defer f.Close()
f.Write(bootstrapJSON)
f.Sync()

// We can't wait for the process since we need to exec into Envoy before it
// will be able to complete so it will be remain as a zombie until Envoy is
// killed then will be reaped by the init process (pid 0). This is all a bit
// gross but the cleanest workaround I can think of for Envoy 1.10 not
// supporting /dev/fd/<fd> config paths any more. So we are done and leaving
// the child to run it's course without reaping it.
return tempFile, nil
}
Comment on lines +17 to +37
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a security concerns. The temp file in here will potentially have the agent token written into it. Is there a way in Windows to emulate an in-memory file? maybe something like https://learn.microsoft.com/en-us/dotnet/api/system.io.memorymappedfiles.memorymappedfile.createnew?view=net-7.0 for golang?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in #17694


func startProc(binary string, args []string) (p *os.Process, err error) {
if binary, err = exec.LookPath(binary); err == nil {
var procAttr os.ProcAttr
procAttr.Files = []*os.File{os.Stdin,
os.Stdout, os.Stderr}
p, err := os.StartProcess(binary, args, &procAttr)
if err == nil {
return p, nil
}
}
return nil, err
}

func execEnvoy(binary string, prefixArgs, suffixArgs []string, bootstrapJSON []byte) error {
tempFile, err := makeBootstrapTemp(bootstrapJSON)
if err != nil {
os.RemoveAll(tempFile)
return err
}
// We don't defer a cleanup since we are about to Exec into Envoy which means
// defer will never fire. The child process cleans up for us in the happy
// path.

// We default to disabling hot restart because it makes it easier to run
// multiple envoys locally for testing without them trying to share memory and
// unix sockets and complain about being different IDs. But if user is
// actually configuring hot-restart explicitly with the --restart-epoch option
// then don't disable it!
disableHotRestart := !hasHotRestartOption(prefixArgs, suffixArgs)

// First argument needs to be the executable name.
envoyArgs := []string{}
envoyArgs = append(envoyArgs, prefixArgs...)
if disableHotRestart {
envoyArgs = append(envoyArgs, "--disable-hot-restart")
}
envoyArgs = append(envoyArgs, suffixArgs...)
envoyArgs = append(envoyArgs, "--config-path", tempFile)

// Exec
if proc, err := startProc(binary, envoyArgs); err == nil {
proc.Wait()
} else if err != nil {
return errors.New("Failed to exec envoy: " + err.Error())
}

return nil
}