import * as plugins from './plugins.js'; export type TClientType = 'api' | 'ci' | 'coreflow' | 'cli' | 'serverconfig'; import { Image } from './classes.image.js'; import { Service } from './classes.service.js'; import { Cluster } from './classes.cluster.js'; import { SecretBundle } from './classes.secretbundle.js'; import { SecretGroup } from './classes.secretgroup.js'; import { ExternalRegistry } from './classes.externalregistry.js'; export class CloudlyApiClient { private cloudlyUrl: string; private registerAs: string; public typedrouter = new plugins.typedrequest.TypedRouter(); public typedsocketClient: plugins.typedsocket.TypedSocket; // Subjects public configUpdateSubject = new plugins.smartrx.rxjs.Subject< plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig['request'] >(); public serverActionSubject = new plugins.smartrx.rxjs.Subject< plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request'] >(); constructor(optionsArg: { registerAs: TClientType; cloudlyUrl?: string; }) { this.registerAs = optionsArg.registerAs; this.cloudlyUrl = optionsArg?.cloudlyUrl || process.env.CLOUDLY_URL || 'https://cloudly.layer.io:443'; console.log( `creating LoleCloudlyClient: registering as ${this.registerAs} and target url ${this.cloudlyUrl}` ); this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler('pushClusterConfig', async (dataArg) => { this.configUpdateSubject.next(dataArg); return {}; }) ); this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler('triggerServerAction', async (dataArg) => { this.serverActionSubject.next(dataArg); return { actionConfirmed: true, }; }) ); } // Helper: resolve HTTP typedrequest endpoint private get httpEndpoint() { const base = (this.cloudlyUrl || '').replace(/\/$/, ''); return `${base}/typedrequest`; } // Helper: choose transport (WS if available, else HTTP) private createWsRequest(operation: string) { return this.typedsocketClient?.createTypedRequest(operation); } private createHttpRequest(operation: string) { return new plugins.typedrequest.TypedRequest(this.httpEndpoint, operation); } public async start() { this.typedsocketClient = await plugins.typedsocket.TypedSocket.createClient( this.typedrouter, this.cloudlyUrl ); console.log( `CloudlyClient connected to cloudly at ${this.cloudlyUrl}. Remember to get an identity.` ); } public async stop() { await this.typedsocketClient.stop(); } public identity: plugins.servezoneInterfaces.data.IIdentity; public async getIdentityByToken( token: string, optionsArg?: { tagConnection?: boolean; statefullIdentity?: boolean; } ): Promise { optionsArg = Object.assign({}, { tagConnection: false, statefullIdentity: true, }, optionsArg); const identityRequest = this.typedsocketClient.createTypedRequest( 'getIdentityByToken' ); console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${token}`); const response = await identityRequest.fire({ token: token, }); console.log('got identity response'); const identity = response.identity; if (optionsArg.tagConnection) { this.typedsocketClient.addTag('identity', identity); } if (optionsArg.statefullIdentity) { this.identity = identity; } return identity; } /** * will use statefull identity by default */ public async getClusterConfigFromCloudlyByIdentity( identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity ): Promise { const clusterConfigRequest = this.typedsocketClient.createTypedRequest( 'getClusterConfig' ); const response = await clusterConfigRequest.fire({ identity: identityArg, }); return response.configData; } /** * will use statefull identity by default */ public async getServerConfigFromCloudlyByIdentity( identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity ): Promise { const serverConfigRequest = this.typedsocketClient.createTypedRequest( 'getServerConfig' ); const response = await serverConfigRequest.fire({ identity: identityArg, serverId: '', // TODO: get server id here }); return response.configData; } /** * gets a certificate for a domain used by a service */ public async getCertificateForDomain(optionsArg: { domainName: string; type: plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain['request']['type']; identity?: plugins.servezoneInterfaces.data.IIdentity; }): Promise { optionsArg.identity = optionsArg.identity || this.identity; if (!optionsArg.identity) { throw new Error('identity is required. Either provide one or login first.'); } const typedCertificateRequest = this.typedsocketClient.createTypedRequest( 'getCertificateForDomain' ); const typedResponse = await typedCertificateRequest.fire({ identity: this.identity, // do proper auth here domainName: optionsArg.domainName, type: optionsArg.type, }); return typedResponse.certificate; } public externalRegistry = { // ExternalRegistry getRegistryById: async (registryNameArg: string) => { return ExternalRegistry.getExternalRegistryById(this, registryNameArg); }, getRegistries: async () => { return ExternalRegistry.getExternalRegistries(this); }, createRegistry: async (optionsArg: Parameters[1]) => { return ExternalRegistry.createExternalRegistry(this, optionsArg); }, verifyRegistry: async (registryId: string): Promise<{ success: boolean; message: string; registry?: ExternalRegistry }> => { const op = 'verifyExternalRegistry'; const wsReq = this.createWsRequest(op); const payload = { identity: this.identity, registryId } as any; const resp = wsReq ? await wsReq.fire(payload) : await this.createHttpRequest(op).fire(payload); let registryInstance: ExternalRegistry | undefined; if (resp.registry) { registryInstance = new ExternalRegistry(this); Object.assign(registryInstance, resp.registry); } return { success: resp.success, message: resp.message, registry: registryInstance }; } } // Auth helpers public async loginWithUsernameAndPassword(username: string, password: string): Promise { const op = 'adminLoginWithUsernameAndPassword'; // Login endpoint is exposed via HTTP typedrequest const httpReq = this.createHttpRequest(op); const response = await httpReq.fire({ username, password }); this.identity = response.identity; // If WS connection is available, tag it with identity for server-side guards if (this.typedsocketClient) { try { this.typedsocketClient.addTag('identity', this.identity); } catch {} } return this.identity; } public image = { // Images getImageById: async (imageIdArg: string) => { return Image.getImageById(this, imageIdArg); }, getImages: async () => { return Image.getImages(this); }, createImage: async (optionsArg: Parameters[1]) => { return Image.createImage(this, optionsArg); }, deleteImage: async (imageId: string): Promise => { const op = 'deleteImage'; const payload = { identity: this.identity, imageId } as any; const wsReq = this.createWsRequest(op); if (wsReq) { await wsReq.fire(payload); return; } await this.createHttpRequest(op).fire(payload); } } public services = { // Services getServiceById: async (serviceIdArg: string) => { return Service.getServiceById(this, serviceIdArg); }, getServices: async () => { return Service.getServices(this); }, createService: async (optionsArg: Parameters[1]) => { return Service.createService(this, optionsArg); }, updateService: async (serviceId: string, serviceData: plugins.servezoneInterfaces.data.IService['data']): Promise<{ service: plugins.servezoneInterfaces.data.IService }> => { const op = 'updateService'; const payload = { identity: this.identity, serviceId, serviceData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteService: async (serviceId: string): Promise => { const op = 'deleteServiceById'; const payload = { identity: this.identity, serviceId } as any; const wsReq = this.createWsRequest(op); if (wsReq) { await wsReq.fire(payload); return; } await this.createHttpRequest(op).fire(payload); } } public cluster = { // Clusters getClusterById: async (clusterIdArg: string) => { return Cluster.getClusterById(this, clusterIdArg); }, getClusters: async () => { return Cluster.getClusters(this); }, createCluster: async (optionsArg: Parameters[1]) => { return Cluster.createCluster(this, optionsArg); }, createClusterAdvanced: async (clusterName: string, setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean') => { const op = 'createCluster'; const payload: any = { identity: this.identity, clusterName }; if (setupMode) payload.setupMode = setupMode; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); } } public secretbundle = { // SecretBundles getSecretBundleById: async (secretBundleIdArg: string) => { return SecretBundle.getSecretBundleById(this, secretBundleIdArg); }, getSecretBundles: async () => { return SecretBundle.getSecretBundles(this); }, createSecretBundle: async (optionsArg: Parameters[1]) => { return SecretBundle.createSecretBundle(this, optionsArg); } } public secretgroup = { // SecretGroups getSecretGroupById: async (secretGroupIdArg: string) => { return SecretGroup.getSecretGroupById(this, secretGroupIdArg); }, getSecretGroups: async () => { return SecretGroup.getSecretGroups(this); }, createSecretGroup: async (optionsArg: Parameters[1]) => { return SecretGroup.createSecretGroup(this, optionsArg); } } // Settings API public settings = { getSettings: async (): Promise<{ settings: plugins.servezoneInterfaces.data.ICloudlySettingsMasked }> => { const op = 'getSettings'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.identity }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.identity }); }, updateSettings: async (updates: Partial): Promise<{ success: boolean; message: string; }> => { const op = 'updateSettings'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.identity, updates }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.identity, updates }); }, testProviderConnection: async (provider: string): Promise<{ connectionValid: boolean; message: string; }> => { const op = 'testProviderConnection'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.identity, provider: provider as any }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.identity, provider: provider as any }); } } // Task API public tasks = { getTasks: async (): Promise<{ tasks: Array<{ name: string; description: string; category: 'maintenance' | 'deployment' | 'backup' | 'monitoring' | 'cleanup' | 'system' | 'security'; schedule?: string; lastRun?: number; enabled: boolean; }> }> => { const op = 'getTasks'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.identity }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.identity }); }, getTaskExecutions: async (filter?: any): Promise<{ executions: plugins.servezoneInterfaces.data.ITaskExecution[]; }> => { const op = 'getTaskExecutions'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.identity, filter }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.identity, filter }); }, getTaskExecutionById: async (executionId: string): Promise<{ execution: plugins.servezoneInterfaces.data.ITaskExecution }> => { const op = 'getTaskExecutionById'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.identity, executionId }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.identity, executionId }); }, triggerTask: async (taskName: string, userId?: string): Promise<{ execution: plugins.servezoneInterfaces.data.ITaskExecution }> => { const op = 'triggerTask'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.identity, taskName, userId }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.identity, taskName, userId }); }, cancelTask: async (executionId: string): Promise<{ success: boolean }> => { const op = 'cancelTask'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.identity, executionId }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.identity, executionId }); } } // Domain API public domains = { getDomains: async (): Promise<{ domains: plugins.servezoneInterfaces.data.IDomain[] }> => { const op = 'getDomains'; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire({ identity: this.identity }); return this.createHttpRequest(op).fire({ identity: this.identity }); }, getDomainById: async (domainId: string): Promise<{ domain: plugins.servezoneInterfaces.data.IDomain }> => { const op = 'getDomainById'; const payload = { identity: this.identity, domainId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, createDomain: async (domainData: plugins.servezoneInterfaces.data.IDomain['data']): Promise<{ domain: plugins.servezoneInterfaces.data.IDomain }> => { const op = 'createDomain'; const payload = { identity: this.identity, domainData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, updateDomain: async (domainId: string, domainData: Partial): Promise<{ domain: plugins.servezoneInterfaces.data.IDomain }> => { const op = 'updateDomain'; const payload = { identity: this.identity, domainId, domainData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteDomain: async (domainId: string): Promise<{ success: boolean }> => { const op = 'deleteDomain'; const payload = { identity: this.identity, domainId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, verifyDomain: async (domainId: string, verificationMethod?: 'dns' | 'http' | 'email' | 'manual'): Promise<{ domain: plugins.servezoneInterfaces.data.IDomain; verificationResult: any }> => { const op = 'verifyDomain'; const payload = { identity: this.identity, domainId, verificationMethod } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, }; // DNS API public dns = { getDnsEntries: async (zone?: string): Promise<{ dnsEntries: plugins.servezoneInterfaces.data.IDnsEntry[] }> => { const op = 'getDnsEntries'; const payload = { identity: this.identity, zone } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, getDnsEntryById: async (dnsEntryId: string): Promise<{ dnsEntry: plugins.servezoneInterfaces.data.IDnsEntry }> => { const op = 'getDnsEntryById'; const payload = { identity: this.identity, dnsEntryId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, createDnsEntry: async (dnsEntryData: plugins.servezoneInterfaces.data.IDnsEntry['data']): Promise<{ dnsEntry: plugins.servezoneInterfaces.data.IDnsEntry }> => { const op = 'createDnsEntry'; const payload = { identity: this.identity, dnsEntryData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, updateDnsEntry: async (dnsEntryId: string, dnsEntryData: plugins.servezoneInterfaces.data.IDnsEntry['data']): Promise<{ dnsEntry: plugins.servezoneInterfaces.data.IDnsEntry }> => { const op = 'updateDnsEntry'; const payload = { identity: this.identity, dnsEntryId, dnsEntryData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteDnsEntry: async (dnsEntryId: string): Promise<{ success: boolean }> => { const op = 'deleteDnsEntry'; const payload = { identity: this.identity, dnsEntryId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, getDnsZones: async (): Promise<{ zones: string[] }> => { const op = 'getDnsZones'; const payload = { identity: this.identity } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, }; // Deployment API public deployments = { getDeployments: async (): Promise<{ deployments: plugins.servezoneInterfaces.data.IDeployment[] }> => { const op = 'getDeployments'; const payload = { identity: this.identity } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, getDeploymentById: async (deploymentId: string): Promise<{ deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'getDeploymentById'; const payload = { identity: this.identity, deploymentId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, createDeployment: async (deploymentData: Partial): Promise<{ deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'createDeployment'; const payload = { identity: this.identity, deploymentData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, updateDeployment: async (deploymentId: string, deploymentData: Partial): Promise<{ deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'updateDeployment'; const payload = { identity: this.identity, deploymentId, deploymentData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteDeployment: async (deploymentId: string): Promise<{ success: boolean }> => { const op = 'deleteDeploymentById'; const payload = { identity: this.identity, deploymentId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, restartDeployment: async (deploymentId: string): Promise<{ success: boolean; deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'restartDeployment'; const payload = { identity: this.identity, deploymentId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, scaleDeployment: async (deploymentId: string, replicas: number): Promise<{ success: boolean; deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'scaleDeployment'; const payload = { identity: this.identity, deploymentId, replicas } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, }; }