mirror of
https://github.com/juanfont/headscale.git
synced 2026-05-23 18:48:42 +09:00
Every Go-identifier reference in // and /* */ comments now uses
godoc's [Name] linking syntax so pkg.go.dev and `go doc` render
them as clickable cross-references. No behaviour change.
Pattern applied across the tree:
In-package [Foo], [Foo.Bar]
Cross-package [pkg.Foo], [pkg.Foo.Bar]
Stdlib [netip.Prefix], [errors.Is], [context.Context]
Tailscale [tailcfg.MapResponse], [tailcfg.Node.CapMap],
[tailcfg.NodeAttrSuggestExitNode]
Skip rules:
- File:line refs left as plain text
- HuJSON wire keys inside backtick raw strings untouched
- ACL/policy syntax tokens (tag:foo, autogroup:self, ...) not Go
symbols, left as plain text
- JSON/OIDC wire keys, gorm tags, RFC IPv6 placeholders, markdown
link tags, decorative dividers — all left as-is
129 lines
3.0 KiB
Go
129 lines
3.0 KiB
Go
package dockertestutil
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
|
"github.com/ory/dockertest/v3"
|
|
)
|
|
|
|
// defaultExecuteTimeout returns the timeout for docker exec commands.
|
|
// On CI runners, docker exec latency is higher due to resource
|
|
// contention, so the timeout is doubled.
|
|
func defaultExecuteTimeout() time.Duration {
|
|
if util.IsCI() {
|
|
return 20 * time.Second
|
|
}
|
|
|
|
return 10 * time.Second
|
|
}
|
|
|
|
var (
|
|
ErrDockertestCommandFailed = errors.New("dockertest command failed")
|
|
ErrDockertestCommandTimeout = errors.New("dockertest command timed out")
|
|
)
|
|
|
|
type ExecuteCommandConfig struct {
|
|
timeout time.Duration
|
|
}
|
|
|
|
type ExecuteCommandOption func(*ExecuteCommandConfig) error
|
|
|
|
func ExecuteCommandTimeout(timeout time.Duration) ExecuteCommandOption {
|
|
return ExecuteCommandOption(func(conf *ExecuteCommandConfig) error {
|
|
conf.timeout = timeout
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// buffer is a goroutine safe [bytes.Buffer].
|
|
type buffer struct {
|
|
store bytes.Buffer
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
// Write appends the contents of p to the buffer, growing the buffer as needed. It returns
|
|
// the number of bytes written.
|
|
func (b *buffer) Write(p []byte) (int, error) {
|
|
b.mutex.Lock()
|
|
defer b.mutex.Unlock()
|
|
|
|
return b.store.Write(p)
|
|
}
|
|
|
|
// String returns the contents of the unread portion of the buffer
|
|
// as a string.
|
|
func (b *buffer) String() string {
|
|
b.mutex.Lock()
|
|
defer b.mutex.Unlock()
|
|
|
|
return b.store.String()
|
|
}
|
|
|
|
func ExecuteCommand(
|
|
resource *dockertest.Resource,
|
|
cmd []string,
|
|
env []string,
|
|
options ...ExecuteCommandOption,
|
|
) (string, string, error) {
|
|
stdout := buffer{}
|
|
stderr := buffer{}
|
|
|
|
execConfig := ExecuteCommandConfig{
|
|
timeout: defaultExecuteTimeout(),
|
|
}
|
|
|
|
for _, opt := range options {
|
|
err := opt(&execConfig)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("execute-command/options: %w", err)
|
|
}
|
|
}
|
|
|
|
type result struct {
|
|
exitCode int
|
|
err error
|
|
}
|
|
|
|
resultChan := make(chan result, 1)
|
|
|
|
// Run your long running function in it's own goroutine and pass back it's
|
|
// response into our channel.
|
|
go func() {
|
|
exitCode, err := resource.Exec(
|
|
cmd,
|
|
dockertest.ExecOptions{
|
|
Env: append(env, "HEADSCALE_LOG_LEVEL=info"),
|
|
StdOut: &stdout,
|
|
StdErr: &stderr,
|
|
},
|
|
)
|
|
|
|
resultChan <- result{exitCode, err}
|
|
}()
|
|
|
|
// Listen on our channel AND a timeout channel - which ever happens first.
|
|
select {
|
|
case res := <-resultChan:
|
|
if res.err != nil {
|
|
return stdout.String(), stderr.String(), fmt.Errorf("command failed, stderr: %s: %w", stderr.String(), res.err)
|
|
}
|
|
|
|
if res.exitCode != 0 {
|
|
// Uncomment for debugging
|
|
// log.Println("Command: ", cmd)
|
|
// log.Println("stdout: ", stdout.String())
|
|
// log.Println("stderr: ", stderr.String())
|
|
return stdout.String(), stderr.String(), fmt.Errorf("command failed, stderr: %s: %w", stderr.String(), ErrDockertestCommandFailed)
|
|
}
|
|
|
|
return stdout.String(), stderr.String(), nil
|
|
case <-time.After(execConfig.timeout):
|
|
return stdout.String(), stderr.String(), fmt.Errorf("command failed, stderr: %s: %w", stderr.String(), ErrDockertestCommandTimeout)
|
|
}
|
|
}
|