feat: add dcrouter external gateway sync
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
import { assert, assertEquals } from '@std/assert';
|
||||
|
||||
import { ExternalGatewayManager } from '../ts/classes/external-gateway.ts';
|
||||
import type { IDomain, IService, ISslCertificate } from '../ts/types.ts';
|
||||
|
||||
class FakeDatabase {
|
||||
public settings = new Map<string, string>();
|
||||
public secretSettings = new Map<string, string>();
|
||||
public domains: IDomain[] = [];
|
||||
public certificates = new Map<string, ISslCertificate>();
|
||||
private nextDomainId = 1;
|
||||
|
||||
getSetting(key: string): string | null {
|
||||
return this.settings.get(key) ?? null;
|
||||
}
|
||||
|
||||
setSetting(key: string, value: string): void {
|
||||
this.settings.set(key, value);
|
||||
}
|
||||
|
||||
async getSecretSetting(key: string): Promise<string | null> {
|
||||
return this.secretSettings.get(key) ?? null;
|
||||
}
|
||||
|
||||
getDomainByName(domain: string): IDomain | null {
|
||||
return this.domains.find((entry) => entry.domain === domain) ?? null;
|
||||
}
|
||||
|
||||
createDomain(domain: Omit<IDomain, 'id'>): IDomain {
|
||||
const createdDomain = { ...domain, id: this.nextDomainId++ };
|
||||
this.domains.push(createdDomain);
|
||||
return createdDomain;
|
||||
}
|
||||
|
||||
updateDomain(id: number, updates: Partial<IDomain>): void {
|
||||
const index = this.domains.findIndex((entry) => entry.id === id);
|
||||
if (index === -1) return;
|
||||
this.domains[index] = { ...this.domains[index], ...updates };
|
||||
}
|
||||
|
||||
getDomainsByProvider(provider: NonNullable<IDomain['dnsProvider']>): IDomain[] {
|
||||
return this.domains.filter((entry) => entry.dnsProvider === provider);
|
||||
}
|
||||
|
||||
getSSLCertificate(domain: string): ISslCertificate | null {
|
||||
return this.certificates.get(domain) ?? null;
|
||||
}
|
||||
|
||||
updateSSLCertificate(domain: string, updates: Partial<ISslCertificate>): void {
|
||||
const existing = this.certificates.get(domain);
|
||||
if (!existing) return;
|
||||
this.certificates.set(domain, { ...existing, ...updates });
|
||||
}
|
||||
|
||||
async createSSLCertificate(cert: Omit<ISslCertificate, 'id'>): Promise<ISslCertificate> {
|
||||
const storedCert = { ...cert, id: this.certificates.size + 1 };
|
||||
this.certificates.set(cert.domain, storedCert);
|
||||
return storedCert;
|
||||
}
|
||||
}
|
||||
|
||||
const makeOneboxRef = () => {
|
||||
const database = new FakeDatabase();
|
||||
database.settings.set('dcrouterGatewayUrl', 'https://edge.example.com');
|
||||
database.settings.set('dcrouterWorkHosterId', 'onebox-1');
|
||||
database.secretSettings.set('dcrouterGatewayApiToken', 'dcr-token');
|
||||
|
||||
let reloadCount = 0;
|
||||
return {
|
||||
database,
|
||||
reverseProxy: {
|
||||
reloadCertificates: async () => {
|
||||
reloadCount++;
|
||||
},
|
||||
get reloadCount() {
|
||||
return reloadCount;
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Deno.test('ExternalGatewayManager syncs dcrouter domains into Onebox domains', async () => {
|
||||
const oneboxRef = makeOneboxRef();
|
||||
oneboxRef.database.domains.push({
|
||||
id: 99,
|
||||
domain: 'old.example.com',
|
||||
dnsProvider: 'dcrouter',
|
||||
isObsolete: false,
|
||||
defaultWildcard: true,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
});
|
||||
|
||||
const manager = new ExternalGatewayManager(oneboxRef as any);
|
||||
(manager as any).fireDcRouterRequest = async (method: string) => {
|
||||
assertEquals(method, 'getWorkHosterDomains');
|
||||
return {
|
||||
domains: [
|
||||
{
|
||||
name: 'example.com',
|
||||
capabilities: {
|
||||
canCreateSubdomains: true,
|
||||
canManageDnsRecords: true,
|
||||
canIssueCertificates: true,
|
||||
canHostEmail: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const domains = await manager.syncDomains();
|
||||
|
||||
assertEquals(domains.length, 2);
|
||||
assertEquals(oneboxRef.database.getDomainByName('example.com')?.dnsProvider, 'dcrouter');
|
||||
assertEquals(oneboxRef.database.getDomainByName('example.com')?.defaultWildcard, true);
|
||||
assertEquals(oneboxRef.database.getDomainByName('old.example.com')?.isObsolete, true);
|
||||
});
|
||||
|
||||
Deno.test('ExternalGatewayManager syncs service routes to dcrouter WorkHoster API', async () => {
|
||||
const oneboxRef = makeOneboxRef();
|
||||
oneboxRef.database.settings.set('serverIP', '203.0.113.10');
|
||||
oneboxRef.database.settings.set('httpPort', '8080');
|
||||
|
||||
const service: IService = {
|
||||
id: 1,
|
||||
name: 'hello',
|
||||
image: 'nginx:latest',
|
||||
envVars: {},
|
||||
port: 3000,
|
||||
domain: 'hello.example.com',
|
||||
status: 'running',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
};
|
||||
|
||||
const requests: Array<{ method: string; requestData: Record<string, unknown> }> = [];
|
||||
const manager = new ExternalGatewayManager(oneboxRef as any);
|
||||
(manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
|
||||
requests.push({ method, requestData });
|
||||
if (method === 'exportCertificate') {
|
||||
return { success: false };
|
||||
}
|
||||
return { success: true, action: 'created', routeId: 'route-1' };
|
||||
};
|
||||
|
||||
await manager.syncServiceRoute(service);
|
||||
|
||||
const syncRequest = requests.find((request) => request.method === 'syncWorkAppRoute')!;
|
||||
const route = syncRequest.requestData.route as any;
|
||||
const ownership = syncRequest.requestData.ownership as any;
|
||||
|
||||
assertEquals(ownership, {
|
||||
workHosterType: 'onebox',
|
||||
workHosterId: 'onebox-1',
|
||||
workAppId: 'hello',
|
||||
hostname: 'hello.example.com',
|
||||
});
|
||||
assertEquals(route.match, { ports: [443], domains: ['hello.example.com'] });
|
||||
assertEquals(route.action.targets, [{ host: '203.0.113.10', port: 8080 }]);
|
||||
assertEquals(route.action.tls, { mode: 'terminate', certificate: 'auto' });
|
||||
assertEquals(syncRequest.requestData.enabled, true);
|
||||
});
|
||||
|
||||
Deno.test('ExternalGatewayManager deletes service routes through dcrouter WorkHoster API', async () => {
|
||||
const oneboxRef = makeOneboxRef();
|
||||
const manager = new ExternalGatewayManager(oneboxRef as any);
|
||||
let deleteRequest: Record<string, unknown> | null = null;
|
||||
|
||||
(manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
|
||||
assertEquals(method, 'syncWorkAppRoute');
|
||||
deleteRequest = requestData;
|
||||
return { success: true, action: 'deleted', routeId: 'route-1' };
|
||||
};
|
||||
|
||||
await manager.deleteServiceRoute({
|
||||
id: 1,
|
||||
name: 'hello',
|
||||
domain: 'hello.example.com',
|
||||
});
|
||||
|
||||
assert(deleteRequest);
|
||||
const capturedDeleteRequest = deleteRequest as Record<string, unknown>;
|
||||
assertEquals(capturedDeleteRequest.delete, true);
|
||||
assertEquals((capturedDeleteRequest.ownership as any).hostname, 'hello.example.com');
|
||||
});
|
||||
|
||||
Deno.test('ExternalGatewayManager imports exported dcrouter certificates into Onebox', async () => {
|
||||
const oneboxRef = makeOneboxRef();
|
||||
const manager = new ExternalGatewayManager(oneboxRef as any);
|
||||
(manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
|
||||
assertEquals(method, 'exportCertificate');
|
||||
assertEquals(requestData.domain, 'hello.example.com');
|
||||
return {
|
||||
success: true,
|
||||
cert: {
|
||||
id: 'cert-1',
|
||||
domainName: 'hello.example.com',
|
||||
created: 1,
|
||||
validUntil: 2,
|
||||
privateKey: '-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----',
|
||||
publicKey: '-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----',
|
||||
csr: '',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const imported = await manager.importCertificateForDomain('hello.example.com');
|
||||
|
||||
assert(imported);
|
||||
assertEquals(oneboxRef.database.getSSLCertificate('hello.example.com')?.issuer, 'dcrouter');
|
||||
assertEquals(oneboxRef.reverseProxy.reloadCount, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user