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:
Kristoffer Dalby
2026-05-18 18:33:40 +00:00
parent 64c398f2c2
commit e00c899219
15 changed files with 263 additions and 216 deletions

View File

@@ -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",

View File

@@ -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)

View File

@@ -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)
}