Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0b36774
VERSION.txt: this is v1.84.0 (#16041)
barnstar May 21, 2025
914acdc
ipn: set RouteAll=true by default for new accounts on iOS and Android…
barnstar May 28, 2025
c417248
net/dns: cache dns.Config for reuse when compileConfig fails (#16059)
barnstar May 28, 2025
72ec281
VERSION.txt: this is v1.84.1
barnstar May 29, 2025
f1b8c4a
cmd/containerboot: allow setting --accept-dns via TS_EXTRA_ARGS again…
irbekrm May 30, 2025
2e915f4
cmd/k8s-operator: explicitly set tcp on VIPService port configuration…
ChaosInTheCRD Jun 9, 2025
5f702f4
VERSION.txt: this is v1.84.2 (#16232)
nickoneill Jun 9, 2025
0c3eac4
make the pr mergable, since the vm check only runs on the tailscale repo
Oct 18, 2024
de806ed
add a github action workflow to build operator
Oct 29, 2024
2e449d8
Merge pull request #1 from ninech/configure-urls
gajicdev Oct 30, 2024
e8376f2
build and publish tailscale client, k8s-operator and k8s-nameserver i…
thirdeyenick Nov 27, 2024
1d5b766
allow for custom domain in k8s-nameserver
thirdeyenick Dec 3, 2024
5fb40c3
allow to deploy a dnsConfig resource
thirdeyenick Dec 4, 2024
a3dcdf1
provide workflow for helm chart pushing
thirdeyenick Dec 4, 2024
3c5c7fb
fix chart.yaml workflow
thirdeyenick Dec 4, 2024
a748f5d
use only the tag name instead of the full ref
thirdeyenick Dec 5, 2024
1a63d9f
revert DNSconfig change
thirdeyenick Dec 17, 2024
678bdcb
allow to attach labels to the nameserver pod
thirdeyenick Dec 17, 2024
d628da7
allow to customize nameserver deployment
thirdeyenick Dec 18, 2024
9478d17
allow to customize domain validation
thirdeyenick Mar 20, 2025
82ac199
add flag parsing
thirdeyenick Mar 21, 2025
cb238af
allow to not append a dot for given FQDNs
thirdeyenick Mar 24, 2025
38e0492
allow k8s-nameserver to read dnsrecords directly
thirdeyenick May 12, 2025
9bd1a0f
fix referenced role
thirdeyenick May 14, 2025
090df3f
fix issue on empty configMap
thirdeyenick May 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/build-and-publish-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Publish Dev Operator

on:
push:
tags:
- 'v*.*.*'
- 'v*.*.*-*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and publish k8s-operator image
env:
REPO: ghcr.io/${{ github.repository_owner }}/tailscale-k8s-operator
TAGS: ${{ github.ref_name }}
run: |
echo "Building and publishing k8s-operator to ${REPO} with tags ${TAGS}"
TAGS="${TAGS}" REPOS=${REPO} PLATFORM=${PLATFORM} PUSH=true TARGET=operator ./build_docker.sh
- name: Build and publish nameserver image
env:
REPO: ghcr.io/${{ github.repository_owner }}/tailscale-k8s-nameserver
TAGS: ${{ github.ref_name }}
run: |
echo "Building and publishing k8s-nameserver to ${REPO} with tags ${TAGS}"
TAGS="${TAGS}" REPOS=${REPO} PLATFORM=${PLATFORM} PUSH=true TARGET=k8s-nameserver ./build_docker.sh
- name: Build and publish client image
env:
REPO: ghcr.io/${{ github.repository_owner }}/tailscale
TAGS: ${{ github.ref_name }}
run: |
echo "Building and publishing tailscale client to ${REPO} with tags ${TAGS}"
TAGS="${TAGS}" REPOS=${REPO} PLATFORM=${PLATFORM} PUSH=true TARGET=client ./build_docker.sh
38 changes: 38 additions & 0 deletions .github/workflows/chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: package-helm-chart

on:
push:
tags:
- 'v*.*.*'
- 'v*.*.*-*'
workflow_dispatch:

jobs:
package-and-push-helm-chart:
permissions:
contents: read
packages: write

runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4.2.2

- name: Set environment variables
id: set-variables
run: |
echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"

- name: Login to GitHub Container Registry
uses: docker/login-action@v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}

- name: Build, package and push helm chart
run: |
./tool/go run cmd/k8s-operator/generate/main.go helmcrd
./tool/helm package --app-version=${{ github.ref_name }} --version=${{ github.ref_name }} './cmd/k8s-operator/deploy/chart'
./tool/helm push ./tailscale-operator-${{ github.ref_name }}.tgz oci://${{ steps.set-variables.outputs.REPOSITORY }}/charts
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,6 @@ jobs:
- android
- test
- windows
- vm
- cross
- ios
- wasm
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.83.0
1.84.2
248 changes: 157 additions & 91 deletions cmd/containerboot/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,97 +41,6 @@ import (
"tailscale.com/types/ptr"
)

// testEnv represents the environment needed for a single sub-test so that tests
// can run in parallel.
type testEnv struct {
kube *kubeServer // Fake kube server.
lapi *localAPI // Local TS API server.
d string // Temp dir for the specific test.
argFile string // File with commands test_tailscale{,d}.sh were invoked with.
runningSockPath string // Path to the running tailscaled socket.
localAddrPort int // Port for the containerboot HTTP server.
healthAddrPort int // Port for the (deprecated) containerboot health server.
}

func newTestEnv(t *testing.T) testEnv {
d := t.TempDir()

lapi := localAPI{FSRoot: d}
if err := lapi.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(lapi.Close)

kube := kubeServer{FSRoot: d}
kube.Start(t)
t.Cleanup(kube.Close)

tailscaledConf := &ipn.ConfigVAlpha{AuthKey: ptr.To("foo"), Version: "alpha0"}
serveConf := ipn.ServeConfig{TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}}
egressCfg := egressSvcConfig("foo", "foo.tailnetxyz.ts.net")

dirs := []string{
"var/lib",
"usr/bin",
"tmp",
"dev/net",
"proc/sys/net/ipv4",
"proc/sys/net/ipv6/conf/all",
"etc/tailscaled",
}
for _, path := range dirs {
if err := os.MkdirAll(filepath.Join(d, path), 0700); err != nil {
t.Fatal(err)
}
}
files := map[string][]byte{
"usr/bin/tailscaled": fakeTailscaled,
"usr/bin/tailscale": fakeTailscale,
"usr/bin/iptables": fakeTailscale,
"usr/bin/ip6tables": fakeTailscale,
"dev/net/tun": []byte(""),
"proc/sys/net/ipv4/ip_forward": []byte("0"),
"proc/sys/net/ipv6/conf/all/forwarding": []byte("0"),
"etc/tailscaled/cap-95.hujson": mustJSON(t, tailscaledConf),
"etc/tailscaled/serve-config.json": mustJSON(t, serveConf),
filepath.Join("etc/tailscaled/", egressservices.KeyEgressServices): mustJSON(t, egressCfg),
filepath.Join("etc/tailscaled/", egressservices.KeyHEPPings): []byte("4"),
}
for path, content := range files {
// Making everything executable is a little weird, but the
// stuff that doesn't need to be executable doesn't care if we
// do make it executable.
if err := os.WriteFile(filepath.Join(d, path), content, 0700); err != nil {
t.Fatal(err)
}
}

argFile := filepath.Join(d, "args")
runningSockPath := filepath.Join(d, "tmp/tailscaled.sock")
var localAddrPort, healthAddrPort int
for _, p := range []*int{&localAddrPort, &healthAddrPort} {
ln, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("Failed to open listener: %v", err)
}
if err := ln.Close(); err != nil {
t.Fatalf("Failed to close listener: %v", err)
}
port := ln.Addr().(*net.TCPAddr).Port
*p = port
}

return testEnv{
kube: &kube,
lapi: &lapi,
d: d,
argFile: argFile,
runningSockPath: runningSockPath,
localAddrPort: localAddrPort,
healthAddrPort: healthAddrPort,
}
}

