chore: use single string docker labels
This commit is contained in:
parent
1c88fe55cb
commit
59874ca749
@ -15,6 +15,7 @@ type T = NonNullable<HeadplaneConfig['integration']>['docker'];
|
||||
export default class DockerIntegration extends Integration<T> {
|
||||
private maxAttempts = 10;
|
||||
private client: Client | undefined;
|
||||
private containerId: string | undefined;
|
||||
|
||||
get name() {
|
||||
return 'Docker';
|
||||
@ -56,22 +57,13 @@ export default class DockerIntegration extends Integration<T> {
|
||||
}
|
||||
|
||||
async isAvailable() {
|
||||
// Perform a basic check to see if any of the required properties are set
|
||||
if (
|
||||
this.context.container_name.length === 0 &&
|
||||
!this.context.container_label
|
||||
) {
|
||||
log.error('config', 'Docker container name and label are both empty');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.context.container_name.length > 0 &&
|
||||
!this.context.container_label
|
||||
) {
|
||||
// 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) {
|
||||
log.error(
|
||||
'config',
|
||||
'Docker container name and label are mutually exclusive',
|
||||
'Missing a Docker `container_name` or `container_label`',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@ -127,40 +119,73 @@ export default class DockerIntegration extends Integration<T> {
|
||||
socketPath: url.pathname,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.client === undefined) {
|
||||
log.error('config', 'Failed to create Docker client');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.context.container_name.length === 0) {
|
||||
try {
|
||||
if (this.context.container_label === undefined) {
|
||||
log.error('config', 'Docker container label is not defined');
|
||||
return false;
|
||||
}
|
||||
const containerName = await this.getContainerName(
|
||||
this.context.container_label.name,
|
||||
this.context.container_label.value,
|
||||
);
|
||||
if (containerName.length === 0) {
|
||||
log.error(
|
||||
'config',
|
||||
'No Docker containers found matching label: %s=%s',
|
||||
this.context.container_label.name,
|
||||
this.context.container_label.value,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
this.context.container_name = containerName;
|
||||
} catch (error) {
|
||||
log.error('config', 'Failed to get Docker container name: %s', error);
|
||||
return false;
|
||||
}
|
||||
const qp = new URLSearchParams({
|
||||
filters: JSON.stringify(
|
||||
container_name.length > 0
|
||||
? { name: container_name }
|
||||
: { label: [container_label] },
|
||||
),
|
||||
});
|
||||
|
||||
const res = await this.client.request({
|
||||
method: 'GET',
|
||||
path: `/v1.30/containers/json?${qp.toString()}`,
|
||||
});
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
log.error('config', 'Could not request available Docker containers');
|
||||
log.debug('config', 'Error Details: %o', await res.body.json());
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info('config', 'Using container: %s', this.context.container_name);
|
||||
const data = (await res.body.json()) as DockerContainer[];
|
||||
if (data.length > 1) {
|
||||
if (container_name.length > 0) {
|
||||
log.error(
|
||||
'config',
|
||||
`Found multiple containers with name ${container_name}`,
|
||||
);
|
||||
} else {
|
||||
log.error(
|
||||
'config',
|
||||
`Found multiple containers with label ${container_label}`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.client !== undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
if (container_name.length > 0) {
|
||||
log.error(
|
||||
'config',
|
||||
`No container found with the name ${container_name}`,
|
||||
);
|
||||
} else {
|
||||
log.error(
|
||||
'config',
|
||||
`No container found with the label ${container_label}`,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this.containerId = data[0].Id;
|
||||
log.info(
|
||||
'config',
|
||||
'Using container: %s (ID: %s)',
|
||||
data[0].Names[0],
|
||||
this.containerId,
|
||||
);
|
||||
|
||||
return this.client !== undefined && this.containerId !== undefined;
|
||||
}
|
||||
|
||||
async onConfigChange(client: ApiClient) {
|
||||
@ -175,13 +200,13 @@ export default class DockerIntegration extends Integration<T> {
|
||||
log.debug(
|
||||
'config',
|
||||
'Restarting container: %s (attempt %d)',
|
||||
this.context.container_name,
|
||||
this.containerId,
|
||||
attempts,
|
||||
);
|
||||
|
||||
const response = await this.client.request({
|
||||
method: 'POST',
|
||||
path: `/v1.30/containers/${this.context.container_name}/restart`,
|
||||
path: `/v1.30/containers/${this.containerId}/restart`,
|
||||
});
|
||||
|
||||
if (response.statusCode !== 204) {
|
||||
@ -217,11 +242,7 @@ export default class DockerIntegration extends Integration<T> {
|
||||
continue;
|
||||
}
|
||||
|
||||
log.error(
|
||||
'config',
|
||||
'Missed restart deadline for %s',
|
||||
this.context.container_name,
|
||||
);
|
||||
log.error('config', 'Missed restart deadline for %s', this.containerId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export async function loadIntegration(context: HeadplaneConfig['integration']) {
|
||||
try {
|
||||
const res = await integration.isAvailable();
|
||||
if (!res) {
|
||||
log.error('config', 'Integration %s is not available', integration);
|
||||
log.error('config', 'Integration %s is not available', integration.name);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -46,11 +46,6 @@ const headscaleConfig = type({
|
||||
dns_records_path: 'string?',
|
||||
}).onDeepUndeclaredKey('reject');
|
||||
|
||||
const containerLabel = type({
|
||||
name: 'string',
|
||||
value: 'string',
|
||||
}).optional();
|
||||
|
||||
const agentConfig = type({
|
||||
enabled: stringToBool.default(false),
|
||||
host_name: 'string = "headplane-agent"',
|
||||
@ -64,8 +59,8 @@ const agentConfig = type({
|
||||
const dockerConfig = type({
|
||||
enabled: stringToBool,
|
||||
container_name: 'string = ""',
|
||||
container_label: 'string = "me.tale.headplane.target=headscale"',
|
||||
socket: 'string = "unix:///var/run/docker.sock"',
|
||||
container_label: containerLabel,
|
||||
});
|
||||
|
||||
const kubernetesConfig = type({
|
||||
|
||||
@ -11,7 +11,7 @@ services:
|
||||
image: "headscale/headscale:0.25.1"
|
||||
container_name: "headscale"
|
||||
labels:
|
||||
- com.headplane.selector=headscale
|
||||
me.tale.headplane.target: headscale
|
||||
restart: "unless-stopped"
|
||||
command: "serve"
|
||||
networks:
|
||||
|
||||
@ -84,18 +84,23 @@ integration:
|
||||
# at the same time as any of these and is recommended for the best experience.
|
||||
docker:
|
||||
enabled: false
|
||||
# Preferred method: use container_label to dynamically discover the Headscale container.
|
||||
container_label:
|
||||
name: "com.headplane.selector"
|
||||
value: "headscale"
|
||||
# Optional fallback: directly specify the container name (or ID)
|
||||
# of the container running Headscale
|
||||
|
||||
# By default we check for the presence of a container label (see the docs)
|
||||
# to determine the container to signal when changes are made to DNS settings.
|
||||
container_label: "me.tale.headplane.target=headscale"
|
||||
|
||||
# HOWEVER, you can fallback to a container name if you desire, but this is
|
||||
# not recommended as its brittle and doesn't work with orchestrators that
|
||||
# automatically assign container names.
|
||||
#
|
||||
# If `container_name` is set, it will override any label checks.
|
||||
# container_name: "headscale"
|
||||
|
||||
# The path to the Docker socket (do not change this if you are unsure)
|
||||
# Docker socket paths must start with unix:// or tcp:// and at the moment
|
||||
# https connections are not supported.
|
||||
socket: "unix:///var/run/docker.sock"
|
||||
|
||||
# Please refer to docs/integration/Kubernetes.md for more information
|
||||
# on how to configure the Kubernetes integration. There are requirements in
|
||||
# order to allow Headscale to be controlled by Headplane in a cluster.
|
||||
|
||||
@ -58,6 +58,9 @@ services:
|
||||
container_name: headscale
|
||||
restart: unless-stopped
|
||||
command: serve
|
||||
labels:
|
||||
# This is needed for Headplane to find it and signal it
|
||||
me.tale.headplane.target: headscale
|
||||
ports:
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user