n8n/packages/testing/containers/services/cloudflared.ts
Declan Carroll 9017b745fa
ci: Add external flag for local dev mode (#25604)
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Danny Martini <danny@n8n.io>
2026-02-19 16:01:08 +00:00

96 lines
2.6 KiB
TypeScript

import { GenericContainer, Wait } from 'testcontainers';
import { createSilentLogConsumer } from '../helpers/utils';
import { TEST_CONTAINER_IMAGES } from '../test-containers';
import { EXTERNAL_HOST, type Service, type ServiceResult, type StartContext } from './types';
export interface CloudflaredMeta {
publicUrl: string;
proxyHops: number;
}
export type CloudflaredResult = ServiceResult<CloudflaredMeta>;
const METRICS_PORT = 2000;
function getTunnelTarget(ctx: StartContext): string {
if (ctx.external) {
return `${EXTERNAL_HOST}:5678`;
}
if (ctx.needsLoadBalancer) {
return `${ctx.projectName}-caddy-lb:80`;
}
return `${ctx.projectName}-n8n:5678`;
}
export const cloudflared: Service<CloudflaredResult> = {
description: 'Cloudflare Tunnel',
dependsOn: ['loadBalancer'],
shouldStart: (ctx) => ctx.config.services?.includes('cloudflared') ?? false,
getOptions(ctx) {
const proxyHops = ctx.needsLoadBalancer ? 2 : 1;
return { tunnelTarget: getTunnelTarget(ctx), proxyHops };
},
env(result) {
return {
WEBHOOK_URL: result.meta.publicUrl,
N8N_PROXY_HOPS: String(result.meta.proxyHops),
};
},
async start(
network,
projectName,
config?: unknown,
ctx?: StartContext,
): Promise<CloudflaredResult> {
const { tunnelTarget, proxyHops } = config as { tunnelTarget: string; proxyHops: number };
const { consumer, throwWithLogs } = createSilentLogConsumer();
try {
let builder = new GenericContainer(TEST_CONTAINER_IMAGES.cloudflared)
.withNetwork(network)
.withNetworkAliases('cloudflared')
.withName(`${projectName}-cloudflared`)
.withExposedPorts(METRICS_PORT)
.withCommand([
'tunnel',
'--url',
`http://${tunnelTarget}`,
'--metrics',
`0.0.0.0:${METRICS_PORT}`,
'--no-autoupdate',
])
.withWaitStrategy(Wait.forHttp('/quicktunnel', METRICS_PORT).forStatusCode(200))
.withLabels({
'com.docker.compose.project': projectName,
'com.docker.compose.service': 'cloudflared',
})
.withReuse()
.withLogConsumer(consumer);
// On Linux, host.docker.internal is not available without explicit mapping
if (ctx?.external) {
builder = builder.withExtraHosts([{ host: EXTERNAL_HOST, ipAddress: 'host-gateway' }]);
}
const container = await builder.start();
const hostPort = container.getMappedPort(METRICS_PORT);
const response = await fetch(`http://${container.getHost()}:${hostPort}/quicktunnel`);
const data = (await response.json()) as { hostname: string };
const publicUrl = `https://${data.hostname}`;
return {
container,
meta: { publicUrl, proxyHops },
};
} catch (error) {
return throwWithLogs(error);
}
},
};