mirror of
https://github.com/tale/headplane.git
synced 2026-08-27 15:37:04 +00:00
feat: overhaul config loading
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { access, constants } from 'node:fs/promises';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { type } from 'arktype';
|
||||
import { Client } from 'undici';
|
||||
import type { RuntimeApiClient } from '~/server/headscale/api/endpoints';
|
||||
import log from '~/utils/log';
|
||||
import type { HeadplaneConfig } from '../schema';
|
||||
import { Integration } from './abstract';
|
||||
|
||||
interface DockerContainer {
|
||||
@@ -11,8 +11,25 @@ interface DockerContainer {
|
||||
Names: string[];
|
||||
}
|
||||
|
||||
type T = NonNullable<HeadplaneConfig['integration']>['docker'];
|
||||
export default class DockerIntegration extends Integration<T> {
|
||||
const configSchema = {
|
||||
full: type({
|
||||
enabled: 'boolean',
|
||||
container_name: 'string?',
|
||||
container_label: 'string = "me.tale.headplane.target=headscale"',
|
||||
socket: 'string = "unix:///var/run/docker.sock"',
|
||||
}),
|
||||
|
||||
partial: type({
|
||||
enabled: 'boolean?',
|
||||
container_name: 'string?',
|
||||
container_label: 'string?',
|
||||
socket: 'string?',
|
||||
}).partial(),
|
||||
};
|
||||
|
||||
export default class DockerIntegration extends Integration<
|
||||
typeof configSchema.full.infer
|
||||
> {
|
||||
private maxAttempts = 10;
|
||||
private client: Client | undefined;
|
||||
private containerId: string | undefined;
|
||||
@@ -21,6 +38,10 @@ export default class DockerIntegration extends Integration<T> {
|
||||
return 'Docker';
|
||||
}
|
||||
|
||||
static get configSchema() {
|
||||
return configSchema;
|
||||
}
|
||||
|
||||
async getContainerName(label: string, value: string): Promise<string> {
|
||||
if (!this.client) {
|
||||
throw new Error('Docker client is not initialized');
|
||||
@@ -60,7 +81,7 @@ export default class DockerIntegration extends Integration<T> {
|
||||
// Basic configuration check, the name overrides the container_label
|
||||
// selector because of legacy support.
|
||||
const { container_name, container_label } = this.context;
|
||||
if (container_name.length === 0 && container_label.length === 0) {
|
||||
if (container_name?.length === 0 && container_label.length === 0) {
|
||||
log.error(
|
||||
'config',
|
||||
'Missing a Docker `container_name` or `container_label`',
|
||||
@@ -127,7 +148,7 @@ export default class DockerIntegration extends Integration<T> {
|
||||
|
||||
const qp = new URLSearchParams({
|
||||
filters: JSON.stringify(
|
||||
container_name.length > 0
|
||||
container_name != null && container_name.length > 0
|
||||
? { name: [container_name] }
|
||||
: { label: [container_label] },
|
||||
),
|
||||
@@ -151,7 +172,7 @@ export default class DockerIntegration extends Integration<T> {
|
||||
|
||||
const data = (await res.body.json()) as DockerContainer[];
|
||||
if (data.length > 1) {
|
||||
if (container_name.length > 0) {
|
||||
if (container_name != null && container_name.length > 0) {
|
||||
log.error(
|
||||
'config',
|
||||
`Found multiple containers with name ${container_name}`,
|
||||
@@ -167,7 +188,7 @@ export default class DockerIntegration extends Integration<T> {
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
if (container_name.length > 0) {
|
||||
if (container_name != null && container_name.length > 0) {
|
||||
log.error(
|
||||
'config',
|
||||
`No container found with the name ${container_name}`,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HeadplaneConfig } from '~/server/config/schema';
|
||||
import log from '~/utils/log';
|
||||
import type { HeadplaneConfig } from '../config-schema';
|
||||
import dockerIntegration from './docker';
|
||||
import kubernetesIntegration from './kubernetes';
|
||||
import procIntegration from './proc';
|
||||
@@ -47,16 +47,16 @@ function getIntegration(integration: HeadplaneConfig['integration']) {
|
||||
|
||||
if (docker?.enabled) {
|
||||
log.info('config', 'Using Docker integration');
|
||||
return new dockerIntegration(integration?.docker);
|
||||
return new dockerIntegration(integration!.docker!);
|
||||
}
|
||||
|
||||
if (k8s?.enabled) {
|
||||
log.info('config', 'Using Kubernetes integration');
|
||||
return new kubernetesIntegration(integration?.kubernetes);
|
||||
return new kubernetesIntegration(integration!.kubernetes!);
|
||||
}
|
||||
|
||||
if (proc?.enabled) {
|
||||
log.info('config', 'Using Proc integration');
|
||||
return new procIntegration(integration?.proc);
|
||||
return new procIntegration(integration!.proc!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { join, resolve } from 'node:path';
|
||||
import { kill } from 'node:process';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { CoreV1Api, KubeConfig } from '@kubernetes/client-node';
|
||||
import { type } from 'arktype';
|
||||
import type { RuntimeApiClient } from '~/server/headscale/api/endpoints';
|
||||
import log from '~/utils/log';
|
||||
import type { HeadplaneConfig } from '../schema';
|
||||
import { Integration } from './abstract';
|
||||
|
||||
// https://github.com/kubernetes-client/javascript/blob/055b83c6504dfd1b2a2d081efd974163c6cbb808/src/config.ts#L40
|
||||
@@ -15,8 +15,23 @@ const svcCaPath = `${svcRoot}/ca.crt`;
|
||||
const svcTokenPath = `${svcRoot}/token`;
|
||||
const svcNamespacePath = `${svcRoot}/namespace`;
|
||||
|
||||
type T = NonNullable<HeadplaneConfig['integration']>['kubernetes'];
|
||||
export default class KubernetesIntegration extends Integration<T> {
|
||||
const configSchema = {
|
||||
full: type({
|
||||
enabled: 'boolean',
|
||||
pod_name: 'string',
|
||||
validate_manifest: 'boolean = true',
|
||||
}),
|
||||
|
||||
partial: type({
|
||||
enabled: 'boolean?',
|
||||
pod_name: 'string?',
|
||||
validate_manifest: 'boolean?',
|
||||
}).partial(),
|
||||
};
|
||||
|
||||
export default class KubernetesIntegration extends Integration<
|
||||
typeof configSchema.full.infer
|
||||
> {
|
||||
private pid: number | undefined;
|
||||
private maxAttempts = 10;
|
||||
|
||||
@@ -24,6 +39,10 @@ export default class KubernetesIntegration extends Integration<T> {
|
||||
return 'Kubernetes (k8s)';
|
||||
}
|
||||
|
||||
static get configSchema() {
|
||||
return configSchema;
|
||||
}
|
||||
|
||||
async isAvailable() {
|
||||
if (platform() !== 'linux') {
|
||||
log.error('config', 'Kubernetes is only available on Linux');
|
||||
|
||||
@@ -3,13 +3,25 @@ import { platform } from 'node:os';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { kill } from 'node:process';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { type } from 'arktype';
|
||||
import type { RuntimeApiClient } from '~/server/headscale/api/endpoints';
|
||||
import log from '~/utils/log';
|
||||
import type { HeadplaneConfig } from '../schema';
|
||||
import type { HeadplaneConfig } from '../config-schema';
|
||||
import { Integration } from './abstract';
|
||||
|
||||
type T = NonNullable<HeadplaneConfig['integration']>['proc'];
|
||||
export default class ProcIntegration extends Integration<T> {
|
||||
const configSchema = {
|
||||
full: type({
|
||||
enabled: 'boolean',
|
||||
}),
|
||||
|
||||
partial: type({
|
||||
enabled: 'boolean?',
|
||||
}).partial(),
|
||||
};
|
||||
|
||||
export default class ProcIntegration extends Integration<
|
||||
typeof configSchema.full.infer
|
||||
> {
|
||||
private pid: number | undefined;
|
||||
private maxAttempts = 10;
|
||||
|
||||
@@ -17,6 +29,10 @@ export default class ProcIntegration extends Integration<T> {
|
||||
return 'Native Linux (/proc)';
|
||||
}
|
||||
|
||||
static get configSchema() {
|
||||
return configSchema;
|
||||
}
|
||||
|
||||
async isAvailable() {
|
||||
if (platform() !== 'linux') {
|
||||
log.error('config', '/proc is only available on Linux');
|
||||
|
||||
Reference in New Issue
Block a user