func TestContainerBoot(t *testing.T) {
boot := filepath.Join(t.TempDir(), "containerboot")
if err := exec.Command("go", "build", "-ldflags", "-X main.testSleepDuration=1ms", "-o", boot, "tailscale.com/cmd/containerboot").Run(); err != nil {
Expand Down Expand Up @@ -515,6 +424,37 @@ func TestContainerBoot(t *testing.T) {
},
}
},
"auth_key_once_extra_args_override_dns": func(env *testEnv) testCase {
return testCase{
Env: map[string]string{
"TS_AUTHKEY": "tskey-key",
"TS_AUTH_ONCE": "true",
"TS_ACCEPT_DNS": "false",
"TS_EXTRA_ARGS": "--accept-dns",
},
Phases: []phase{
{
WantCmds: []string{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
},
},
{
Notify: &ipn.Notify{
State: ptr.To(ipn.NeedsLogin),
},
WantCmds: []string{
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=true --authkey=tskey-key",
},
},
{
Notify: runningNotify,
WantCmds: []string{
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock set --accept-dns=true",
},
},
},
}
},
"kube_storage": func(env *testEnv) testCase {
return testCase{
Env: map[string]string{
Expand Down Expand Up @@ -766,6 +706,41 @@ func TestContainerBoot(t *testing.T) {
},
}
},
"extra_args_accept_dns": func(env *testEnv) testCase {
return testCase{
Env: map[string]string{
"TS_EXTRA_ARGS": "--accept-dns",
},
Phases: []phase{
{
WantCmds: []string{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=true",
},
}, {
Notify: runningNotify,
},
},
}
},
"extra_args_accept_dns_overrides_env_var": func(env *testEnv) testCase {
return testCase{
Env: map[string]string{
"TS_ACCEPT_DNS": "true", // Overridden by TS_EXTRA_ARGS.
"TS_EXTRA_ARGS": "--accept-dns=false",
},
Phases: []phase{
{
WantCmds: []string{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false",
},
}, {
Notify: runningNotify,
},
},
}
},
"hostname": func(env *testEnv) testCase {
return testCase{
Env: map[string]string{
Expand Down Expand Up @@ -1604,3 +1579,94 @@ func egressSvcConfig(name, fqdn string) egressservices.Configs {
},
}
}

// testEnv represents the environment needed for a single sub-test so that tests
// can run in parallel.
type testEnv struct {
kube *kubeServer // Fake kube server.
lapi *localAPI // Local TS API server.
d string // Temp dir for the specific test.
argFile string // File with commands test_tailscale{,d}.sh were invoked with.
runningSockPath string // Path to the running tailscaled socket.
localAddrPort int // Port for the containerboot HTTP server.
healthAddrPort int // Port for the (deprecated) containerboot health server.
}

func newTestEnv(t *testing.T) testEnv {
d := t.TempDir()

lapi := localAPI{FSRoot: d}
if err := lapi.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(lapi.Close)

kube := kubeServer{FSRoot: d}
kube.Start(t)
t.Cleanup(kube.Close)

tailscaledConf := &ipn.ConfigVAlpha{AuthKey: ptr.To("foo"), Version: "alpha0"}
serveConf := ipn.ServeConfig{TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}}
egressCfg := egressSvcConfig("foo", "foo.tailnetxyz.ts.net")

dirs := []string{
"var/lib",
"usr/bin",
"tmp",
"dev/net",
"proc/sys/net/ipv4",
"proc/sys/net/ipv6/conf/all",
"etc/tailscaled",
}
for _, path := range dirs {
if err := os.MkdirAll(filepath.Join(d, path), 0700); err != nil {
t.Fatal(err)
}
}
files := map[string][]byte{
"usr/bin/tailscaled": fakeTailscaled,
"usr/bin/tailscale": fakeTailscale,
"usr/bin/iptables": fakeTailscale,
"usr/bin/ip6tables": fakeTailscale,
"dev/net/tun": []byte(""),
"proc/sys/net/ipv4/ip_forward": []byte("0"),
"proc/sys/net/ipv6/conf/all/forwarding": []byte("0"),
"etc/tailscaled/cap-95.hujson": mustJSON(t, tailscaledConf),
"etc/tailscaled/serve-config.json": mustJSON(t, serveConf),
filepath.Join("etc/tailscaled/", egressservices.KeyEgressServices): mustJSON(t, egressCfg),
filepath.Join("etc/tailscaled/", egressservices.KeyHEPPings): []byte("4"),
}
for path, content := range files {
// Making everything executable is a little weird, but the
// stuff that doesn't need to be executable doesn't care if we
// do make it executable.
if err := os.WriteFile(filepath.Join(d, path), content, 0700); err != nil {
t.Fatal(err)
}
}

argFile := filepath.Join(d, "args")
runningSockPath := filepath.Join(d, "tmp/tailscaled.sock")
var localAddrPort, healthAddrPort int
for _, p := range []*int{&localAddrPort, &healthAddrPort} {
ln, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("Failed to open listener: %v", err)
}
if err := ln.Close(); err != nil {
t.Fatalf("Failed to close listener: %v", err)
}
port := ln.Addr().(*net.TCPAddr).Port
*p = port
}

return testEnv{
kube: &kube,
lapi: &lapi,
d: d,
argFile: argFile,
runningSockPath: runningSockPath,
localAddrPort: localAddrPort,
healthAddrPort: healthAddrPort,
}
}
Loading