mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-17 02:27:35 +00:00
Compare commits
29 Commits
main
...
web-auth-f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
70e08462b3 | ||
![]() |
a231ece825 | ||
![]() |
ae43d82a33 | ||
![]() |
ad3c36fd07 | ||
![]() |
f176503448 | ||
![]() |
f7ad88aa08 | ||
![]() |
f63d22655c | ||
![]() |
89c468fc43 | ||
![]() |
b0fda6b216 | ||
![]() |
154fb59bdb | ||
![]() |
d3e9703fb5 | ||
![]() |
7ce3f8c7d1 | ||
![]() |
58c8633cc1 | ||
![]() |
b3f5af30a4 | ||
![]() |
9f64ac8a33 | ||
![]() |
aa1cc05cfb | ||
![]() |
670ef9a93e | ||
![]() |
987abcfdce | ||
![]() |
c70f5696dc | ||
![]() |
825e88311e | ||
![]() |
bbc8cb11da | ||
![]() |
3a6ef6bece | ||
![]() |
b2dc480f22 | ||
![]() |
5d7eae46f8 | ||
![]() |
45cb0f3fa3 | ||
![]() |
658478cba3 | ||
![]() |
ec90e9d716 | ||
![]() |
181f1eeb4f | ||
![]() |
e270cf6d20 |
35
.github/workflows/test-integration-v2-general-auth.yml
vendored
Normal file
35
.github/workflows/test-integration-v2-general-auth.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Integration Test v2
|
||||||
|
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
integration-test-v2:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Set Swap Space
|
||||||
|
uses: pierotofy/set-swap-space@master
|
||||||
|
with:
|
||||||
|
swap-size-gb: 10
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
*.nix
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
|
- uses: cachix/install-nix-action@v16
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
|
||||||
|
- name: Run general integration tests
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
run: nix develop --command -- make test_integration_v2_auth_web_flow
|
@@ -36,6 +36,9 @@ linters:
|
|||||||
- makezero
|
- makezero
|
||||||
- maintidx
|
- maintidx
|
||||||
|
|
||||||
|
# Limits the methods of an interface to 10. We have more in integration tests
|
||||||
|
- interfacebloat
|
||||||
|
|
||||||
# We might want to enable this, but it might be a lot of work
|
# We might want to enable this, but it might be a lot of work
|
||||||
- cyclop
|
- cyclop
|
||||||
- nestif
|
- nestif
|
||||||
|
11
Makefile
11
Makefile
@@ -68,6 +68,17 @@ test_integration_v2_general:
|
|||||||
golang:1 \
|
golang:1 \
|
||||||
go test $(TAGS) -failfast ./... -timeout 60m -parallel 6
|
go test $(TAGS) -failfast ./... -timeout 60m -parallel 6
|
||||||
|
|
||||||
|
|
||||||
|
test_integration_v2_auth_web_flow:
|
||||||
|
docker run \
|
||||||
|
-t --rm \
|
||||||
|
-v ~/.cache/hs-integration-go:/go \
|
||||||
|
--name headscale-test-suite \
|
||||||
|
-v $$PWD:$$PWD -w $$PWD/integration \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
golang:1 \
|
||||||
|
go test ./... -timeout 60m -parallel 6 -run TestAuthWebFlow
|
||||||
|
|
||||||
coverprofile_func:
|
coverprofile_func:
|
||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
|
|
||||||
|
@@ -134,7 +134,7 @@ var registerNodeCmd = &cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SuccessOutput(response.Machine, "Machine register", output)
|
SuccessOutput(response.Machine, fmt.Sprintf("Machine %s registered", response.Machine.GivenName), output)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
192
integration/auth_web_flow_test.go
Normal file
192
integration/auth_web_flow_test.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errParseAuthPage = errors.New("failed to parse auth page")
|
||||||
|
|
||||||
|
type AuthWebFlowScenario struct {
|
||||||
|
*Scenario
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthWebFlowAuthenticationPingAll(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
|
||||||
|
baseScenario, err := NewScenario()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create scenario: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario := AuthWebFlowScenario{
|
||||||
|
Scenario: baseScenario,
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := map[string]int{
|
||||||
|
"namespace1": len(TailscaleVersions),
|
||||||
|
"namespace2": len(TailscaleVersions),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.CreateHeadscaleEnv(spec)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create headscale environment: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
allClients, err := scenario.ListTailscaleClients()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get clients: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
allIps, err := scenario.ListTailscaleClientsIPs()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get clients: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed wait for tailscale clients to be in sync: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
success := 0
|
||||||
|
|
||||||
|
for _, client := range allClients {
|
||||||
|
for _, ip := range allIps {
|
||||||
|
err := client.Ping(ip.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
|
||||||
|
} else {
|
||||||
|
success++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||||
|
|
||||||
|
err = scenario.Shutdown()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to tear down scenario: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AuthWebFlowScenario) CreateHeadscaleEnv(namespaces map[string]int) error {
|
||||||
|
err := s.StartHeadscale()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Headscale().WaitForReady()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for namespaceName, clientCount := range namespaces {
|
||||||
|
log.Printf("creating namespace %s with %d clients", namespaceName, clientCount)
|
||||||
|
err = s.CreateNamespace(namespaceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.CreateTailscaleNodesInNamespace(namespaceName, "all", clientCount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.runTailscaleUp(namespaceName, s.Headscale().GetEndpoint())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AuthWebFlowScenario) runTailscaleUp(
|
||||||
|
namespaceStr, loginServer string,
|
||||||
|
) error {
|
||||||
|
log.Printf("running tailscale up for namespace %s", namespaceStr)
|
||||||
|
if namespace, ok := s.namespaces[namespaceStr]; ok {
|
||||||
|
for _, client := range namespace.Clients {
|
||||||
|
namespace.joinWaitGroup.Add(1)
|
||||||
|
|
||||||
|
go func(c TailscaleClient) {
|
||||||
|
defer namespace.joinWaitGroup.Done()
|
||||||
|
|
||||||
|
// TODO(juanfont): error handle this
|
||||||
|
loginURL, err := c.UpWithLoginURL(loginServer)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to run tailscale up: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.runHeadscaleRegister(namespaceStr, loginURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to register client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.WaitForReady()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error waiting for client %s to be ready: %s", c.Hostname(), err)
|
||||||
|
}
|
||||||
|
}(client)
|
||||||
|
}
|
||||||
|
namespace.joinWaitGroup.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to up tailscale node: %w", errNoNamespaceAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AuthWebFlowScenario) runHeadscaleRegister(namespaceStr string, loginURL *url.URL) error {
|
||||||
|
log.Printf("loginURL: %s", loginURL)
|
||||||
|
loginURL.Host = fmt.Sprintf("%s:8080", s.Headscale().GetIP())
|
||||||
|
loginURL.Scheme = "http"
|
||||||
|
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
ctx := context.Background()
|
||||||
|
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, loginURL.String(), nil)
|
||||||
|
resp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// see api.go HTML template
|
||||||
|
codeSep := strings.Split(string(body), "</code>")
|
||||||
|
if len(codeSep) != 2 {
|
||||||
|
return errParseAuthPage
|
||||||
|
}
|
||||||
|
|
||||||
|
keySep := strings.Split(codeSep[0], "key ")
|
||||||
|
if len(keySep) != 2 {
|
||||||
|
return errParseAuthPage
|
||||||
|
}
|
||||||
|
key := keySep[1]
|
||||||
|
log.Printf("registering node %s", key)
|
||||||
|
|
||||||
|
if headscale, ok := s.controlServers["headscale"]; ok {
|
||||||
|
_, err = headscale.Execute([]string{"headscale", "-n", namespaceStr, "nodes", "register", "--key", key})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to register node: %s", err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to find headscale: %w", errNoHeadscaleAvailable)
|
||||||
|
}
|
@@ -168,7 +168,7 @@ func TestTaildrop(t *testing.T) {
|
|||||||
for _, client := range allClients {
|
for _, client := range allClients {
|
||||||
command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", client.Hostname())}
|
command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", client.Hostname())}
|
||||||
|
|
||||||
if _, err := client.Execute(command); err != nil {
|
if _, _, err := client.Execute(command); err != nil {
|
||||||
t.Errorf("failed to create taildrop file on %s, err: %s", client.Hostname(), err)
|
t.Errorf("failed to create taildrop file on %s, err: %s", client.Hostname(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ func TestTaildrop(t *testing.T) {
|
|||||||
client.Hostname(),
|
client.Hostname(),
|
||||||
peer.Hostname(),
|
peer.Hostname(),
|
||||||
)
|
)
|
||||||
_, err := client.Execute(command)
|
_, _, err := client.Execute(command)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -214,7 +214,7 @@ func TestTaildrop(t *testing.T) {
|
|||||||
"get",
|
"get",
|
||||||
"/tmp/",
|
"/tmp/",
|
||||||
}
|
}
|
||||||
if _, err := client.Execute(command); err != nil {
|
if _, _, err := client.Execute(command); err != nil {
|
||||||
t.Errorf("failed to get taildrop file on %s, err: %s", client.Hostname(), err)
|
t.Errorf("failed to get taildrop file on %s, err: %s", client.Hostname(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ func TestTaildrop(t *testing.T) {
|
|||||||
peer.Hostname(),
|
peer.Hostname(),
|
||||||
)
|
)
|
||||||
|
|
||||||
result, err := client.Execute(command)
|
result, _, err := client.Execute(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to execute command to ls taildrop: %s", err)
|
t.Errorf("failed to execute command to ls taildrop: %s", err)
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ func TestResolveMagicDNS(t *testing.T) {
|
|||||||
"tailscale",
|
"tailscale",
|
||||||
"ip", peerFQDN,
|
"ip", peerFQDN,
|
||||||
}
|
}
|
||||||
result, err := client.Execute(command)
|
result, _, err := client.Execute(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"failed to execute resolve/ip command %s from %s: %s",
|
"failed to execute resolve/ip command %s from %s: %s",
|
||||||
|
@@ -179,9 +179,7 @@ func (t *HeadscaleInContainer) GetIP() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *HeadscaleInContainer) GetPort() string {
|
func (t *HeadscaleInContainer) GetPort() string {
|
||||||
portProto := fmt.Sprintf("%d/tcp", t.port)
|
return fmt.Sprintf("%d", t.port)
|
||||||
|
|
||||||
return t.container.GetPort(portProto)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HeadscaleInContainer) GetHealthEndpoint() string {
|
func (t *HeadscaleInContainer) GetHealthEndpoint() string {
|
||||||
|
@@ -2,6 +2,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
)
|
)
|
||||||
@@ -10,11 +11,13 @@ type TailscaleClient interface {
|
|||||||
Hostname() string
|
Hostname() string
|
||||||
Shutdown() error
|
Shutdown() error
|
||||||
Version() string
|
Version() string
|
||||||
Execute(command []string) (string, error)
|
Execute(command []string) (string, string, error)
|
||||||
Up(loginServer, authKey string) error
|
Up(loginServer, authKey string) error
|
||||||
|
UpWithLoginURL(loginServer string) (*url.URL, error)
|
||||||
IPs() ([]netip.Addr, error)
|
IPs() ([]netip.Addr, error)
|
||||||
FQDN() (string, error)
|
FQDN() (string, error)
|
||||||
Status() (*ipnstate.Status, error)
|
Status() (*ipnstate.Status, error)
|
||||||
|
WaitForReady() error
|
||||||
WaitForPeers(expected int) error
|
WaitForPeers(expected int) error
|
||||||
Ping(hostnameOrIP string) error
|
Ping(hostnameOrIP string) error
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
@@ -25,6 +26,7 @@ var (
|
|||||||
errTailscalePingFailed = errors.New("ping failed")
|
errTailscalePingFailed = errors.New("ping failed")
|
||||||
errTailscaleNotLoggedIn = errors.New("tailscale not logged in")
|
errTailscaleNotLoggedIn = errors.New("tailscale not logged in")
|
||||||
errTailscaleWrongPeerCount = errors.New("wrong peer count")
|
errTailscaleWrongPeerCount = errors.New("wrong peer count")
|
||||||
|
errTailscaleNotConnected = errors.New("tailscale not connected")
|
||||||
)
|
)
|
||||||
|
|
||||||
type TailscaleInContainer struct {
|
type TailscaleInContainer struct {
|
||||||
@@ -110,7 +112,7 @@ func (t *TailscaleInContainer) Version() string {
|
|||||||
|
|
||||||
func (t *TailscaleInContainer) Execute(
|
func (t *TailscaleInContainer) Execute(
|
||||||
command []string,
|
command []string,
|
||||||
) (string, error) {
|
) (string, string, error) {
|
||||||
log.Println("command", command)
|
log.Println("command", command)
|
||||||
log.Printf("running command for %s\n", t.hostname)
|
log.Printf("running command for %s\n", t.hostname)
|
||||||
stdout, stderr, err := dockertestutil.ExecuteCommand(
|
stdout, stderr, err := dockertestutil.ExecuteCommand(
|
||||||
@@ -126,13 +128,13 @@ func (t *TailscaleInContainer) Execute(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(stderr, "NeedsLogin") {
|
if strings.Contains(stderr, "NeedsLogin") {
|
||||||
return "", errTailscaleNotLoggedIn
|
return stdout, stderr, errTailscaleNotLoggedIn
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", err
|
return stdout, stderr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return stdout, nil
|
return stdout, stderr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TailscaleInContainer) Up(
|
func (t *TailscaleInContainer) Up(
|
||||||
@@ -149,13 +151,42 @@ func (t *TailscaleInContainer) Up(
|
|||||||
t.hostname,
|
t.hostname,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := t.Execute(command); err != nil {
|
if _, _, err := t.Execute(command); err != nil {
|
||||||
return fmt.Errorf("failed to join tailscale client: %w", err)
|
return fmt.Errorf("failed to join tailscale client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TailscaleInContainer) UpWithLoginURL(
|
||||||
|
loginServer string,
|
||||||
|
) (*url.URL, error) {
|
||||||
|
command := []string{
|
||||||
|
"tailscale",
|
||||||
|
"up",
|
||||||
|
"-login-server",
|
||||||
|
loginServer,
|
||||||
|
"--hostname",
|
||||||
|
t.hostname,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, stderr, _ := t.Execute(command)
|
||||||
|
|
||||||
|
urlStr := strings.ReplaceAll(stderr, "\nTo authenticate, visit:\n\n\t", "")
|
||||||
|
urlStr = strings.TrimSpace(urlStr)
|
||||||
|
|
||||||
|
// parse URL
|
||||||
|
loginURL, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not parse login URL: %s", err)
|
||||||
|
log.Printf("Original join command result: %s", stderr)
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return loginURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
|
func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
|
||||||
if t.ips != nil && len(t.ips) != 0 {
|
if t.ips != nil && len(t.ips) != 0 {
|
||||||
return t.ips, nil
|
return t.ips, nil
|
||||||
@@ -168,7 +199,7 @@ func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
|
|||||||
"ip",
|
"ip",
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := t.Execute(command)
|
result, _, err := t.Execute(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []netip.Addr{}, fmt.Errorf("failed to join tailscale client: %w", err)
|
return []netip.Addr{}, fmt.Errorf("failed to join tailscale client: %w", err)
|
||||||
}
|
}
|
||||||
@@ -195,7 +226,7 @@ func (t *TailscaleInContainer) Status() (*ipnstate.Status, error) {
|
|||||||
"--json",
|
"--json",
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := t.Execute(command)
|
result, _, err := t.Execute(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute tailscale status command: %w", err)
|
return nil, fmt.Errorf("failed to execute tailscale status command: %w", err)
|
||||||
}
|
}
|
||||||
@@ -222,6 +253,21 @@ func (t *TailscaleInContainer) FQDN() (string, error) {
|
|||||||
return status.Self.DNSName, nil
|
return status.Self.DNSName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TailscaleInContainer) WaitForReady() error {
|
||||||
|
return t.pool.Retry(func() error {
|
||||||
|
status, err := t.Status()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch tailscale status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.CurrentTailnet != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errTailscaleNotConnected
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TailscaleInContainer) WaitForPeers(expected int) error {
|
func (t *TailscaleInContainer) WaitForPeers(expected int) error {
|
||||||
return t.pool.Retry(func() error {
|
return t.pool.Retry(func() error {
|
||||||
status, err := t.Status()
|
status, err := t.Status()
|
||||||
@@ -248,7 +294,7 @@ func (t *TailscaleInContainer) Ping(hostnameOrIP string) error {
|
|||||||
hostnameOrIP,
|
hostnameOrIP,
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := t.Execute(command)
|
result, _, err := t.Execute(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"failed to run ping command from %s to %s, err: %s",
|
"failed to run ping command from %s to %s, err: %s",
|
||||||
|
Reference in New Issue
Block a user