mirror of
https://github.com/juanfont/headscale.git
synced 2026-05-23 18:48:42 +09:00
cmd, templates, integration: extract shared production constants
Constants the operator/test reader benefits from centralising.
Tests stay verbatim (the .golangci.yaml goconst tune skips them);
extraction here applies only where the same literal acts as
shared vocabulary across files.
cmd/headscale/cli/strings.go (new)
Cobra subcommand verbs and aliases shared by every list / show
/ new / delete / expire command across api_key, nodes, policy,
preauthkeys, users — plus the Result / Created / Expiration
column headers used in printOutput maps.
hscontrol/templates/design.go
cssBorderHS, cssBreakWord, cssCenter, cssOverflowWrap — shared
styles applied across design.go, ping.go, register_confirm.go.
spaceS already existed; switch raw "0.5rem" literals to it.
integration/hsic/hsic.go
binHeadscale, flagOutput, acceptJSON — names invoked across
hsic.go and config.go.
integration/tsic/tsic.go
tailscaleBin — used across docker exec call sites.
This commit is contained in:
@@ -32,14 +32,14 @@ func DefaultConfigEnv() map[string]string {
|
||||
|
||||
// Embedded DERP is the default for test isolation.
|
||||
// Tests should not depend on external DERP infrastructure.
|
||||
// Use WithPublicDERP() to opt out for tests that explicitly
|
||||
// Use [WithPublicDERP] to opt out for tests that explicitly
|
||||
// need public DERP relays.
|
||||
"HEADSCALE_DERP_URLS": "",
|
||||
"HEADSCALE_DERP_AUTO_UPDATE_ENABLED": "false",
|
||||
"HEADSCALE_DERP_UPDATE_FREQUENCY": "1m",
|
||||
"HEADSCALE_DERP_SERVER_ENABLED": "true",
|
||||
"HEADSCALE_DERP_SERVER_REGION_ID": "999",
|
||||
"HEADSCALE_DERP_SERVER_REGION_CODE": "headscale",
|
||||
"HEADSCALE_DERP_SERVER_REGION_CODE": binHeadscale,
|
||||
"HEADSCALE_DERP_SERVER_REGION_NAME": "Headscale Embedded DERP",
|
||||
"HEADSCALE_DERP_SERVER_STUN_LISTEN_ADDR": "0.0.0.0:3478",
|
||||
"HEADSCALE_DERP_SERVER_PRIVATE_KEY_PATH": "/tmp/derp.key",
|
||||
|
||||
@@ -48,6 +48,9 @@ const (
|
||||
headscaleDefaultPort = 8080
|
||||
IntegrationTestDockerFileName = "Dockerfile.integration"
|
||||
defaultDirPerm = 0o755
|
||||
binHeadscale = "headscale"
|
||||
flagOutput = "--output"
|
||||
acceptJSON = "Accept: application/json"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -94,8 +97,8 @@ type HeadscaleInContainer struct {
|
||||
// Headscale instance.
|
||||
type Option = func(c *HeadscaleInContainer)
|
||||
|
||||
// WithACLPolicy adds a hscontrol.ACLPolicy policy to the
|
||||
// HeadscaleInContainer instance.
|
||||
// WithACLPolicy adds a [policyv2.Policy] to the
|
||||
// [HeadscaleInContainer] instance.
|
||||
func WithACLPolicy(acl *policyv2.Policy) Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
if acl == nil {
|
||||
@@ -127,7 +130,7 @@ func WithoutTLS() Option {
|
||||
|
||||
// WithCustomTLS uses the given certificates for the Headscale instance.
|
||||
// The caCert is installed into the container's trust store and returned
|
||||
// by GetCert() so that clients can trust this server.
|
||||
// by [HeadscaleInContainer.GetCert] so that clients can trust this server.
|
||||
func WithCustomTLS(caCert, cert, key []byte) Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
hsic.tlsCACert = caCert
|
||||
@@ -320,7 +323,7 @@ func (hsic *HeadscaleInContainer) buildEntrypoint() []string {
|
||||
return []string{"/bin/bash", "-c", strings.Join(commands, " ; ")}
|
||||
}
|
||||
|
||||
// New returns a new HeadscaleInContainer instance.
|
||||
// New returns a new [HeadscaleInContainer] instance.
|
||||
//
|
||||
//nolint:gocyclo // complex container setup with many options
|
||||
func New(
|
||||
@@ -364,8 +367,8 @@ func New(
|
||||
|
||||
// TLS is enabled by default for all integration tests.
|
||||
// Generate a self-signed certificate if TLS was not explicitly
|
||||
// disabled via WithoutTLS() and no custom cert was provided
|
||||
// via WithCustomTLS().
|
||||
// disabled via [WithoutTLS] and no custom cert was provided
|
||||
// via [WithCustomTLS].
|
||||
if !hsic.noTLS && len(hsic.tlsCert) == 0 {
|
||||
caCert, cert, key, err := integrationutil.CreateCertificate(hsic.hostname)
|
||||
if err != nil {
|
||||
@@ -394,9 +397,9 @@ func New(
|
||||
if hsic.postgres {
|
||||
hsic.env["HEADSCALE_DATABASE_TYPE"] = "postgres"
|
||||
hsic.env["HEADSCALE_DATABASE_POSTGRES_HOST"] = "postgres-" + hash
|
||||
hsic.env["HEADSCALE_DATABASE_POSTGRES_USER"] = "headscale"
|
||||
hsic.env["HEADSCALE_DATABASE_POSTGRES_PASS"] = "headscale"
|
||||
hsic.env["HEADSCALE_DATABASE_POSTGRES_NAME"] = "headscale"
|
||||
hsic.env["HEADSCALE_DATABASE_POSTGRES_USER"] = binHeadscale
|
||||
hsic.env["HEADSCALE_DATABASE_POSTGRES_PASS"] = binHeadscale
|
||||
hsic.env["HEADSCALE_DATABASE_POSTGRES_NAME"] = binHeadscale
|
||||
delete(hsic.env, "HEADSCALE_DATABASE_SQLITE_PATH")
|
||||
|
||||
// Determine postgres image - use prebuilt if available, otherwise pull from registry
|
||||
@@ -501,7 +504,7 @@ func New(
|
||||
}
|
||||
|
||||
// Add integration test labels if running under hi tool
|
||||
dockertestutil.DockerAddIntegrationLabels(runOptions, "headscale")
|
||||
dockertestutil.DockerAddIntegrationLabels(runOptions, binHeadscale)
|
||||
|
||||
var container *dockertest.Resource
|
||||
|
||||
@@ -509,7 +512,7 @@ func New(
|
||||
prebuiltImage := os.Getenv("HEADSCALE_INTEGRATION_HEADSCALE_IMAGE")
|
||||
|
||||
if prebuiltImage != "" {
|
||||
log.Printf("Using pre-built headscale image: %s", prebuiltImage)
|
||||
log.Printf("Using pre-built headscale image: %s", prebuiltImage) //nolint:gosec // G706: integration-only log of trusted env value
|
||||
// Parse image into repository and tag
|
||||
repo, tag, ok := strings.Cut(prebuiltImage, ":")
|
||||
if !ok {
|
||||
@@ -709,7 +712,7 @@ func (t *HeadscaleInContainer) Shutdown() (string, string, error) {
|
||||
}
|
||||
|
||||
// WriteLogs writes the current stdout/stderr log of the container to
|
||||
// the given io.Writers.
|
||||
// the given [io.Writer]s.
|
||||
func (t *HeadscaleInContainer) WriteLogs(stdout, stderr io.Writer) error {
|
||||
return dockertestutil.WriteLog(t.pool, t.container, stdout, stderr)
|
||||
}
|
||||
@@ -1010,13 +1013,13 @@ func (t *HeadscaleInContainer) GetHostMetricsPort() string {
|
||||
return t.hostMetricsPort
|
||||
}
|
||||
|
||||
// GetHealthEndpoint returns a health endpoint for the HeadscaleInContainer
|
||||
// GetHealthEndpoint returns a health endpoint for the [HeadscaleInContainer]
|
||||
// instance.
|
||||
func (t *HeadscaleInContainer) GetHealthEndpoint() string {
|
||||
return t.GetEndpoint() + "/health"
|
||||
}
|
||||
|
||||
// GetEndpoint returns the Headscale endpoint for the HeadscaleInContainer.
|
||||
// GetEndpoint returns the Headscale endpoint for the [HeadscaleInContainer].
|
||||
func (t *HeadscaleInContainer) GetEndpoint() string {
|
||||
return t.getEndpoint(false)
|
||||
}
|
||||
@@ -1051,12 +1054,12 @@ func (t *HeadscaleInContainer) GetCert() []byte {
|
||||
return t.tlsCACert
|
||||
}
|
||||
|
||||
// GetHostname returns the hostname of the HeadscaleInContainer.
|
||||
// GetHostname returns the hostname of the [HeadscaleInContainer].
|
||||
func (t *HeadscaleInContainer) GetHostname() string {
|
||||
return t.hostname
|
||||
}
|
||||
|
||||
// GetIPInNetwork returns the IP address of the HeadscaleInContainer in the given network.
|
||||
// GetIPInNetwork returns the IP address of the [HeadscaleInContainer] in the given network.
|
||||
func (t *HeadscaleInContainer) GetIPInNetwork(network *dockertest.Network) string {
|
||||
return t.container.GetIPInNetwork(network)
|
||||
}
|
||||
@@ -1095,12 +1098,12 @@ func (t *HeadscaleInContainer) CreateUser(
|
||||
user string,
|
||||
) (*v1.User, error) {
|
||||
command := []string{
|
||||
"headscale",
|
||||
binHeadscale,
|
||||
"users",
|
||||
"create",
|
||||
user,
|
||||
fmt.Sprintf("--email=%s@test.no", user),
|
||||
"--output",
|
||||
flagOutput,
|
||||
"json",
|
||||
}
|
||||
|
||||
@@ -1140,7 +1143,7 @@ type AuthKeyOptions struct {
|
||||
// This supports both user-owned and tags-only auth keys.
|
||||
func (t *HeadscaleInContainer) CreateAuthKeyWithOptions(opts AuthKeyOptions) (*v1.PreAuthKey, error) {
|
||||
command := []string{
|
||||
"headscale",
|
||||
binHeadscale,
|
||||
}
|
||||
|
||||
// Only add --user flag if User is specified
|
||||
@@ -1153,7 +1156,7 @@ func (t *HeadscaleInContainer) CreateAuthKeyWithOptions(opts AuthKeyOptions) (*v
|
||||
"create",
|
||||
"--expiration",
|
||||
"24h",
|
||||
"--output",
|
||||
flagOutput,
|
||||
"json",
|
||||
)
|
||||
|
||||
@@ -1189,7 +1192,7 @@ func (t *HeadscaleInContainer) CreateAuthKeyWithOptions(opts AuthKeyOptions) (*v
|
||||
}
|
||||
|
||||
// CreateAuthKey creates a new "authorisation key" for a User that can be used
|
||||
// to authorise a TailscaleClient with the Headscale instance.
|
||||
// to authorise a TailscaleClient with the [HeadscaleInContainer] instance.
|
||||
func (t *HeadscaleInContainer) CreateAuthKey(
|
||||
user uint64,
|
||||
reusable bool,
|
||||
@@ -1223,12 +1226,12 @@ func (t *HeadscaleInContainer) DeleteAuthKey(
|
||||
id uint64,
|
||||
) error {
|
||||
command := []string{
|
||||
"headscale",
|
||||
binHeadscale,
|
||||
"preauthkeys",
|
||||
"delete",
|
||||
"--id",
|
||||
strconv.FormatUint(id, 10),
|
||||
"--output",
|
||||
flagOutput,
|
||||
"json",
|
||||
}
|
||||
|
||||
@@ -1275,13 +1278,13 @@ func (t *HeadscaleInContainer) ListNodes(
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
err := execUnmarshal([]string{"headscale", "nodes", "list", "--output", "json"})
|
||||
err := execUnmarshal([]string{binHeadscale, "nodes", "list", flagOutput, "json"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
for _, user := range users {
|
||||
command := []string{"headscale", "--user", user, "nodes", "list", "--output", "json"}
|
||||
command := []string{binHeadscale, "--user", user, "nodes", "list", flagOutput, "json"}
|
||||
|
||||
err := execUnmarshal(command)
|
||||
if err != nil {
|
||||
@@ -1299,12 +1302,12 @@ func (t *HeadscaleInContainer) ListNodes(
|
||||
|
||||
func (t *HeadscaleInContainer) DeleteNode(nodeID uint64) error {
|
||||
command := []string{
|
||||
"headscale",
|
||||
binHeadscale,
|
||||
"nodes",
|
||||
"delete",
|
||||
"--identifier",
|
||||
strconv.FormatUint(nodeID, 10),
|
||||
"--output",
|
||||
flagOutput,
|
||||
"json",
|
||||
"--force",
|
||||
}
|
||||
@@ -1355,7 +1358,7 @@ func (t *HeadscaleInContainer) NodesByName() (map[string]*v1.Node, error) {
|
||||
|
||||
// ListUsers returns a list of users from Headscale.
|
||||
func (t *HeadscaleInContainer) ListUsers() ([]*v1.User, error) {
|
||||
command := []string{"headscale", "users", "list", "--output", "json"}
|
||||
command := []string{binHeadscale, "users", "list", flagOutput, "json"}
|
||||
|
||||
result, _, err := dockertestutil.ExecuteCommand(
|
||||
t.container,
|
||||
@@ -1395,13 +1398,13 @@ func (t *HeadscaleInContainer) MapUsers() (map[string]*v1.User, error) {
|
||||
// DeleteUser deletes a user from the Headscale instance.
|
||||
func (t *HeadscaleInContainer) DeleteUser(userID uint64) error {
|
||||
command := []string{
|
||||
"headscale",
|
||||
binHeadscale,
|
||||
"users",
|
||||
"delete",
|
||||
"--identifier",
|
||||
strconv.FormatUint(userID, 10),
|
||||
"--force",
|
||||
"--output",
|
||||
flagOutput,
|
||||
"json",
|
||||
}
|
||||
|
||||
@@ -1444,7 +1447,7 @@ func (h *HeadscaleInContainer) SetPolicy(pol *policyv2.Policy) error {
|
||||
func (h *HeadscaleInContainer) reloadDatabasePolicy() error {
|
||||
_, err := h.Execute(
|
||||
[]string{
|
||||
"headscale",
|
||||
binHeadscale,
|
||||
"policy",
|
||||
"set",
|
||||
"-f",
|
||||
@@ -1476,7 +1479,7 @@ func (h *HeadscaleInContainer) PID() (int, error) {
|
||||
// Use pidof to find the headscale process, which is more reliable than grep
|
||||
// as it only looks for the actual binary name, not processes that contain
|
||||
// "headscale" in their command line (like the dlv debugger).
|
||||
output, err := h.Execute([]string{"pidof", "headscale"})
|
||||
output, err := h.Execute([]string{"pidof", binHeadscale})
|
||||
if err != nil {
|
||||
// pidof returns exit code 1 when no process is found
|
||||
return 0, os.ErrNotExist
|
||||
@@ -1533,8 +1536,8 @@ func (h *HeadscaleInContainer) Reload() error {
|
||||
// ApproveRoutes approves routes for a node.
|
||||
func (t *HeadscaleInContainer) ApproveRoutes(id uint64, routes []netip.Prefix) (*v1.Node, error) {
|
||||
command := []string{
|
||||
"headscale", "nodes", "approve-routes",
|
||||
"--output", "json",
|
||||
binHeadscale, "nodes", "approve-routes",
|
||||
flagOutput, "json",
|
||||
"--identifier", strconv.FormatUint(id, 10),
|
||||
"--routes=" + strings.Join(util.PrefixesToString(routes), ","),
|
||||
}
|
||||
@@ -1568,9 +1571,9 @@ func (t *HeadscaleInContainer) ApproveRoutes(id uint64, routes []netip.Prefix) (
|
||||
// SetTags API which is exposed via the CLI command: headscale nodes tag -i <id> -t <tags>.
|
||||
func (t *HeadscaleInContainer) SetNodeTags(nodeID uint64, tags []string) error {
|
||||
command := []string{
|
||||
"headscale", "nodes", "tag",
|
||||
binHeadscale, "nodes", "tag",
|
||||
"--identifier", strconv.FormatUint(nodeID, 10),
|
||||
"--output", "json",
|
||||
flagOutput, "json",
|
||||
}
|
||||
|
||||
// Add tags - the CLI expects -t flag for each tag or comma-separated
|
||||
@@ -1605,7 +1608,7 @@ func (t *HeadscaleInContainer) FetchPath(path string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (t *HeadscaleInContainer) SendInterrupt() error {
|
||||
pid, err := t.Execute([]string{"pidof", "headscale"})
|
||||
pid, err := t.Execute([]string{"pidof", binHeadscale})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1621,7 +1624,7 @@ func (t *HeadscaleInContainer) SendInterrupt() error {
|
||||
func (t *HeadscaleInContainer) GetAllMapReponses() (map[types.NodeID][]tailcfg.MapResponse, error) {
|
||||
// Execute curl inside the container to access the debug endpoint locally
|
||||
command := []string{
|
||||
"curl", "-s", "-H", "Accept: application/json", "http://localhost:9090/debug/mapresponses",
|
||||
"curl", "-s", "-H", acceptJSON, "http://localhost:9090/debug/mapresponses",
|
||||
}
|
||||
|
||||
result, err := t.Execute(command)
|
||||
@@ -1641,7 +1644,7 @@ func (t *HeadscaleInContainer) GetAllMapReponses() (map[types.NodeID][]tailcfg.M
|
||||
func (t *HeadscaleInContainer) PrimaryRoutes() (*types.DebugRoutes, error) {
|
||||
// Execute curl inside the container to access the debug endpoint locally
|
||||
command := []string{
|
||||
"curl", "-s", "-H", "Accept: application/json", "http://localhost:9090/debug/routes",
|
||||
"curl", "-s", "-H", acceptJSON, "http://localhost:9090/debug/routes",
|
||||
}
|
||||
|
||||
result, err := t.Execute(command)
|
||||
@@ -1661,7 +1664,7 @@ func (t *HeadscaleInContainer) PrimaryRoutes() (*types.DebugRoutes, error) {
|
||||
func (t *HeadscaleInContainer) DebugBatcher() (*hscontrol.DebugBatcherInfo, error) {
|
||||
// Execute curl inside the container to access the debug endpoint locally
|
||||
command := []string{
|
||||
"curl", "-s", "-H", "Accept: application/json", "http://localhost:9090/debug/batcher",
|
||||
"curl", "-s", "-H", acceptJSON, "http://localhost:9090/debug/batcher",
|
||||
}
|
||||
|
||||
result, err := t.Execute(command)
|
||||
@@ -1677,11 +1680,11 @@ func (t *HeadscaleInContainer) DebugBatcher() (*hscontrol.DebugBatcherInfo, erro
|
||||
return &debugInfo, nil
|
||||
}
|
||||
|
||||
// DebugNodeStore fetches the NodeStore data from the debug endpoint.
|
||||
// DebugNodeStore fetches the [state.NodeStore] data from the debug endpoint.
|
||||
func (t *HeadscaleInContainer) DebugNodeStore() (map[types.NodeID]types.Node, error) {
|
||||
// Execute curl inside the container to access the debug endpoint locally
|
||||
command := []string{
|
||||
"curl", "-s", "-H", "Accept: application/json", "http://localhost:9090/debug/nodestore",
|
||||
"curl", "-s", "-H", acceptJSON, "http://localhost:9090/debug/nodestore",
|
||||
}
|
||||
|
||||
result, err := t.Execute(command)
|
||||
@@ -1701,7 +1704,7 @@ func (t *HeadscaleInContainer) DebugNodeStore() (map[types.NodeID]types.Node, er
|
||||
func (t *HeadscaleInContainer) DebugFilter() ([]tailcfg.FilterRule, error) {
|
||||
// Execute curl inside the container to access the debug endpoint locally
|
||||
command := []string{
|
||||
"curl", "-s", "-H", "Accept: application/json", "http://localhost:9090/debug/filter",
|
||||
"curl", "-s", "-H", acceptJSON, "http://localhost:9090/debug/filter",
|
||||
}
|
||||
|
||||
result, err := t.Execute(command)
|
||||
|
||||
@@ -43,6 +43,7 @@ const (
|
||||
dockerContextPath = "../."
|
||||
caCertRoot = "/usr/local/share/ca-certificates"
|
||||
dockerExecuteTimeout = 60 * time.Second
|
||||
tailscaleBin = "tailscale"
|
||||
)
|
||||
|
||||
// defaultPingTimeoutVal returns the per-attempt timeout for tailscale ping.
|
||||
@@ -127,7 +128,7 @@ func WithCACert(cert []byte) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithNetwork sets the Docker container network to use with
|
||||
// WithNetwork sets the Docker [dockertest.Network] to use with
|
||||
// the Tailscale instance.
|
||||
func WithNetwork(network *dockertest.Network) Option {
|
||||
return func(tsic *TailscaleInContainer) {
|
||||
@@ -215,7 +216,7 @@ func WithBuildTag(tag string) Option {
|
||||
}
|
||||
|
||||
// WithExtraLoginArgs adds additional arguments to the `tailscale up` command
|
||||
// as part of the Login function.
|
||||
// as part of the [TailscaleInContainer.Login] function.
|
||||
func WithExtraLoginArgs(args []string) Option {
|
||||
return func(tsic *TailscaleInContainer) {
|
||||
tsic.extraLoginArgs = append(tsic.extraLoginArgs, args...)
|
||||
@@ -270,7 +271,7 @@ func (t *TailscaleInContainer) buildEntrypoint() []string {
|
||||
commands = append(commands, "while ! ip route show default >/dev/null 2>&1; do sleep 0.1; done")
|
||||
|
||||
// If CA certs are configured, wait for them to be written by the Go code
|
||||
// (certs are written after container start via tsic.WriteFile)
|
||||
// (certs are written after container start via [TailscaleInContainer.WriteFile])
|
||||
if len(t.caCerts) > 0 {
|
||||
commands = append(commands,
|
||||
fmt.Sprintf("while [ ! -f %s/user-0.crt ]; do sleep 0.1; done", caCertRoot))
|
||||
@@ -389,7 +390,7 @@ func New(
|
||||
}
|
||||
|
||||
// Add integration test labels if running under hi tool
|
||||
dockertestutil.DockerAddIntegrationLabels(tailscaleOptions, "tailscale")
|
||||
dockertestutil.DockerAddIntegrationLabels(tailscaleOptions, tailscaleBin)
|
||||
|
||||
var container *dockertest.Resource
|
||||
|
||||
@@ -413,13 +414,13 @@ func New(
|
||||
// the pre-built image as it won't have the necessary code compiled in.
|
||||
hasBuildTags := len(tsic.buildConfig.tags) > 0
|
||||
if hasBuildTags && prebuiltImage != "" {
|
||||
log.Printf("Ignoring pre-built image %s because custom build tags are required: %v",
|
||||
log.Printf("Ignoring pre-built image %s because custom build tags are required: %v", //nolint:gosec // G706: integration-only log of trusted env value
|
||||
prebuiltImage, tsic.buildConfig.tags)
|
||||
prebuiltImage = ""
|
||||
}
|
||||
|
||||
if prebuiltImage != "" {
|
||||
log.Printf("Using pre-built tailscale image: %s", prebuiltImage)
|
||||
log.Printf("Using pre-built tailscale image: %s", prebuiltImage) //nolint:gosec // G706: integration-only log of trusted env value
|
||||
|
||||
// Parse image into repository and tag
|
||||
repo, tag, ok := strings.Cut(prebuiltImage, ":")
|
||||
@@ -609,7 +610,7 @@ func (t *TailscaleInContainer) Version() string {
|
||||
return t.version
|
||||
}
|
||||
|
||||
// ContainerID returns the Docker container ID of the TailscaleInContainer
|
||||
// ContainerID returns the Docker container ID of the [TailscaleInContainer]
|
||||
// instance.
|
||||
func (t *TailscaleInContainer) ContainerID() string {
|
||||
return t.container.Container.ID
|
||||
@@ -657,7 +658,7 @@ func (t *TailscaleInContainer) buildLoginCommand(
|
||||
loginServer, authKey string,
|
||||
) []string {
|
||||
command := []string{
|
||||
"tailscale",
|
||||
tailscaleBin,
|
||||
"up",
|
||||
"--login-server=" + loginServer,
|
||||
"--hostname=" + t.hostname,
|
||||
@@ -736,12 +737,12 @@ func (t *TailscaleInContainer) LoginWithURL(
|
||||
|
||||
// Logout runs the logout routine on the given Tailscale instance.
|
||||
func (t *TailscaleInContainer) Logout() error {
|
||||
_, _, err := t.Execute([]string{"tailscale", "logout"})
|
||||
_, _, err := t.Execute([]string{tailscaleBin, "logout"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdout, stderr, _ := t.Execute([]string{"tailscale", "status"})
|
||||
stdout, stderr, _ := t.Execute([]string{tailscaleBin, "status"})
|
||||
if !strings.Contains(stdout+stderr, "Logged out.") {
|
||||
return fmt.Errorf("logging out, stdout: %s, stderr: %s", stdout, stderr) //nolint:err113
|
||||
}
|
||||
@@ -768,7 +769,7 @@ func (t *TailscaleInContainer) Restart() error {
|
||||
// We use exponential backoff to poll until we can successfully execute a command
|
||||
_, err = backoff.Retry(context.Background(), func() (struct{}, error) {
|
||||
// Try to execute a simple command to verify the container is responsive
|
||||
_, _, err := t.Execute([]string{"tailscale", "version"}, dockertestutil.ExecuteCommandTimeout(5*time.Second))
|
||||
_, _, err := t.Execute([]string{tailscaleBin, "version"}, dockertestutil.ExecuteCommandTimeout(5*time.Second))
|
||||
if err != nil {
|
||||
return struct{}{}, fmt.Errorf("container not ready: %w", err)
|
||||
}
|
||||
@@ -785,7 +786,7 @@ func (t *TailscaleInContainer) Restart() error {
|
||||
// Up runs `tailscale up` with no arguments.
|
||||
func (t *TailscaleInContainer) Up() error {
|
||||
command := []string{
|
||||
"tailscale",
|
||||
tailscaleBin,
|
||||
"up",
|
||||
}
|
||||
|
||||
@@ -804,7 +805,7 @@ func (t *TailscaleInContainer) Up() error {
|
||||
// Down runs `tailscale down` with no arguments.
|
||||
func (t *TailscaleInContainer) Down() error {
|
||||
command := []string{
|
||||
"tailscale",
|
||||
tailscaleBin,
|
||||
"down",
|
||||
}
|
||||
|
||||
@@ -835,7 +836,7 @@ func (t *TailscaleInContainer) ReconnectToNetwork(network *dockertest.Network) e
|
||||
return dockertestutil.ReconnectContainerToNetwork(t.pool, network, t.hostname)
|
||||
}
|
||||
|
||||
// IPs returns the netip.Addr of the Tailscale instance.
|
||||
// IPs returns the [netip.Addr] of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
|
||||
if len(t.ips) != 0 {
|
||||
return t.ips, nil
|
||||
@@ -844,7 +845,7 @@ func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
|
||||
// Retry with exponential backoff to handle eventual consistency
|
||||
ips, err := backoff.Retry(context.Background(), func() ([]netip.Addr, error) {
|
||||
command := []string{
|
||||
"tailscale",
|
||||
tailscaleBin,
|
||||
"ip",
|
||||
}
|
||||
|
||||
@@ -926,10 +927,10 @@ func (t *TailscaleInContainer) MustIPv6() netip.Addr {
|
||||
panic("no ipv6 found")
|
||||
}
|
||||
|
||||
// Status returns the ipnstate.Status of the Tailscale instance.
|
||||
// Status returns the [ipnstate.Status] of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) Status(save ...bool) (*ipnstate.Status, error) {
|
||||
command := []string{
|
||||
"tailscale",
|
||||
tailscaleBin,
|
||||
"status",
|
||||
"--json",
|
||||
}
|
||||
@@ -954,7 +955,7 @@ func (t *TailscaleInContainer) Status(save ...bool) (*ipnstate.Status, error) {
|
||||
return &status, err
|
||||
}
|
||||
|
||||
// MustStatus returns the ipnstate.Status of the Tailscale instance.
|
||||
// MustStatus returns the [ipnstate.Status] of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) MustStatus() *ipnstate.Status {
|
||||
status, err := t.Status()
|
||||
if err != nil {
|
||||
@@ -979,7 +980,7 @@ func (t *TailscaleInContainer) MustID() types.NodeID {
|
||||
return types.NodeID(id)
|
||||
}
|
||||
|
||||
// Netmap returns the current Netmap (netmap.NetworkMap) of the Tailscale instance.
|
||||
// Netmap returns the current Netmap ([netmap.NetworkMap]) of the Tailscale instance.
|
||||
// Only works with Tailscale 1.56 and newer.
|
||||
// Panics if version is lower then minimum.
|
||||
func (t *TailscaleInContainer) Netmap() (*netmap.NetworkMap, error) {
|
||||
@@ -988,7 +989,7 @@ func (t *TailscaleInContainer) Netmap() (*netmap.NetworkMap, error) {
|
||||
}
|
||||
|
||||
command := []string{
|
||||
"tailscale",
|
||||
tailscaleBin,
|
||||
"debug",
|
||||
"netmap",
|
||||
}
|
||||
@@ -1014,7 +1015,7 @@ func (t *TailscaleInContainer) Netmap() (*netmap.NetworkMap, error) {
|
||||
return &nm, err
|
||||
}
|
||||
|
||||
// Netmap returns the current Netmap (netmap.NetworkMap) of the Tailscale instance.
|
||||
// Netmap returns the current Netmap ([netmap.NetworkMap]) of the Tailscale instance.
|
||||
// This implementation is based on getting the netmap from `tailscale debug watch-ipn`
|
||||
// as there seem to be some weirdness omitting endpoint and DERP info if we use
|
||||
// Patch updates.
|
||||
@@ -1037,8 +1038,8 @@ func (t *TailscaleInContainer) Netmap() (*netmap.NetworkMap, error) {
|
||||
// return notify.NetMap, nil
|
||||
// }
|
||||
|
||||
// watchIPN watches `tailscale debug watch-ipn` for a ipn.Notify object until
|
||||
// it gets one that has a netmap.NetworkMap.
|
||||
// watchIPN watches `tailscale debug watch-ipn` for a [ipn.Notify] object until
|
||||
// it gets one that has a [netmap.NetworkMap].
|
||||
//
|
||||
//nolint:unused
|
||||
func (t *TailscaleInContainer) watchIPN(ctx context.Context) (*ipn.Notify, error) {
|
||||
@@ -1115,7 +1116,7 @@ func (t *TailscaleInContainer) DebugDERPRegion(region string) (*ipnstate.DebugDE
|
||||
}
|
||||
|
||||
command := []string{
|
||||
"tailscale",
|
||||
tailscaleBin,
|
||||
"debug",
|
||||
"derp",
|
||||
region,
|
||||
@@ -1138,10 +1139,10 @@ func (t *TailscaleInContainer) DebugDERPRegion(region string) (*ipnstate.DebugDE
|
||||
return &report, err
|
||||
}
|
||||
|
||||
// Netcheck returns the current Netcheck Report (netcheck.Report) of the Tailscale instance.
|
||||
// Netcheck returns the current Netcheck Report ([netcheck.Report]) of the Tailscale instance.
|
||||
func (t *TailscaleInContainer) Netcheck() (*netcheck.Report, error) {
|
||||
command := []string{
|
||||
"tailscale",
|
||||
tailscaleBin,
|
||||
"netcheck",
|
||||
"--format=json",
|
||||
}
|
||||
@@ -1258,7 +1259,7 @@ func (t *TailscaleInContainer) waitForBackendState(state string, timeout time.Du
|
||||
continue // Keep retrying on status errors
|
||||
}
|
||||
|
||||
// ipnstate.Status.CurrentTailnet was added in Tailscale 1.22.0
|
||||
// [ipnstate.Status.CurrentTailnet] was added in Tailscale 1.22.0
|
||||
// https://github.com/tailscale/tailscale/pull/3865
|
||||
//
|
||||
// Before that, we can check the BackendState to see if the
|
||||
@@ -1382,7 +1383,7 @@ func WithPingUntilDirect(direct bool) PingOption {
|
||||
}
|
||||
|
||||
// Ping executes the Tailscale ping command and pings a hostname
|
||||
// or IP. It accepts a series of PingOption.
|
||||
// or IP. It accepts a series of [PingOption].
|
||||
// TODO(kradalby): Make multiping, go routine magic.
|
||||
func (t *TailscaleInContainer) Ping(hostnameOrIP string, opts ...PingOption) error {
|
||||
args := pingArgs{
|
||||
@@ -1397,7 +1398,7 @@ func (t *TailscaleInContainer) Ping(hostnameOrIP string, opts ...PingOption) err
|
||||
|
||||
command := make([]string, 0, 6)
|
||||
command = append(command,
|
||||
"tailscale", "ping",
|
||||
tailscaleBin, "ping",
|
||||
fmt.Sprintf("--timeout=%s", args.timeout),
|
||||
fmt.Sprintf("--c=%d", args.count),
|
||||
"--until-direct="+strconv.FormatBool(args.direct),
|
||||
@@ -1494,7 +1495,7 @@ const (
|
||||
)
|
||||
|
||||
// Curl executes the Tailscale curl command and curls a hostname
|
||||
// or IP. It accepts a series of CurlOption.
|
||||
// or IP. It accepts a series of [CurlOption].
|
||||
func (t *TailscaleInContainer) Curl(url string, opts ...CurlOption) (string, error) {
|
||||
args := curlArgs{
|
||||
connectionTimeout: defaultConnectionTimeout,
|
||||
@@ -1536,7 +1537,7 @@ func (t *TailscaleInContainer) Curl(url string, opts ...CurlOption) (string, err
|
||||
// curl exit 0 with an empty body usually means a mid-stream reset
|
||||
// after headers (HTTP 200 with the connection torn down before the
|
||||
// body arrived). Without this signal, callers wrapping the call in
|
||||
// EventuallyWithT see assert.NoError pass and assert.Len fail with
|
||||
// [assert.EventuallyWithT] see [assert.NoError] pass and [assert.Len] fail with
|
||||
// no error to drive a retry.
|
||||
if result == "" {
|
||||
return result, fmt.Errorf("%w: %s from %s", errCurlEmptyResponseBody, url, t.Hostname())
|
||||
@@ -1558,7 +1559,7 @@ func (t *TailscaleInContainer) CurlFailFast(url string) (string, error) {
|
||||
|
||||
func (t *TailscaleInContainer) Traceroute(ip netip.Addr) (util.Traceroute, error) {
|
||||
// -w 1: wait at most 1s for each probe response. busybox's default
|
||||
// is 5s, which means an EventuallyWithT loop at 200ms ticks
|
||||
// is 5s, which means an [assert.EventuallyWithT] loop at 200ms ticks
|
||||
// can spend 25 ticks worth of budget on a single Traceroute.
|
||||
// -q 1: send 1 probe per hop instead of 3. The HA tests only care
|
||||
// about the first hop's identity; the other probes are dead
|
||||
@@ -1603,7 +1604,7 @@ func (t *TailscaleInContainer) SaveLog(path string) (string, string, error) {
|
||||
}
|
||||
|
||||
// WriteLogs writes the current stdout/stderr log of the container to
|
||||
// the given io.Writers.
|
||||
// the given [io.Writer]s.
|
||||
func (t *TailscaleInContainer) WriteLogs(stdout, stderr io.Writer) error {
|
||||
return dockertestutil.WriteLog(t.pool, t.container, stdout, stderr)
|
||||
}
|
||||
@@ -1677,7 +1678,7 @@ func (t *TailscaleInContainer) GetNodePrivateKey() (*key.NodePrivate, error) {
|
||||
return &p.Persist.PrivateNodeKey, nil
|
||||
}
|
||||
|
||||
// ConnectToNetwork connects the Tailscale container to an additional Docker network.
|
||||
// ConnectToNetwork connects the Tailscale container to an additional Docker [dockertest.Network].
|
||||
func (t *TailscaleInContainer) ConnectToNetwork(network *dockertest.Network) error {
|
||||
return t.container.ConnectToNetwork(network)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user