feat: sync workload routes to external gateway
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import * as plugins from './coreflow.plugins.js';
|
import * as plugins from './coreflow.plugins.js';
|
||||||
import { logger } from './coreflow.logging.js';
|
import { logger } from './coreflow.logging.js';
|
||||||
import { Coreflow } from './coreflow.classes.coreflow.js';
|
import { Coreflow } from './coreflow.classes.coreflow.js';
|
||||||
|
import type { IExternalGatewayConfig } from './coreflow.connector.externalgateway.js';
|
||||||
|
|
||||||
export class ClusterManager {
|
export class ClusterManager {
|
||||||
public coreflowRef: Coreflow;
|
public coreflowRef: Coreflow;
|
||||||
@@ -408,8 +409,11 @@ export class ClusterManager {
|
|||||||
* update traffic routing
|
* update traffic routing
|
||||||
*/
|
*/
|
||||||
public async updateTrafficRouting(
|
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 services = await this.coreflowRef.dockerHost.listServices();
|
||||||
const webGatewayNetwork = await this.coreflowRef.dockerHost.getNetworkByName(
|
const webGatewayNetwork = await this.coreflowRef.dockerHost.getNetworkByName(
|
||||||
this.commonDockerData.networkNames.sznWebgateway,
|
this.commonDockerData.networkNames.sznWebgateway,
|
||||||
@@ -420,14 +424,20 @@ export class ClusterManager {
|
|||||||
const reverseProxyConfigs: plugins.servezoneInterfaces.data.IReverseProxyConfig[] = [];
|
const reverseProxyConfigs: plugins.servezoneInterfaces.data.IReverseProxyConfig[] = [];
|
||||||
|
|
||||||
const pushProxyConfig = async (
|
const pushProxyConfig = async (
|
||||||
serviceNameArg: string,
|
workloadServiceArg: plugins.servezoneInterfaces.data.IService,
|
||||||
hostNameArg: string,
|
hostNameArg: string,
|
||||||
containerDestinationIp: string,
|
containerDestinationIp: string,
|
||||||
webDestinationPort: string,
|
webDestinationPort: string,
|
||||||
) => {
|
) => {
|
||||||
logger.log('ok', `trying to obtain a certificate for ${hostNameArg}`);
|
logger.log('ok', `trying to obtain a certificate for ${hostNameArg}`);
|
||||||
const certificate =
|
let certificate = await this.coreflowRef.externalGatewayConnector.exportCertificateForDomain(
|
||||||
await this.coreflowRef.cloudlyConnector.getCertificateForDomainFromCloudly(hostNameArg);
|
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({
|
reverseProxyConfigs.push({
|
||||||
destinationIps: [containerDestinationIp],
|
destinationIps: [containerDestinationIp],
|
||||||
destinationPorts: [Number(webDestinationPort)],
|
destinationPorts: [Number(webDestinationPort)],
|
||||||
@@ -437,8 +447,15 @@ export class ClusterManager {
|
|||||||
});
|
});
|
||||||
logger.log(
|
logger.log(
|
||||||
'success',
|
'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!`);
|
logger.log('info', `Found ${services.length} services!`);
|
||||||
@@ -473,7 +490,7 @@ export class ClusterManager {
|
|||||||
const webDestinationPort: string = workloadConfig.data.ports.web.toString();
|
const webDestinationPort: string = workloadConfig.data.ports.web.toString();
|
||||||
for (const hostName of hostNames) {
|
for (const hostName of hostNames) {
|
||||||
await pushProxyConfig(
|
await pushProxyConfig(
|
||||||
workloadConfig.data.name,
|
workloadConfig,
|
||||||
hostName,
|
hostName,
|
||||||
containerDestinationIp,
|
containerDestinationIp,
|
||||||
webDestinationPort,
|
webDestinationPort,
|
||||||
@@ -485,7 +502,7 @@ export class ClusterManager {
|
|||||||
const customDomainKeys = Object.keys(workloadConfig.data.ports.custom);
|
const customDomainKeys = Object.keys(workloadConfig.data.ports.custom);
|
||||||
for (const customDomainKey of customDomainKeys) {
|
for (const customDomainKey of customDomainKeys) {
|
||||||
await pushProxyConfig(
|
await pushProxyConfig(
|
||||||
workloadConfig.data.name,
|
workloadConfig,
|
||||||
customDomainKey,
|
customDomainKey,
|
||||||
containerDestinationIp,
|
containerDestinationIp,
|
||||||
workloadConfig.data.ports.custom[customDomainKey],
|
workloadConfig.data.ports.custom[customDomainKey],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { CloudlyConnector } from './coreflow.connector.cloudlyconnector.js';
|
|||||||
import { ClusterManager } from './coreflow.classes.clustermanager.js';
|
import { ClusterManager } from './coreflow.classes.clustermanager.js';
|
||||||
import { CoreflowTaskmanager } from './coreflow.classes.taskmanager.js';
|
import { CoreflowTaskmanager } from './coreflow.classes.taskmanager.js';
|
||||||
import { CoretrafficConnector } from './coreflow.connector.coretrafficconnector.js';
|
import { CoretrafficConnector } from './coreflow.connector.coretrafficconnector.js';
|
||||||
|
import { ExternalGatewayConnector } from './coreflow.connector.externalgateway.js';
|
||||||
import { InternalServer } from './coreflow.classes.internalserver.js';
|
import { InternalServer } from './coreflow.classes.internalserver.js';
|
||||||
import { PlatformManager } from './coreflow.classes.platformmanager.js';
|
import { PlatformManager } from './coreflow.classes.platformmanager.js';
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ export class Coreflow {
|
|||||||
public dockerHost: plugins.docker.DockerHost;
|
public dockerHost: plugins.docker.DockerHost;
|
||||||
public cloudlyConnector: CloudlyConnector;
|
public cloudlyConnector: CloudlyConnector;
|
||||||
public corechatConnector: CoretrafficConnector;
|
public corechatConnector: CoretrafficConnector;
|
||||||
|
public externalGatewayConnector: ExternalGatewayConnector;
|
||||||
public clusterManager: ClusterManager;
|
public clusterManager: ClusterManager;
|
||||||
public platformManager: PlatformManager;
|
public platformManager: PlatformManager;
|
||||||
public taskManager: CoreflowTaskmanager;
|
public taskManager: CoreflowTaskmanager;
|
||||||
@@ -28,6 +30,7 @@ export class Coreflow {
|
|||||||
this.internalServer = new InternalServer(this);
|
this.internalServer = new InternalServer(this);
|
||||||
this.cloudlyConnector = new CloudlyConnector(this);
|
this.cloudlyConnector = new CloudlyConnector(this);
|
||||||
this.corechatConnector = new CoretrafficConnector(this);
|
this.corechatConnector = new CoretrafficConnector(this);
|
||||||
|
this.externalGatewayConnector = new ExternalGatewayConnector(this);
|
||||||
this.clusterManager = new ClusterManager(this);
|
this.clusterManager = new ClusterManager(this);
|
||||||
this.platformManager = new PlatformManager(this);
|
this.platformManager = new PlatformManager(this);
|
||||||
this.taskManager = new CoreflowTaskmanager(this);
|
this.taskManager = new CoreflowTaskmanager(this);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export class CoreflowTaskmanager {
|
|||||||
bufferMax: 1,
|
bufferMax: 1,
|
||||||
taskFunction: async () => {
|
taskFunction: async () => {
|
||||||
logger.log('info', 'now updating traffic routing');
|
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);
|
await this.coreflowRef.clusterManager.updateTrafficRouting(config);
|
||||||
logger.log('success', 'traffic routing completed!');
|
logger.log('success', 'traffic routing completed!');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -43,8 +43,14 @@ export class CloudlyConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getConfigFromCloudly(): Promise<plugins.servezoneInterfaces.data.ICluster> {
|
public async getConfigFromCloudly(): Promise<plugins.servezoneInterfaces.data.ICluster> {
|
||||||
const config = await this.cloudlyApiClient.getClusterConfigFromCloudlyByIdentity(this.identity);
|
const config = await this.getClusterConfigPayloadFromCloudly();
|
||||||
return config as unknown as plugins.servezoneInterfaces.data.ICluster;
|
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(
|
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