-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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 #17694
Changes from all commits
0a4c136
5e0bec2
41c02a0
c592e5d
ace9e37
7fab79f
6b1fea5
4b2fc2d
13e5433
906aaa2
2a70c2f
fb1fd8e
45f6f2a
aec2a20
4ab3bdb
aad0dab
4d62ffc
55dba95
6b0a813
6be9fcf
27073a4
b4cf245
f6424ab
c6aca86
05b3fbd
1388115
7a0b163
cd3e7ae
fd4ceed
fd75fb7
f500139
74de240
796a88a
a39760a
e142630
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:feature | ||
Windows: support consul connect envoy command on Windows | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
//go:build linux || darwin || windows | ||
// +build linux darwin windows | ||
|
||
package envoy | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"strings" | ||
) | ||
|
||
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
//go:build windows | ||
// +build windows | ||
|
||
package envoy | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/natefinch/npipe" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"time" | ||
) | ||
|
||
func makeBootstrapPipe(bootstrapJSON []byte) (string, error) { | ||
pipeFile := filepath.Join(os.TempDir(), | ||
fmt.Sprintf("envoy-%x-bootstrap.json", time.Now().UnixNano()+int64(os.Getpid()))) | ||
|
||
binary, args, err := execArgs("connect", "envoy", "pipe-bootstrap", pipeFile) | ||
if err != nil { | ||
return pipeFile, err | ||
} | ||
|
||
// Dial the named pipe | ||
pipeConn, err := npipe.Dial(pipeFile) | ||
if err != nil { | ||
return pipeFile, err | ||
} | ||
defer pipeConn.Close() | ||
|
||
// Start the command to connect to the named pipe | ||
cmd := exec.Command(binary, args...) | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
cmd.Stdin = pipeConn | ||
|
||
// Start the command | ||
err = cmd.Start() | ||
if err != nil { | ||
return pipeFile, err | ||
} | ||
|
||
// Write the config | ||
n, err := pipeConn.Write(bootstrapJSON) | ||
if err != nil { | ||
return pipeFile, err | ||
} | ||
|
||
if n < len(bootstrapJSON) { | ||
return pipeFile, fmt.Errorf("failed writing boostrap to child STDIN: %s", err) | ||
} | ||
|
||
// 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 pipeFile, nil | ||
} | ||
|
||
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 := makeBootstrapPipe(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. | ||
Comment on lines
+82
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the reasoning here, why don't we defer the clean up to after the envoy process finish? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic is not specific to windows. Please see - unix version - https://github.com/hashicorp/consul/blob/b0a2e33e0a6c4ec411266dfcef38a3a648fefb50/command/connect/envoy/exec_unix.go#L127C1-L129C10 |
||
|
||
// 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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the reasoning behind that, why don't we wait for it to be done and is this specific to Windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not specific to Windows. https://github.com/hashicorp/consul/blob/b0a2e33e0a6c4ec411266dfcef38a3a648fefb50/command/connect/envoy/exec_unix.go#L127C1-L129C10