218 lines
4.4 KiB
Go
218 lines
4.4 KiB
Go
|
package tsic
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"net/netip"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/juanfont/headscale"
|
||
|
"github.com/juanfont/headscale/integration/dockertestutil"
|
||
|
"github.com/ory/dockertest/v3"
|
||
|
"github.com/ory/dockertest/v3/docker"
|
||
|
)
|
||
|
|
||
|
const tsicHashLength = 6
|
||
|
const dockerContextPath = "../."
|
||
|
|
||
|
var errTailscalePingFailed = errors.New("ping failed")
|
||
|
|
||
|
type TailscaleInContainer struct {
|
||
|
version string
|
||
|
Hostname string
|
||
|
|
||
|
pool *dockertest.Pool
|
||
|
container *dockertest.Resource
|
||
|
network *dockertest.Network
|
||
|
}
|
||
|
|
||
|
func New(
|
||
|
pool *dockertest.Pool,
|
||
|
version string,
|
||
|
network *dockertest.Network) (*TailscaleInContainer, error) {
|
||
|
hash, err := headscale.GenerateRandomStringDNSSafe(tsicHashLength)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
hostname := fmt.Sprintf("ts-%s-%s", version, hash)
|
||
|
|
||
|
// TODO(kradalby): figure out why we need to "refresh" the network here.
|
||
|
// network, err = dockertestutil.GetFirstOrCreateNetwork(pool, network.Network.Name)
|
||
|
// if err != nil {
|
||
|
// return nil, err
|
||
|
// }
|
||
|
|
||
|
tailscaleOptions := &dockertest.RunOptions{
|
||
|
Name: hostname,
|
||
|
Networks: []*dockertest.Network{network},
|
||
|
Cmd: []string{
|
||
|
"tailscaled", "--tun=tsdev",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// dockertest isnt very good at handling containers that has already
|
||
|
// been created, this is an attempt to make sure this container isnt
|
||
|
// present.
|
||
|
err = pool.RemoveContainerByName(hostname)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
container, err := pool.BuildAndRunWithBuildOptions(
|
||
|
createTailscaleBuildOptions(version),
|
||
|
tailscaleOptions,
|
||
|
dockertestutil.DockerRestartPolicy,
|
||
|
dockertestutil.DockerAllowLocalIPv6,
|
||
|
dockertestutil.DockerAllowNetworkAdministration,
|
||
|
)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("could not start tailscale container: %w", err)
|
||
|
}
|
||
|
log.Printf("Created %s container\n", hostname)
|
||
|
|
||
|
return &TailscaleInContainer{
|
||
|
version: version,
|
||
|
Hostname: hostname,
|
||
|
|
||
|
pool: pool,
|
||
|
container: container,
|
||
|
network: network,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (t *TailscaleInContainer) Shutdown() error {
|
||
|
return t.pool.Purge(t.container)
|
||
|
}
|
||
|
|
||
|
func (t *TailscaleInContainer) Up(
|
||
|
loginServer, authKey string,
|
||
|
) error {
|
||
|
command := []string{
|
||
|
"tailscale",
|
||
|
"up",
|
||
|
"-login-server",
|
||
|
loginServer,
|
||
|
"--authkey",
|
||
|
authKey,
|
||
|
"--hostname",
|
||
|
t.Hostname,
|
||
|
}
|
||
|
|
||
|
log.Println("Join command:", command)
|
||
|
log.Printf("Running join command for %s\n", t.Hostname)
|
||
|
_, _, err := dockertestutil.ExecuteCommand(
|
||
|
t.container,
|
||
|
command,
|
||
|
[]string{},
|
||
|
)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
log.Printf("%s joined\n", t.Hostname)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
|
||
|
ips := make([]netip.Addr, 0)
|
||
|
|
||
|
command := []string{
|
||
|
"tailscale",
|
||
|
"ip",
|
||
|
}
|
||
|
|
||
|
result, _, err := dockertestutil.ExecuteCommand(
|
||
|
t.container,
|
||
|
command,
|
||
|
[]string{},
|
||
|
)
|
||
|
if err != nil {
|
||
|
return []netip.Addr{}, err
|
||
|
}
|
||
|
|
||
|
for _, address := range strings.Split(result, "\n") {
|
||
|
address = strings.TrimSuffix(address, "\n")
|
||
|
if len(address) < 1 {
|
||
|
continue
|
||
|
}
|
||
|
ip, err := netip.ParseAddr(address)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ips = append(ips, ip)
|
||
|
}
|
||
|
|
||
|
return ips, nil
|
||
|
}
|
||
|
|
||
|
func (t *TailscaleInContainer) Ping(ip netip.Addr) error {
|
||
|
command := []string{
|
||
|
"tailscale", "ping",
|
||
|
"--timeout=1s",
|
||
|
"--c=10",
|
||
|
"--until-direct=true",
|
||
|
ip.String(),
|
||
|
}
|
||
|
|
||
|
result, _, err := dockertestutil.ExecuteCommand(
|
||
|
t.container,
|
||
|
command,
|
||
|
[]string{},
|
||
|
)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if !strings.Contains(result, "pong") {
|
||
|
return errTailscalePingFailed
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func createTailscaleBuildOptions(version string) *dockertest.BuildOptions {
|
||
|
var tailscaleBuildOptions *dockertest.BuildOptions
|
||
|
switch version {
|
||
|
case "head":
|
||
|
tailscaleBuildOptions = &dockertest.BuildOptions{
|
||
|
Dockerfile: "Dockerfile.tailscale-HEAD",
|
||
|
ContextDir: dockerContextPath,
|
||
|
BuildArgs: []docker.BuildArg{},
|
||
|
}
|
||
|
case "unstable":
|
||
|
tailscaleBuildOptions = &dockertest.BuildOptions{
|
||
|
Dockerfile: "Dockerfile.tailscale",
|
||
|
ContextDir: dockerContextPath,
|
||
|
BuildArgs: []docker.BuildArg{
|
||
|
{
|
||
|
Name: "TAILSCALE_VERSION",
|
||
|
Value: "*", // Installs the latest version https://askubuntu.com/a/824926
|
||
|
},
|
||
|
{
|
||
|
Name: "TAILSCALE_CHANNEL",
|
||
|
Value: "unstable",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
default:
|
||
|
tailscaleBuildOptions = &dockertest.BuildOptions{
|
||
|
Dockerfile: "Dockerfile.tailscale",
|
||
|
ContextDir: dockerContextPath,
|
||
|
BuildArgs: []docker.BuildArg{
|
||
|
{
|
||
|
Name: "TAILSCALE_VERSION",
|
||
|
Value: version,
|
||
|
},
|
||
|
{
|
||
|
Name: "TAILSCALE_CHANNEL",
|
||
|
Value: "stable",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return tailscaleBuildOptions
|
||
|
}
|