mirror of
https://github.com/tale/headplane.git
synced 2026-08-13 07:06:51 +00:00
feat: add e2e integration tests and canonical versioning
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
server_url: http://localhost:8080
|
||||
listen_addr: 0.0.0.0:8080
|
||||
metrics_listen_addr: 127.0.0.1:9090
|
||||
grpc_listen_addr: 127.0.0.1:50443
|
||||
grpc_allow_insecure: false
|
||||
noise:
|
||||
private_key_path: /var/lib/headscale/noise_private.key
|
||||
prefixes:
|
||||
v4: 100.64.0.0/10
|
||||
v6: fd7a:115c:a1e0::/48
|
||||
allocation: sequential
|
||||
derp:
|
||||
server:
|
||||
enabled: false
|
||||
region_id: 999
|
||||
region_code: "headscale"
|
||||
region_name: "Headscale Embedded DERP"
|
||||
verify_clients: true
|
||||
stun_listen_addr: "0.0.0.0:3478"
|
||||
private_key_path: /var/lib/headscale/derp_server_private.key
|
||||
automatically_add_embedded_derp_region: true
|
||||
ipv4: 198.51.100.1
|
||||
ipv6: 2001:db8::1
|
||||
urls:
|
||||
- https://controlplane.tailscale.com/derpmap/default
|
||||
paths: []
|
||||
auto_update_enabled: true
|
||||
update_frequency: 3h
|
||||
disable_check_updates: false
|
||||
ephemeral_node_inactivity_timeout: 30m
|
||||
database:
|
||||
type: sqlite
|
||||
debug: false
|
||||
gorm:
|
||||
prepare_stmt: true
|
||||
parameterized_queries: true
|
||||
skip_err_record_not_found: true
|
||||
slow_threshold: 1000
|
||||
sqlite:
|
||||
path: /var/lib/headscale/db.sqlite
|
||||
write_ahead_log: true
|
||||
wal_autocheckpoint: 1000
|
||||
acme_url: https://acme-v02.api.letsencrypt.org/directory
|
||||
acme_email: ""
|
||||
tls_letsencrypt_hostname: ""
|
||||
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
|
||||
tls_letsencrypt_challenge_type: HTTP-01
|
||||
tls_letsencrypt_listen: ":http"
|
||||
tls_cert_path: ""
|
||||
tls_key_path: ""
|
||||
log:
|
||||
level: info
|
||||
format: text
|
||||
policy:
|
||||
mode: database
|
||||
path: ""
|
||||
dns:
|
||||
magic_dns: true
|
||||
base_domain: example.com
|
||||
override_local_dns: true
|
||||
nameservers:
|
||||
global:
|
||||
- 1.1.1.1
|
||||
- 1.0.0.1
|
||||
- 2606:4700:4700::1111
|
||||
- 2606:4700:4700::1001
|
||||
split: {}
|
||||
search_domains: []
|
||||
extra_records: []
|
||||
unix_socket: /var/run/headscale/headscale.sock
|
||||
unix_socket_permission: "0770"
|
||||
logtail:
|
||||
enabled: false
|
||||
randomize_client_port: false
|
||||
@@ -0,0 +1,49 @@
|
||||
import hashes from '~/openapi-operation-hashes.json';
|
||||
import {
|
||||
createHeadscaleInterface,
|
||||
type HeadscaleApiInterface,
|
||||
} from '~/server/headscale/api';
|
||||
import { type HeadscaleEnv, startHeadscale } from './start-headscale';
|
||||
|
||||
export type Version = keyof typeof hashes;
|
||||
export const HS_VERSIONS = Object.keys(hashes) as Version[];
|
||||
|
||||
interface VersionStateEntry {
|
||||
env: HeadscaleEnv;
|
||||
bootstrap: HeadscaleApiInterface;
|
||||
}
|
||||
|
||||
const versionState = new Map<Version, VersionStateEntry>();
|
||||
async function ensureVersion(version: Version) {
|
||||
if (versionState.has(version)) {
|
||||
return versionState.get(version)!;
|
||||
}
|
||||
|
||||
const env = await startHeadscale(version);
|
||||
const bootstrap = await createHeadscaleInterface(env.apiUrl);
|
||||
|
||||
const entry = { env, bootstrap };
|
||||
versionState.set(version, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
export async function getBootstrapClient(version: Version) {
|
||||
const { bootstrap } = await ensureVersion(version);
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
export async function getRuntimeClient(version: Version) {
|
||||
const { env, bootstrap } = await ensureVersion(version);
|
||||
return bootstrap.getRuntimeClient(env.apiKey);
|
||||
}
|
||||
|
||||
export async function stopAllVersions() {
|
||||
for (const { env } of versionState.values()) {
|
||||
await env.container.stop({
|
||||
remove: true,
|
||||
removeVolumes: true,
|
||||
});
|
||||
}
|
||||
|
||||
versionState.clear();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import tc from 'testcontainers';
|
||||
|
||||
export interface HeadscaleEnv {
|
||||
container: tc.StartedTestContainer;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
const cwd = fileURLToPath(import.meta.url);
|
||||
const config = join(cwd, '..', 'config.yaml');
|
||||
|
||||
export async function startHeadscale(version: string): Promise<HeadscaleEnv> {
|
||||
const container = await new tc.GenericContainer(
|
||||
`headscale/headscale:${version}`,
|
||||
)
|
||||
.withExposedPorts(8080)
|
||||
.withWaitStrategy(
|
||||
tc.Wait.forHttp('/health', 8080)
|
||||
.withStartupTimeout(30_000)
|
||||
.forStatusCode(200),
|
||||
)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: config,
|
||||
target: '/etc/headscale/config.yaml',
|
||||
},
|
||||
])
|
||||
.withCommand(['serve'])
|
||||
.start();
|
||||
|
||||
const host = container.getHost();
|
||||
const port = container.getMappedPort(8080);
|
||||
const apiUrl = `http://${host}:${port}`;
|
||||
|
||||
const exec = await container.exec([
|
||||
'headscale',
|
||||
'apikeys',
|
||||
'create',
|
||||
'-o',
|
||||
'json',
|
||||
]);
|
||||
|
||||
if (exec.exitCode !== 0) {
|
||||
throw new Error(`headscale apikeys create failed:\n${exec.stderr}`);
|
||||
}
|
||||
|
||||
const apiKey = JSON.parse(exec.stdout.toString());
|
||||
return { container, apiUrl, apiKey };
|
||||
}
|
||||
|
||||
// await startHeadscale('0.26.0');
|
||||
@@ -0,0 +1,11 @@
|
||||
import { getBootstrapClient, HS_VERSIONS, stopAllVersions } from './env';
|
||||
|
||||
export async function setup() {
|
||||
for (const version of HS_VERSIONS) {
|
||||
await getBootstrapClient(version);
|
||||
}
|
||||
}
|
||||
|
||||
export async function teardown() {
|
||||
await stopAllVersions();
|
||||
}
|
||||
Reference in New Issue
Block a user