feat: sync workload routes to external gateway
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as plugins from './coreflow.plugins.js';
|
||||
import { logger } from './coreflow.logging.js';
|
||||
import { Coreflow } from './coreflow.classes.coreflow.js';
|
||||
import type { IExternalGatewayConfig } from './coreflow.connector.externalgateway.js';
|
||||
|
||||
export class ClusterManager {
|
||||
public coreflowRef: Coreflow;
|
||||
@@ -408,8 +409,11 @@ export class ClusterManager {
|
||||
* update traffic routing
|
||||
*/
|
||||
public async updateTrafficRouting(
|
||||
_clusterConfigArg: plugins.servezoneInterfaces.data.ICluster,
|
||||
clusterConfigArg: plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig['response'] & {
|
||||
externalGateway?: IExternalGatewayConfig;
|
||||
},
|
||||
) {
|
||||
const externalGatewayConfig = clusterConfigArg.externalGateway;
|
||||
const services = await this.coreflowRef.dockerHost.listServices();
|
||||
const webGatewayNetwork = await this.coreflowRef.dockerHost.getNetworkByName(
|
||||
this.commonDockerData.networkNames.sznWebgateway,
|
||||
@@ -420,14 +424,20 @@ export class ClusterManager {
|
||||
const reverseProxyConfigs: plugins.servezoneInterfaces.data.IReverseProxyConfig[] = [];
|
||||
|
||||
const pushProxyConfig = async (
|
||||
serviceNameArg: string,
|
||||
workloadServiceArg: plugins.servezoneInterfaces.data.IService,
|
||||
hostNameArg: string,
|
||||
containerDestinationIp: string,
|
||||
webDestinationPort: string,
|
||||
) => {
|
||||
logger.log('ok', `trying to obtain a certificate for ${hostNameArg}`);
|
||||
const certificate =
|
||||
await this.coreflowRef.cloudlyConnector.getCertificateForDomainFromCloudly(hostNameArg);
|
||||
let certificate = await this.coreflowRef.externalGatewayConnector.exportCertificateForDomain(
|
||||
externalGatewayConfig,
|
||||
hostNameArg,
|
||||
).catch((error) => {
|
||||
logger.log('warn', `external gateway certificate export failed for ${hostNameArg}: ${(error as Error).message}`);
|
||||
return undefined;
|
||||
});
|
||||
certificate = certificate || await this.coreflowRef.cloudlyConnector.getCertificateForDomainFromCloudly(hostNameArg);
|
||||
reverseProxyConfigs.push({
|
||||
destinationIps: [containerDestinationIp],
|
||||
destinationPorts: [Number(webDestinationPort)],
|
||||
@@ -437,8 +447,15 @@ export class ClusterManager {
|
||||
});
|
||||
logger.log(
|
||||
'success',
|
||||
`pushed routing config for ${hostNameArg} on workload service ${serviceNameArg}`,
|
||||
`pushed routing config for ${hostNameArg} on workload service ${workloadServiceArg.data.name}`,
|
||||
);
|
||||
await this.coreflowRef.externalGatewayConnector.syncWorkAppRoute({
|
||||
config: externalGatewayConfig,
|
||||
service: workloadServiceArg,
|
||||
hostname: hostNameArg,
|
||||
}).catch((error) => {
|
||||
logger.log('warn', `external gateway route sync failed for ${hostNameArg}: ${(error as Error).message}`);
|
||||
});
|
||||
};
|
||||
|
||||
logger.log('info', `Found ${services.length} services!`);
|
||||
@@ -473,7 +490,7 @@ export class ClusterManager {
|
||||
const webDestinationPort: string = workloadConfig.data.ports.web.toString();
|
||||
for (const hostName of hostNames) {
|
||||
await pushProxyConfig(
|
||||
workloadConfig.data.name,
|
||||
workloadConfig,
|
||||
hostName,
|
||||
containerDestinationIp,
|
||||
webDestinationPort,
|
||||
@@ -485,7 +502,7 @@ export class ClusterManager {
|
||||
const customDomainKeys = Object.keys(workloadConfig.data.ports.custom);
|
||||
for (const customDomainKey of customDomainKeys) {
|
||||
await pushProxyConfig(
|
||||
workloadConfig.data.name,
|
||||
workloadConfig,
|
||||
customDomainKey,
|
||||
containerDestinationIp,
|
||||
workloadConfig.data.ports.custom[customDomainKey],
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CloudlyConnector } from './coreflow.connector.cloudlyconnector.js';
|
||||
import { ClusterManager } from './coreflow.classes.clustermanager.js';
|
||||
import { CoreflowTaskmanager } from './coreflow.classes.taskmanager.js';
|
||||
import { CoretrafficConnector } from './coreflow.connector.coretrafficconnector.js';
|
||||
import { ExternalGatewayConnector } from './coreflow.connector.externalgateway.js';
|
||||
import { InternalServer } from './coreflow.classes.internalserver.js';
|
||||
import { PlatformManager } from './coreflow.classes.platformmanager.js';
|
||||
|
||||
@@ -18,6 +19,7 @@ export class Coreflow {
|
||||
public dockerHost: plugins.docker.DockerHost;
|
||||
public cloudlyConnector: CloudlyConnector;
|
||||
public corechatConnector: CoretrafficConnector;
|
||||
public externalGatewayConnector: ExternalGatewayConnector;
|
||||
public clusterManager: ClusterManager;
|
||||
public platformManager: PlatformManager;
|
||||
public taskManager: CoreflowTaskmanager;
|
||||
@@ -28,6 +30,7 @@ export class Coreflow {
|
||||
this.internalServer = new InternalServer(this);
|
||||
this.cloudlyConnector = new CloudlyConnector(this);
|
||||
this.corechatConnector = new CoretrafficConnector(this);
|
||||
this.externalGatewayConnector = new ExternalGatewayConnector(this);
|
||||
this.clusterManager = new ClusterManager(this);
|
||||
this.platformManager = new PlatformManager(this);
|
||||
this.taskManager = new CoreflowTaskmanager(this);
|
||||
|
||||
@@ -55,7 +55,7 @@ export class CoreflowTaskmanager {
|
||||
bufferMax: 1,
|
||||
taskFunction: async () => {
|
||||
logger.log('info', 'now updating traffic routing');
|
||||
const config = await this.coreflowRef.cloudlyConnector.getConfigFromCloudly();
|
||||
const config = await this.coreflowRef.cloudlyConnector.getClusterConfigPayloadFromCloudly();
|
||||
await this.coreflowRef.clusterManager.updateTrafficRouting(config);
|
||||
logger.log('success', 'traffic routing completed!');
|
||||
},
|
||||
|
||||
@@ -43,8 +43,14 @@ export class CloudlyConnector {
|
||||
}
|
||||
|
||||
public async getConfigFromCloudly(): Promise<plugins.servezoneInterfaces.data.ICluster> {
|
||||
const config = await this.cloudlyApiClient.getClusterConfigFromCloudlyByIdentity(this.identity);
|
||||
return config as unknown as plugins.servezoneInterfaces.data.ICluster;
|
||||
const config = await this.getClusterConfigPayloadFromCloudly();
|
||||
return config.configData as unknown as plugins.servezoneInterfaces.data.ICluster;
|
||||
}
|
||||
|
||||
public async getClusterConfigPayloadFromCloudly(): Promise<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig['response'] & {
|
||||
externalGateway?: import('./coreflow.connector.externalgateway.js').IExternalGatewayConfig;
|
||||
}> {
|
||||
return await this.cloudlyApiClient.getClusterConfigFromCloudlyByIdentity(this.identity) as any;
|
||||
}
|
||||
|
||||
public async getCertificateForDomainFromCloudly(
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import * as plugins from './coreflow.plugins.js';
|
||||
import { Coreflow } from './coreflow.classes.coreflow.js';
|
||||
import { logger } from './coreflow.logging.js';
|
||||
|
||||
export interface IExternalGatewayConfig {
|
||||
url: string;
|
||||
apiToken: string;
|
||||
workHosterType: 'cloudly';
|
||||
workHosterId: string;
|
||||
targetHost?: string;
|
||||
targetPort?: number;
|
||||
}
|
||||
|
||||
interface IWorkAppRouteSyncResult {
|
||||
success: boolean;
|
||||
action?: 'created' | 'updated' | 'deleted' | 'unchanged';
|
||||
routeId?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface IDcRouterCertificateExport {
|
||||
success: boolean;
|
||||
cert?: {
|
||||
id: string;
|
||||
domainName: string;
|
||||
created: number;
|
||||
validUntil: number;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
csr: string;
|
||||
};
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class ExternalGatewayConnector {
|
||||
constructor(public coreflowRef: Coreflow) {}
|
||||
|
||||
public isConfigured(configArg?: IExternalGatewayConfig): configArg is IExternalGatewayConfig {
|
||||
return Boolean(
|
||||
configArg?.url
|
||||
&& configArg.apiToken
|
||||
&& configArg.workHosterId
|
||||
&& configArg.targetHost
|
||||
&& configArg.targetPort,
|
||||
);
|
||||
}
|
||||
|
||||
public async syncWorkAppRoute(optionsArg: {
|
||||
config: IExternalGatewayConfig | undefined;
|
||||
service: plugins.servezoneInterfaces.data.IService;
|
||||
hostname: string;
|
||||
}): Promise<void> {
|
||||
const config = optionsArg.config;
|
||||
if (!this.isConfigured(config)) return;
|
||||
|
||||
const result = await this.fireDcRouterRequest<IWorkAppRouteSyncResult>(
|
||||
config,
|
||||
'syncWorkAppRoute',
|
||||
{
|
||||
ownership: {
|
||||
workHosterType: 'cloudly',
|
||||
workHosterId: config.workHosterId,
|
||||
workAppId: optionsArg.service.id || optionsArg.service.data.name,
|
||||
hostname: optionsArg.hostname,
|
||||
},
|
||||
route: {
|
||||
name: this.routeName(optionsArg.hostname),
|
||||
match: {
|
||||
ports: [443],
|
||||
domains: [optionsArg.hostname],
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
targets: [{ host: config.targetHost!, port: config.targetPort! }],
|
||||
tls: {
|
||||
mode: 'terminate',
|
||||
certificate: 'auto',
|
||||
},
|
||||
websocket: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || `dcrouter route sync failed for ${optionsArg.hostname}`);
|
||||
}
|
||||
|
||||
logger.log('success', `external gateway route ${result.action || 'synced'} for ${optionsArg.hostname}`);
|
||||
}
|
||||
|
||||
public async exportCertificateForDomain(
|
||||
configArg: IExternalGatewayConfig | undefined,
|
||||
hostnameArg: string,
|
||||
): Promise<plugins.tsclass.network.ICert | undefined> {
|
||||
if (!configArg?.url || !configArg.apiToken) return undefined;
|
||||
|
||||
const result = await this.fireDcRouterRequest<IDcRouterCertificateExport>(
|
||||
configArg,
|
||||
'exportCertificate',
|
||||
{ domain: hostnameArg },
|
||||
);
|
||||
|
||||
if (!result.success || !result.cert) return undefined;
|
||||
return {
|
||||
id: result.cert.id,
|
||||
domainName: result.cert.domainName,
|
||||
created: result.cert.created,
|
||||
validUntil: result.cert.validUntil,
|
||||
privateKey: result.cert.privateKey,
|
||||
publicKey: result.cert.publicKey,
|
||||
csr: result.cert.csr,
|
||||
};
|
||||
}
|
||||
|
||||
private routeName(hostnameArg: string): string {
|
||||
return `cloudly-${hostnameArg.replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-|-$/g, '')}`;
|
||||
}
|
||||
|
||||
private async fireDcRouterRequest<TResponse>(
|
||||
configArg: IExternalGatewayConfig,
|
||||
methodArg: string,
|
||||
requestDataArg: Record<string, unknown>,
|
||||
): Promise<TResponse> {
|
||||
const typedRequest = new plugins.typedrequest.TypedRequest<any>(
|
||||
`${configArg.url.replace(/\/+$/, '')}/typedrequest`,
|
||||
methodArg,
|
||||
);
|
||||
return await typedRequest.fire({
|
||||
...requestDataArg,
|
||||
apiToken: configArg.apiToken,
|
||||
}) as TResponse;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user