fix(core): update
This commit is contained in:
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @pushrocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/cloudly',
|
||||
version: '1.0.214',
|
||||
description: 'A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.'
|
||||
}
|
120
ts/cloudly.classes.cloudly.ts
Normal file
120
ts/cloudly.classes.cloudly.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import { CloudlyConfig } from './cloudly.classes.config.js';
|
||||
|
||||
// interfaces
|
||||
import {} from '@tsclass/tsclass';
|
||||
|
||||
// Cloudly mods
|
||||
import { CloudlyInfo } from './cloudly.classes.cloudlyinfo.js';
|
||||
import { CloudlyServer } from './cloudly.classes.server.js';
|
||||
|
||||
// connectors
|
||||
import { CloudflareConnector } from './connector.cloudflare/connector.js';
|
||||
import { LetsencryptConnector } from './connector.letsencrypt/connector.js';
|
||||
import { MongodbConnector } from './connector.mongodb/connector.js';
|
||||
|
||||
// processes
|
||||
import { CloudlyCoreflowManager } from './manager.coreflow/coreflowmanager.js';
|
||||
import { ClusterManager } from './manager.cluster/clustermanager.js';
|
||||
import { CloudlyTaskmanager } from './manager.task/taskmanager.js';
|
||||
import { CloudlyVersionManager } from './manager.version/versionmanager.js';
|
||||
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js'
|
||||
import { CloudlyServerManager } from './manager.server/servermanager.js';
|
||||
import { ExternalApiManager } from './manager.status/statusmanager.js';
|
||||
import { ImageManager } from './manager.image/classes.imagemanager.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
|
||||
/**
|
||||
* Cloudly class can be used to instantiate a cloudly server.
|
||||
* It is implemented as class in order to make it easier ro write node services that are more adjusted to invidual services
|
||||
*
|
||||
* ```ts
|
||||
* const mycloudly = new Cloudly ({...})
|
||||
* ```
|
||||
*/
|
||||
export class Cloudly {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public config: CloudlyConfig;
|
||||
public logger: plugins.smartlog.Smartlog;
|
||||
public ready: Promise<any>;
|
||||
|
||||
// mods
|
||||
public cloudlyInfo: CloudlyInfo;
|
||||
public server: CloudlyServer;
|
||||
|
||||
// connectors
|
||||
public cloudflareConnector: CloudflareConnector;
|
||||
public letsencryptConnector: LetsencryptConnector;
|
||||
public mongodbConnector: MongodbConnector;
|
||||
|
||||
// managers
|
||||
public secretManager: CloudlySecretManager;
|
||||
public clusterManager: ClusterManager;
|
||||
public coreflowManager: CloudlyCoreflowManager;
|
||||
public externalApiManager: ExternalApiManager;
|
||||
public imageManager: ImageManager;
|
||||
public taskManager: CloudlyTaskmanager;
|
||||
public versionManager: CloudlyVersionManager;
|
||||
public serverManager: CloudlyServerManager;
|
||||
|
||||
private readyDeferred = new plugins.smartpromise.Deferred();
|
||||
|
||||
constructor() {
|
||||
this.cloudlyInfo = new CloudlyInfo(this);
|
||||
this.config = new CloudlyConfig(this);
|
||||
|
||||
this.logger = logger;
|
||||
this.server = new CloudlyServer(this);
|
||||
this.ready = this.readyDeferred.promise;
|
||||
|
||||
// connectors
|
||||
this.mongodbConnector = new MongodbConnector(this); // database needs to go first
|
||||
this.cloudflareConnector = new CloudflareConnector(this);
|
||||
this.letsencryptConnector = new LetsencryptConnector(this);
|
||||
|
||||
// processes
|
||||
this.clusterManager = new ClusterManager(this);
|
||||
this.coreflowManager = new CloudlyCoreflowManager(this);
|
||||
this.externalApiManager = new ExternalApiManager(this);
|
||||
this.imageManager = new ImageManager(this);
|
||||
this.taskManager = new CloudlyTaskmanager(this);
|
||||
this.versionManager = new CloudlyVersionManager(this);
|
||||
this.secretManager = new CloudlySecretManager(this);
|
||||
this.serverManager = new CloudlyServerManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the cloudly instance and
|
||||
* @param configArg
|
||||
*/
|
||||
public async start() {
|
||||
// config
|
||||
await this.config.init();
|
||||
|
||||
// manageers
|
||||
await this.secretManager.start();
|
||||
await this.serverManager.start();
|
||||
|
||||
// connectors
|
||||
await this.mongodbConnector.init();
|
||||
await this.cloudflareConnector.init();
|
||||
await this.letsencryptConnector.init();
|
||||
await this.clusterManager.init();
|
||||
await this.server.start();
|
||||
this.readyDeferred.resolve();
|
||||
|
||||
// start the managers
|
||||
this.imageManager.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* stop the reception instance
|
||||
*/
|
||||
public async stop() {
|
||||
await this.server.stop();
|
||||
await this.letsencryptConnector.stop();
|
||||
await this.mongodbConnector.stop();
|
||||
await this.secretManager.stop();
|
||||
}
|
||||
}
|
13
ts/cloudly.classes.cloudlyinfo.ts
Normal file
13
ts/cloudly.classes.cloudlyinfo.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
import { Cloudly } from './index.js';
|
||||
|
||||
export class CloudlyInfo {
|
||||
public cloudlyRef: Cloudly;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public projectInfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
}
|
79
ts/cloudly.classes.config.ts
Normal file
79
ts/cloudly.classes.config.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
import type { Cloudly } from './cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* the main cloudly config
|
||||
*/
|
||||
export class CloudlyConfig {
|
||||
public cloudlyRef: Cloudly;
|
||||
public appData: plugins.npmextra.AppData<plugins.servezoneInterfaces.data.ICloudlyConfig>;
|
||||
public data: plugins.servezoneInterfaces.data.ICloudlyConfig
|
||||
|
||||
// authentication and settings
|
||||
public smartjwtInstance: plugins.smartjwt.SmartJwt;
|
||||
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.appData = await plugins.npmextra.AppData.createAndInit<plugins.servezoneInterfaces.data.ICloudlyConfig>({
|
||||
envMapping: {
|
||||
cfToken: 'CF_TOKEN',
|
||||
environment: 'SERVEZONE_ENVIRONMENT' as 'production' | 'integration',
|
||||
letsEncryptEmail: 'hard:domains@lossless.org',
|
||||
hetznerToken: 'HETZNER_API_TOKEN',
|
||||
letsEncryptPrivateKey: null,
|
||||
publicUrl: 'SERVEZONE_URL',
|
||||
publicPort: 'SERVEZONE_PORT',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'MONGODB_URL',
|
||||
mongoDbName: 'MONGODB_DATABASE',
|
||||
mongoDbUser: 'MONGODB_USER',
|
||||
mongoDbPass: 'MONGODB_PASSWORD',
|
||||
},
|
||||
s3Descriptor: {
|
||||
endpoint: 'S3_ENDPOINT',
|
||||
accessKey: 'S3_ACCESSKEY',
|
||||
accessSecret: 'S3_SECRETKEY',
|
||||
port: 'S3_PORT', // Note: This will remain as a string. Ensure to parse it to an integer where it's used.
|
||||
useSsl: true,
|
||||
},
|
||||
sslMode: 'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
|
||||
},
|
||||
requiredKeys: [
|
||||
'cfToken',
|
||||
'hetznerToken',
|
||||
'letsEncryptEmail',
|
||||
'publicUrl',
|
||||
'publicPort',
|
||||
'sslMode',
|
||||
'environment',
|
||||
'mongoDescriptor',
|
||||
],
|
||||
});
|
||||
|
||||
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
||||
const kvStore = await this.appData.getKvStore();
|
||||
|
||||
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys');
|
||||
|
||||
if (!existingJwtKeys) {
|
||||
await this.smartjwtInstance.createNewKeyPair();
|
||||
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
|
||||
await kvStore.writeKey('jwtKeys', newJwtKeys);
|
||||
} else {
|
||||
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
|
||||
}
|
||||
|
||||
this.data = await kvStore.readAll();
|
||||
const missingKeys = await this.appData.logMissingKeys();
|
||||
if (missingKeys.length > 0) {
|
||||
logger.log('error', `missing keys: ${missingKeys.join(', ')}`);
|
||||
throw new Error('missing keys');
|
||||
}
|
||||
}
|
||||
}
|
96
ts/cloudly.classes.server.ts
Normal file
96
ts/cloudly.classes.server.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
import { Cloudly } from './cloudly.classes.cloudly.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
|
||||
/**
|
||||
* handles incoming requests from CI to deploy new versions of apps
|
||||
*/
|
||||
export class CloudlyServer {
|
||||
/**
|
||||
* a reference to the cloudly instance
|
||||
*/
|
||||
private cloudlyRef: Cloudly;
|
||||
|
||||
/**
|
||||
* the smartexpress server handling the actual requests
|
||||
*/
|
||||
public typedServer: plugins.typedserver.TypedServer;
|
||||
public typedsocketServer: plugins.typedsocket.TypedSocket;
|
||||
|
||||
/**
|
||||
* typedrouter
|
||||
* @param cloudlyArg
|
||||
*/
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(cloudlyArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
}
|
||||
|
||||
// =========
|
||||
// LIFECYCLE
|
||||
// =========
|
||||
|
||||
/**
|
||||
* init the reception instance
|
||||
*/
|
||||
public async start() {
|
||||
logger.log('info', `cloudly domain is ${this.cloudlyRef.config.data.publicUrl}`)
|
||||
let sslCert: plugins.smartacme.Cert;
|
||||
|
||||
if (this.cloudlyRef.config.data.sslMode === 'letsencrypt') {
|
||||
logger.log('info', `Using letsencrypt for ssl mode. Trying to obtain a certificate...`)
|
||||
logger.log('info', `This might take 10 minutes...`)
|
||||
sslCert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
||||
this.cloudlyRef.config.data.publicUrl
|
||||
);
|
||||
logger.log('success', `Successfully obtained certificate for cloudly domain ${this.cloudlyRef.config.data.publicUrl}`)
|
||||
} else if (this.cloudlyRef.config.data.sslMode === 'external') {
|
||||
logger.log('info', `Using external certificate for ssl mode, meaning cloudly is not in charge of ssl termination.`)
|
||||
}
|
||||
|
||||
interface IRequestGuardData {
|
||||
req: plugins.typedserver.Request;
|
||||
res: plugins.typedserver.Response;
|
||||
}
|
||||
|
||||
// guards
|
||||
const guardIp = new plugins.smartguard.Guard<IRequestGuardData>(async (dataArg) => {
|
||||
if (true) {
|
||||
return true;
|
||||
} else {
|
||||
dataArg.res.status(500);
|
||||
dataArg.res.send(`Not allowed to perform this operation!`);
|
||||
dataArg.res.end();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// server
|
||||
this.typedServer = new plugins.typedserver.TypedServer({
|
||||
cors: true,
|
||||
forceSsl: false,
|
||||
port: this.cloudlyRef.config.data.publicPort,
|
||||
...(sslCert ? {
|
||||
privateKey: sslCert.privateKey,
|
||||
publicKey: sslCert.publicKey,
|
||||
} : {}),
|
||||
injectReload: true,
|
||||
serveDir: paths.distServeDir,
|
||||
watch: true,
|
||||
enableCompression: true,
|
||||
preferredCompressionMethod: 'gzip',
|
||||
});
|
||||
this.typedServer.typedrouter.addTypedRouter(this.typedrouter);
|
||||
await this.typedServer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* stop the reception instance
|
||||
*/
|
||||
public async stop() {
|
||||
await this.typedServer.stop();
|
||||
}
|
||||
}
|
16
ts/cloudly.logging.ts
Normal file
16
ts/cloudly.logging.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
|
||||
export const logger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
company: null,
|
||||
environment: null,
|
||||
runtime: null,
|
||||
zone: null,
|
||||
companyunit: null,
|
||||
containerName: null,
|
||||
}
|
||||
});
|
||||
logger.enableConsole({
|
||||
captureAll: false
|
||||
});
|
5
ts/cloudly.paths.ts
Normal file
5
ts/cloudly.paths.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
|
||||
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
|
||||
export const distServeDir = plugins.path.join(packageDir, './dist_serve');
|
77
ts/cloudly.plugins.ts
Normal file
77
ts/cloudly.plugins.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
// node native
|
||||
import * as path from 'path';
|
||||
|
||||
export { path };
|
||||
|
||||
// @apiglobal scope
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
import * as typedsocket from '@api.global/typedsocket';
|
||||
|
||||
export { typedrequest, typedsocket };
|
||||
|
||||
// @mojoio scope
|
||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||
import * as digitalocean from '@apiclient.xyz/digitalocean';
|
||||
import * as hetznercloud from '@apiclient.xyz/hetznercloud';
|
||||
import * as slack from '@apiclient.xyz/slack';
|
||||
|
||||
export { cloudflare, digitalocean, hetznercloud, slack };
|
||||
|
||||
// @tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
|
||||
export { tsclass };
|
||||
|
||||
// @push.rocks scope
|
||||
import * as npmextra from '@push.rocks/npmextra';
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartacme from '@push.rocks/smartacme';
|
||||
import * as smartbucket from '@push.rocks/smartbucket';
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartdelay from '@push.rocks/smartdelay';
|
||||
import * as smartexit from '@push.rocks/smartexit';
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartguard from '@push.rocks/smartguard';
|
||||
import * as smartjson from '@push.rocks/smartjson';
|
||||
import * as smartjwt from '@push.rocks/smartjwt';
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import * as smartssh from '@push.rocks/smartssh';
|
||||
import * as smartstring from '@push.rocks/smartstring';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||
|
||||
export {
|
||||
npmextra,
|
||||
projectinfo,
|
||||
qenv,
|
||||
smartacme,
|
||||
smartbucket,
|
||||
smartcli,
|
||||
smartdata,
|
||||
smartexit,
|
||||
typedserver,
|
||||
smartdelay,
|
||||
smartfile,
|
||||
smartguard,
|
||||
smartjson,
|
||||
smartjwt,
|
||||
smartlog,
|
||||
smartpath,
|
||||
smartpromise,
|
||||
smartrequest,
|
||||
smartssh,
|
||||
smartstring,
|
||||
smartunique,
|
||||
taskbuffer,
|
||||
};
|
||||
|
||||
// @servezone scope
|
||||
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
||||
|
||||
export { servezoneInterfaces };
|
19
ts/connector.cloudflare/connector.ts
Normal file
19
ts/connector.cloudflare/connector.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* the portion of Cloudflare responsible
|
||||
*/
|
||||
export class CloudflareConnector {
|
||||
private cloudlyRef: Cloudly;
|
||||
public cloudflare: plugins.cloudflare.CloudflareAccount;
|
||||
|
||||
constructor(cloudlyArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyArg;
|
||||
}
|
||||
|
||||
// init the instance
|
||||
public async init() {
|
||||
this.cloudflare = new plugins.cloudflare.CloudflareAccount(this.cloudlyRef.config.data.cfToken);
|
||||
}
|
||||
}
|
46
ts/connector.letsencrypt/connector.ts
Normal file
46
ts/connector.letsencrypt/connector.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
export class LetsencryptConnector {
|
||||
private cloudlyRef: Cloudly;
|
||||
private smartacme: plugins.smartacme.SmartAcme;
|
||||
|
||||
constructor(cloudlyArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyArg;
|
||||
}
|
||||
|
||||
public async getCertificateForDomain(domainName: string) {
|
||||
const cert = await this.smartacme.getCertificateForDomain(domainName);
|
||||
return cert;
|
||||
}
|
||||
|
||||
/**
|
||||
* inits letsencrypt
|
||||
*/
|
||||
public async init() {
|
||||
this.smartacme = new plugins.smartacme.SmartAcme({
|
||||
accountEmail: this.cloudlyRef.config.data.letsEncryptEmail,
|
||||
accountPrivateKey: this.cloudlyRef.config.data.letsEncryptPrivateKey,
|
||||
environment: this.cloudlyRef.config.data.environment,
|
||||
setChallenge: async (dnsChallenge) => {
|
||||
await this.cloudlyRef.cloudflareConnector.cloudflare.convenience.acmeSetDnsChallenge(
|
||||
dnsChallenge
|
||||
);
|
||||
},
|
||||
removeChallenge: async (dnsChallenge) => {
|
||||
await this.cloudlyRef.cloudflareConnector.cloudflare.convenience.acmeRemoveDnsChallenge(
|
||||
dnsChallenge
|
||||
);
|
||||
},
|
||||
mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor,
|
||||
});
|
||||
await this.smartacme.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* stops the instance
|
||||
*/
|
||||
public async stop() {
|
||||
await this.smartacme.stop();
|
||||
}
|
||||
}
|
21
ts/connector.mongodb/connector.ts
Normal file
21
ts/connector.mongodb/connector.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
export class MongodbConnector {
|
||||
// INSTANCE
|
||||
private cloudlyRef: Cloudly;
|
||||
public smartdataDb: plugins.smartdata.SmartdataDb;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.smartdataDb = new plugins.smartdata.SmartdataDb(this.cloudlyRef.config.data.mongoDescriptor);
|
||||
await this.smartdataDb.init();
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
await this.smartdataDb.close();
|
||||
}
|
||||
}
|
0
ts/demo/demo.data.clusters.ts
Normal file
0
ts/demo/demo.data.clusters.ts
Normal file
83
ts/demo/demo.data.secrets.ts
Normal file
83
ts/demo/demo.data.secrets.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
// Create an array to hold 10 ISecretGroup objects
|
||||
const demoSecretGroups: plugins.servezoneInterfaces.data.ISecretGroup[] = [];
|
||||
|
||||
// Generate 10 ISecretGroup objects
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const secretGroup: plugins.servezoneInterfaces.data.ISecretGroup = {
|
||||
id: `${plugins.smartunique.shortId(8)}`,
|
||||
data: {
|
||||
name: `Demo Secret Group ${i}`,
|
||||
description: `This is a demo secret group for testing purposes ${i}`,
|
||||
key: `CI_RUNNER_TOKEN_${i}`,
|
||||
priority: i,
|
||||
tags: [
|
||||
{
|
||||
key: 'project',
|
||||
value: `my_project_${i}`,
|
||||
},
|
||||
{
|
||||
key: 'environment',
|
||||
value: i % 2 === 0 ? 'staging' : 'production',
|
||||
},
|
||||
],
|
||||
environments: {
|
||||
production: {
|
||||
value: `prod_secret_value_${i}`,
|
||||
lastUpdated: 1630522000 + i,
|
||||
history: [
|
||||
{
|
||||
timestamp: String(1630521000 + i),
|
||||
value: `old_prod_value_${i}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
staging: {
|
||||
value: `stag_secret_value_${i}`,
|
||||
updateToken: `updateToken${i}`,
|
||||
lastUpdated: 1630522500 + i,
|
||||
history: [
|
||||
{
|
||||
timestamp: String(1630521500 + i),
|
||||
value: `old_stag_value_${i}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Push each ISecretGroup object into the array
|
||||
demoSecretGroups.push(secretGroup);
|
||||
}
|
||||
|
||||
// Create an array to hold 10 IConfigBundle objects
|
||||
const demoConfigBundles: plugins.servezoneInterfaces.data.ISecretBundle[] = [];
|
||||
|
||||
// Generate 10 IConfigBundle objects that match demoSecretGroups
|
||||
for (let i = 0; i < demoSecretGroups.length; i++) {
|
||||
const secretGroup = demoSecretGroups[i];
|
||||
|
||||
const configBundle: plugins.servezoneInterfaces.data.ISecretBundle = {
|
||||
id: `configBundleId${i + 1}`,
|
||||
data: {
|
||||
name: `Demo Config Bundle ${i + 1}`,
|
||||
description: 'Demo Purpose',
|
||||
includedSecretGroupIds: [secretGroup.id],
|
||||
includedTags: secretGroup.data.tags,
|
||||
authorizations: Object.keys(secretGroup.data.environments).map((env) => {
|
||||
return {
|
||||
secretAccessKey: `mockSecretAccessKeyFor${env}`,
|
||||
environment: env,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
// Push each IConfigBundle object into the array
|
||||
demoConfigBundles.push(configBundle);
|
||||
}
|
||||
|
||||
// Exporting the array of demo IConfigBundle objects
|
||||
export { demoSecretGroups, demoConfigBundles };
|
38
ts/demo/index.ts
Normal file
38
ts/demo/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
export const installDemoData = async (cloudlyRef: Cloudly) => {
|
||||
|
||||
// ================================================================================
|
||||
// SECRETS
|
||||
|
||||
const demoDataSecrets = await import('./demo.data.secrets.js');
|
||||
|
||||
const secretGroups = await cloudlyRef.secretManager.CSecretGroup.getInstances({});
|
||||
for (const secretGroup of secretGroups) {
|
||||
await secretGroup.delete();
|
||||
}
|
||||
|
||||
const secretBundles = await cloudlyRef.secretManager.CSecretBundle.getInstances({});
|
||||
for (const secretBundle of secretBundles) {
|
||||
await secretBundle.delete();
|
||||
}
|
||||
|
||||
for (const secretData of demoDataSecrets.demoSecretGroups) {
|
||||
const secretGroup = new cloudlyRef.secretManager.CSecretGroup();
|
||||
Object.assign(secretGroup, secretData);
|
||||
await secretGroup.save();
|
||||
}
|
||||
for (const secretBundleData of demoDataSecrets.demoConfigBundles) {
|
||||
const secretBundle = new cloudlyRef.secretManager.CSecretBundle();
|
||||
Object.assign(secretBundle, secretBundleData);
|
||||
await secretBundle.save();
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// CLUSTERS
|
||||
const clusters = await cloudlyRef.clusterManager.CCluster.getInstances({});
|
||||
for (const cluster of clusters) {
|
||||
await cluster.delete();
|
||||
}
|
||||
|
||||
}
|
28
ts/index.ts
Normal file
28
ts/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as early from '@push.rocks/early';
|
||||
early.start('cloudly');
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
import { Cloudly } from './cloudly.classes.cloudly.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
const cloudlyQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir, true);
|
||||
early.stop();
|
||||
|
||||
|
||||
/**
|
||||
* starts the cloudly instance
|
||||
*/
|
||||
const runCli = async () => {
|
||||
logger.log('info', process.env.SERVEZONE_ENVIRONMENT);
|
||||
const cloudlyInstance = new Cloudly();
|
||||
|
||||
logger.log(
|
||||
'info',
|
||||
`running in environment ${await cloudlyQenv.getEnvVarOnDemand('SERVEZONE_ENVIRONMENT')}`
|
||||
);
|
||||
|
||||
await cloudlyInstance.start();
|
||||
const demoMod = await import('./demo/index.js');
|
||||
demoMod.installDemoData(cloudlyInstance);
|
||||
};
|
||||
|
||||
export { runCli, Cloudly };
|
31
ts/manager.cluster/cluster.ts
Normal file
31
ts/manager.cluster/cluster.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
/*
|
||||
* cluster defines a swarmkit cluster
|
||||
*/
|
||||
@plugins.smartdata.Manager()
|
||||
export class Cluster extends plugins.smartdata.SmartDataDbDoc<Cluster, plugins.servezoneInterfaces.data.ICluster> {
|
||||
// STATIC
|
||||
public static async fromConfigObject(
|
||||
configObjectArg: plugins.servezoneInterfaces.data.ICluster
|
||||
) {
|
||||
const newCluster = new Cluster();
|
||||
Object.assign(newCluster, configObjectArg);
|
||||
return newCluster;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.ICluster['data'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public async getServices(): Promise<plugins.servezoneInterfaces.data.IService[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
131
ts/manager.cluster/clustermanager.ts
Normal file
131
ts/manager.cluster/clustermanager.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import * as paths from '../cloudly.paths.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
|
||||
import { Cluster } from './cluster.js';
|
||||
|
||||
export class ClusterManager {
|
||||
public ready = plugins.smartpromise.defer();
|
||||
|
||||
public cloudlyRef: Cloudly;
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public CCluster = plugins.smartdata.setDefaultManagerForDoc(this, Cluster);
|
||||
|
||||
constructor(cloudlyRef: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRef;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>(
|
||||
new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => {
|
||||
const cluster = await this.storeCluster({
|
||||
id: plugins.smartunique.uniSimple('cluster'),
|
||||
data: {
|
||||
name: dataArg.clusterName,
|
||||
jumpCode: plugins.smartunique.uniSimple('cluster'),
|
||||
jumpCodeUsedAt: null,
|
||||
secretKey: plugins.smartunique.shortId(16),
|
||||
acmeInfo: null,
|
||||
cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`,
|
||||
servers: [],
|
||||
sshKeys: [],
|
||||
},
|
||||
});
|
||||
console.log(await cluster.createSavableObject());
|
||||
this.cloudlyRef.serverManager.ensureServerInfrastructure();
|
||||
return {
|
||||
clusterConfig: await cluster.createSavableObject(),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_GetAllClusters>(
|
||||
new plugins.typedrequest.TypedHandler('getAllClusters', async (dataArg) => {
|
||||
// TODO: do authentication here
|
||||
const clusters = await this.getAllClusters();
|
||||
return {
|
||||
clusters: await Promise.all(
|
||||
clusters.map((clusterArg) => clusterArg.createSavableObject())
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async init() {
|
||||
// lets read the config folder
|
||||
logger.log('info', 'config manager is now initializing');
|
||||
this.ready.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a cluster config by Name
|
||||
*/
|
||||
public async getClusterConfigBy_ServerIP() {
|
||||
await this.ready.promise;
|
||||
// TODO: implement getclusterConfigByServerIp
|
||||
}
|
||||
|
||||
public async getClusterConfigBy_JumpCode(jumpCodeArg: string) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
data: {
|
||||
jumpCode: jumpCodeArg,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async getClusterConfigBy_ClusterIdentifier(
|
||||
clusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||
) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
data: {
|
||||
name: clusterIdentifier.clusterName,
|
||||
secretKey: clusterIdentifier.secretKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get config by id
|
||||
*/
|
||||
public async getConfigBy_ConfigID(configId: string) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
id: configId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* gets all cluster configs
|
||||
*/
|
||||
public async getAllClusters() {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstances({});
|
||||
}
|
||||
|
||||
/**
|
||||
* allows storage of a config
|
||||
* @param configName
|
||||
* @param configObjectArg
|
||||
*/
|
||||
public async storeCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
|
||||
let clusterInstance = await Cluster.getInstance({ id: configObjectArg.id });
|
||||
if (!clusterInstance) {
|
||||
clusterInstance = await Cluster.fromConfigObject(configObjectArg);
|
||||
} else {
|
||||
Object.assign(clusterInstance, configObjectArg);
|
||||
}
|
||||
await clusterInstance.save();
|
||||
return clusterInstance;
|
||||
}
|
||||
}
|
73
ts/manager.coreflow/coreflowmanager.ts
Normal file
73
ts/manager.coreflow/coreflowmanager.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* in charge of talking to coreflow services on clusters
|
||||
* coreflow runs on a server when ServerManager is done.
|
||||
*/
|
||||
export class CloudlyCoreflowManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
|
||||
new plugins.typedrequest.TypedHandler('getIdentityByJumpCode', async (requestData) => {
|
||||
const clusterConfig =
|
||||
await this.cloudlyRef.clusterManager.getClusterConfigBy_JumpCode(
|
||||
requestData.jumpCode
|
||||
);
|
||||
|
||||
if (!clusterConfig) {
|
||||
throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid.');
|
||||
}
|
||||
|
||||
return {
|
||||
clusterIdentifier: {
|
||||
clusterName: clusterConfig.data.name,
|
||||
secretKey: clusterConfig.data.secretKey,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// lets enable the getting of cluster configs
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
|
||||
'getClusterConfig',
|
||||
async (dataArg) => {
|
||||
const clusterIdentifier = dataArg.clusterIdentifier;
|
||||
console.log('trying to get clusterConfigSet');
|
||||
console.log(dataArg);
|
||||
const clusterConfigSet =
|
||||
await this.cloudlyRef.clusterManager.getClusterConfigBy_ClusterIdentifier(
|
||||
clusterIdentifier
|
||||
);
|
||||
console.log('got cluster config and sending it back to coreflow');
|
||||
return {
|
||||
configData: await clusterConfigSet.createSavableObject()
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// lets enable getting of certificates
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>(
|
||||
'getSslCertificate',
|
||||
async (dataArg) => {
|
||||
console.log(`got request for certificate ${dataArg.requiredCertName}`);
|
||||
const cert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
||||
dataArg.requiredCertName
|
||||
);
|
||||
console.log(`got certificate ready for reponse ${dataArg.requiredCertName}`);
|
||||
return {
|
||||
certificate: await cert.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
21
ts/manager.image/classes.image.ts
Normal file
21
ts/manager.image/classes.image.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import type { ImageManager } from './classes.imagemanager.js';
|
||||
|
||||
@plugins.smartdata.Manager()
|
||||
export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.servezoneInterfaces.data.IImage, ImageManager> {
|
||||
public static async create(imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
|
||||
const image = new Image();
|
||||
image.id = plugins.smartunique.uni('image');
|
||||
Object.assign(image.data, imageDataArg);
|
||||
await image.save();
|
||||
return image;
|
||||
}
|
||||
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.IImage['data'];
|
||||
|
||||
public async getVersions() {}
|
||||
}
|
82
ts/manager.image/classes.imagemanager.ts
Normal file
82
ts/manager.image/classes.imagemanager.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
import { Image } from './classes.image.js';
|
||||
|
||||
export class ImageManager {
|
||||
cloudlyRef: Cloudly;
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
|
||||
|
||||
smartbucketInstance: plugins.smartbucket.SmartBucket;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||
'getAllImages',
|
||||
async (requestArg) => {
|
||||
const images = await this.CImage.getInstances({});
|
||||
return {
|
||||
images: await Promise.all(
|
||||
images.map((image) => {
|
||||
return image.createSavableObject();
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
||||
'createImage',
|
||||
async (reqArg) => {
|
||||
const image = await this.CImage.create({
|
||||
name: reqArg.name,
|
||||
});
|
||||
return {
|
||||
image: await image.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DownloadImage>(
|
||||
'pullImage',
|
||||
async (reqArg) => {
|
||||
const image = await this.CImage.getInstance({
|
||||
data: {
|
||||
name: reqArg.name,
|
||||
}
|
||||
});
|
||||
const imageVersion =
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const s3Descriptor: plugins.tsclass.storage.IS3Descriptor =
|
||||
await this.cloudlyRef.config.appData.waitForAndGetKey('s3Descriptor');
|
||||
console.log(this.cloudlyRef.config.data.s3Descriptor);
|
||||
this.smartbucketInstance = new plugins.smartbucket.SmartBucket(
|
||||
this.cloudlyRef.config.data.s3Descriptor
|
||||
);
|
||||
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
|
||||
await bucket.fastStore('test/test.txt', 'hello');
|
||||
}
|
||||
|
||||
public async createImage(nameArg: string) {
|
||||
const newImage = await this.CImage.create({
|
||||
name: nameArg,
|
||||
});
|
||||
}
|
||||
}
|
15
ts/manager.log/logmanager.ts
Normal file
15
ts/manager.log/logmanager.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* takes care of receiving and providing logs
|
||||
*/
|
||||
export class LogManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
}
|
||||
}
|
62
ts/manager.secret/classes.secretbundle.ts
Normal file
62
ts/manager.secret/classes.secretbundle.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// a secret bundle is a set of secrets ready to be used in a project.
|
||||
// it bundles secretgroups
|
||||
import { SecretGroup } from './classes.secretgroup.js';
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
@plugins.smartdata.Manager()
|
||||
export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
|
||||
SecretBundle,
|
||||
plugins.servezoneInterfaces.data.ISecretBundle
|
||||
> {
|
||||
// STATIC
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.ISecretBundle['data'];
|
||||
|
||||
public async getSecretGroups() {
|
||||
const secretGroups: SecretGroup[] = [];
|
||||
for (const secretGroupId of this.data.includedSecretGroupIds) {
|
||||
secretGroups.push(
|
||||
await SecretGroup.getInstance({
|
||||
id: secretGroupId,
|
||||
})
|
||||
);
|
||||
}
|
||||
return secretGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* searches the secretGroups for environments and returns them
|
||||
*/
|
||||
public async getEnvironments() {
|
||||
const environments = new Set();
|
||||
const secretGroups = await this.getSecretGroups();
|
||||
for (const secretGroup of secretGroups) {
|
||||
environments.add(secretGroup.data.environments);
|
||||
}
|
||||
return Array.from(environments);
|
||||
}
|
||||
|
||||
public async getAuthorizationFromAuthKey(authKeyArg: string) {
|
||||
const authorization = this.data.authorizations.find((authArg) => {
|
||||
return authArg.secretAccessKey === authKeyArg;
|
||||
});
|
||||
return authorization;
|
||||
}
|
||||
|
||||
public async getKeyValueObjectForEnvironment(environmentArg: string) {
|
||||
const secretGroups = await this.getSecretGroups();
|
||||
const returnObject = {};
|
||||
for (const secretGroup of secretGroups) {
|
||||
if (!secretGroup.data.environments[environmentArg]) {
|
||||
continue;
|
||||
}
|
||||
returnObject[secretGroup.data.key] = secretGroup.data.environments[environmentArg].value;
|
||||
}
|
||||
return returnObject;
|
||||
}
|
||||
}
|
21
ts/manager.secret/classes.secretgroup.ts
Normal file
21
ts/manager.secret/classes.secretgroup.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* a secretgroup is a set of secrets for different environments.
|
||||
*/
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
@plugins.smartdata.Manager()
|
||||
export class SecretGroup extends plugins.smartdata.SmartDataDbDoc<
|
||||
SecretGroup,
|
||||
plugins.servezoneInterfaces.data.ISecretGroup
|
||||
> {
|
||||
// INSTANCE
|
||||
|
||||
/**
|
||||
* the insatnce id. This should be a random id, except for default
|
||||
*/
|
||||
@plugins.smartdata.unI()
|
||||
id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
data: plugins.servezoneInterfaces.data.ISecretGroup['data'];
|
||||
}
|
156
ts/manager.secret/classes.secretmanager.ts
Normal file
156
ts/manager.secret/classes.secretmanager.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import * as paths from '../cloudly.paths.js';
|
||||
import { SecretBundle } from './classes.secretbundle.js';
|
||||
import { SecretGroup } from './classes.secretgroup.js';
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* The `ConfigVault` class provides methods for reading and writing configuration data to a file.
|
||||
* It uses the `TypedServer` and `TypedRouter` classes from the `configvault.plugins.js` module to handle HTTP requests and route them to the appropriate handlers.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
export class CloudlySecretManager {
|
||||
// attached classes
|
||||
public CSecretBundle = plugins.smartdata.setDefaultManagerForDoc(this, SecretBundle);
|
||||
public CSecretGroup = plugins.smartdata.setDefaultManagerForDoc(this, SecretGroup);
|
||||
|
||||
// INSTANCE
|
||||
public cloudlyRef: Cloudly;
|
||||
public projectinfo = new plugins.projectinfo.ProjectinfoNpm(paths.packageDir);
|
||||
public serviceQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir);
|
||||
public typedrouter: plugins.typedrequest.TypedRouter;
|
||||
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets set up a typedrouter
|
||||
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
||||
'adminLoginWithUsernameAndPassword',
|
||||
async (dataArg) => {
|
||||
let jwt: string;
|
||||
// console.log(dataArg);
|
||||
if (dataArg.username !== 'admin' || dataArg.password !== 'password') {
|
||||
logger.log('warn', 'login failed');
|
||||
} else {
|
||||
jwt = await this.cloudlyRef.config.smartjwtInstance.createJWT({
|
||||
status: 'loggedIn',
|
||||
});
|
||||
logger.log('success', 'login successful');
|
||||
}
|
||||
return {
|
||||
jwt,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
||||
'adminGetConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
dataArg.jwt
|
||||
const secretBundles = await SecretBundle.getInstances({});
|
||||
const secretGroups = await SecretGroup.getInstances({});
|
||||
return {
|
||||
secretBundles: [
|
||||
...(await Promise.all(
|
||||
secretBundles.map((configBundle) => configBundle.createSavableObject())
|
||||
)),
|
||||
],
|
||||
secretGroups: [
|
||||
...(await Promise.all(
|
||||
secretGroups.map((secretGroup) => secretGroup.createSavableObject())
|
||||
)),
|
||||
],
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_CreateConfigBundlesAndSecretGroups>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'adminCreateConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
for (const secretGroupObject of dataArg.secretGroups) {
|
||||
const secretGroup = new SecretGroup();
|
||||
secretGroup.id = plugins.smartunique.shortId(8);
|
||||
secretGroup.data = secretGroupObject.data;
|
||||
await secretGroup.save();
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
|
||||
'adminDeleteConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
for (const secretGroupId of dataArg.secretGroupIds) {
|
||||
const secretGroup = await SecretGroup.getInstance({
|
||||
id: secretGroupId,
|
||||
});
|
||||
await secretGroup.delete();
|
||||
}
|
||||
for (const secretBundleId of dataArg.secretBundleIds) {
|
||||
const configBundle = await SecretBundle.getInstance({
|
||||
id: secretBundleId,
|
||||
});
|
||||
await configBundle.delete();
|
||||
console.log(`deleted configbundle ${secretBundleId}`);
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// lets add typedrouter routes for accessing the configvailt from apps
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_GetEnvBundle>(
|
||||
'getEnvBundle',
|
||||
async (dataArg) => {
|
||||
const wantedBundle = await SecretBundle.getInstance({
|
||||
data: {
|
||||
authorizations: {
|
||||
// @ts-ignore
|
||||
$elemMatch: {
|
||||
secretAccessKey: dataArg.authorization,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const authorization = await wantedBundle.getAuthorizationFromAuthKey(
|
||||
dataArg.authorization
|
||||
);
|
||||
return {
|
||||
envBundle: {
|
||||
configKeyValueObject: await wantedBundle.getKeyValueObjectForEnvironment(
|
||||
authorization.environment
|
||||
),
|
||||
environment: authorization.environment,
|
||||
timeSensitive: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
}
|
39
ts/manager.server/server.ts
Normal file
39
ts/manager.server/server.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
/*
|
||||
* cluster defines a swarmkit cluster
|
||||
*/
|
||||
@plugins.smartdata.Manager()
|
||||
export class Server extends plugins.smartdata.SmartDataDbDoc<Server, plugins.servezoneInterfaces.data.IServer> {
|
||||
// STATIC
|
||||
public static async createFromHetznerServer(
|
||||
hetznerServerArg: plugins.hetznercloud.HetznerServer
|
||||
) {
|
||||
const newServer = new Server();
|
||||
newServer.id = plugins.smartunique.shortId(8);
|
||||
const data: plugins.servezoneInterfaces.data.IServer['data'] = {
|
||||
assignedClusterId: hetznerServerArg.data.labels.clusterId,
|
||||
requiredDebianPackages: [],
|
||||
sshKeys: [],
|
||||
type: 'hetzner',
|
||||
}
|
||||
Object.assign(newServer, { data });
|
||||
await newServer.save();
|
||||
return newServer;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.IServer['data'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public async getServices(): Promise<plugins.servezoneInterfaces.data.IService[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
103
ts/manager.server/servermanager.ts
Normal file
103
ts/manager.server/servermanager.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
import { Cluster } from '../manager.cluster/cluster.js';
|
||||
import { Server } from './server.js';
|
||||
|
||||
export class CloudlyServerManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
|
||||
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public CServer = plugins.smartdata.setDefaultManagerForDoc(this, Server);
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
|
||||
/**
|
||||
* is used be serverconfig module on the server to get the actual server config
|
||||
*/
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>(
|
||||
'getServerConfig',
|
||||
async (requestData) => {
|
||||
const serverId = requestData.serverId;
|
||||
const server = await this.CServer.getInstance({
|
||||
id: serverId,
|
||||
})
|
||||
return {
|
||||
configData: await server.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(this.cloudlyRef.config.data.hetznerToken);
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
|
||||
/**
|
||||
* creates the server infrastructure on hetzner
|
||||
* ensures that there are exactly the reources that are needed
|
||||
* no more, no less
|
||||
*/
|
||||
public async ensureServerInfrastructure() {
|
||||
// get all clusters
|
||||
const allClusters = await this.cloudlyRef.clusterManager.getAllClusters();
|
||||
for (const cluster of allClusters) {
|
||||
// get existing servers
|
||||
const servers = await this.getServersByCluster(cluster);
|
||||
|
||||
// if there is no server, create one
|
||||
if (servers.length === 0) {
|
||||
const server = await this.hetznerAccount.createServer({
|
||||
name: plugins.smartunique.uniSimple('server'),
|
||||
location: 'nbg1',
|
||||
type: 'cpx41',
|
||||
labels: {
|
||||
clusterId: cluster.id,
|
||||
priority: '1',
|
||||
}
|
||||
});
|
||||
const newServer = await Server.createFromHetznerServer(server);
|
||||
console.log(`cluster created new server for cluster ${cluster.id}`);
|
||||
} else {
|
||||
console.log(`cluster ${cluster.id} already has servers. Making sure that they actually exist in the real world...`);
|
||||
// if there is a server, make sure that it exists
|
||||
for (const server of servers) {
|
||||
const hetznerServer = await this.hetznerAccount.getServersByLabel({
|
||||
'clusterId': cluster.id
|
||||
});
|
||||
if (!hetznerServer) {
|
||||
console.log(`server ${server.id} does not exist in the real world. Creating it now...`);
|
||||
const hetznerServer = await this.hetznerAccount.createServer({
|
||||
name: plugins.smartunique.uniSimple('server'),
|
||||
location: 'nbg1',
|
||||
type: 'cpx41',
|
||||
labels: {
|
||||
clusterId: cluster.id,
|
||||
priority: '1',
|
||||
}
|
||||
});
|
||||
const newServer = await Server.createFromHetznerServer(hetznerServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getServersByCluster(clusterArg: Cluster) {
|
||||
const results = await this.CServer.getInstances({
|
||||
data: {
|
||||
assignedClusterId: clusterArg.id,
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
}
|
0
ts/manager.service/classes.service.ts
Normal file
0
ts/manager.service/classes.service.ts
Normal file
0
ts/manager.service/classes.servicemanager.ts
Normal file
0
ts/manager.service/classes.servicemanager.ts
Normal file
22
ts/manager.status/statusmanager.ts
Normal file
22
ts/manager.status/statusmanager.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../index.js';
|
||||
|
||||
/**
|
||||
* external api manager manages external api requests
|
||||
*/
|
||||
export class ExternalApiManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(cloudlyRef: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRef;
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.network.IRequest_Any_Cloudly_GetNetworkNodes>(
|
||||
new plugins.typedrequest.TypedHandler('getNetworkNodes', async (requestData) => {
|
||||
const networkNodes = [];
|
||||
return {
|
||||
networkNodes,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
35
ts/manager.task/taskmanager.ts
Normal file
35
ts/manager.task/taskmanager.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
|
||||
export class CloudlyTaskmanager {
|
||||
public cloudlyRef: Cloudly;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public everyMinuteTask = new plugins.taskbuffer.Task({
|
||||
taskFunction: async () => {},
|
||||
});
|
||||
|
||||
public everyHourTask = new plugins.taskbuffer.Task({
|
||||
taskFunction: async () => {
|
||||
logger.log('info', `Performing hourly maintenance check.`);
|
||||
const configs = await this.cloudlyRef.clusterManager.getAllClusters();
|
||||
logger.log('info', `Got ${configs.length} configs`);
|
||||
configs.map((configArg) => {
|
||||
console.log(configArg.name);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
public everyDayTask = new plugins.taskbuffer.Task({
|
||||
taskFunction: async () => {},
|
||||
});
|
||||
|
||||
public everyWeekTask = new plugins.taskbuffer.Task({
|
||||
taskFunction: async () => {},
|
||||
});
|
||||
}
|
32
ts/manager.version/containerversion.ts
Normal file
32
ts/manager.version/containerversion.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
/*
|
||||
A container version is managed by the versionmanager
|
||||
*/
|
||||
@plugins.smartdata.Manager()
|
||||
export class ContainerVersion
|
||||
extends plugins.smartdata.SmartDataDbDoc<ContainerVersion, unknown>
|
||||
implements plugins.servezoneInterfaces.data.IContainerVersionData
|
||||
{
|
||||
public static async fromIVersionData(
|
||||
dataArg: plugins.servezoneInterfaces.data.IContainerVersionData
|
||||
) {
|
||||
const containerVersionInstance = new ContainerVersion();
|
||||
containerVersionInstance.id = plugins.smartunique.shortId();
|
||||
Object.assign(containerVersionInstance, dataArg);
|
||||
return containerVersionInstance;
|
||||
}
|
||||
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public dockerImageUrl: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public dockerImageVersion: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
149
ts/manager.version/versionmanager.ts
Normal file
149
ts/manager.version/versionmanager.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
import { ContainerVersion } from './containerversion.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
export class CloudlyVersionManager {
|
||||
// INSTANCE
|
||||
public cloudlyRef: Cloudly;
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
// connected classes
|
||||
public CContainerVersion = plugins.smartdata.setDefaultManagerForDoc(this, ContainerVersion);
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
// get version
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_GetLatestContainerVersion>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'getLatestContainerVersion',
|
||||
async (typedRequestData) => {
|
||||
const containerVersionGet: ContainerVersion =
|
||||
await ContainerVersion.getInstance<ContainerVersion>({
|
||||
dockerImageUrl: typedRequestData.dockerImageUrl,
|
||||
});
|
||||
return {
|
||||
dockerImageUrl: containerVersionGet.dockerImageUrl,
|
||||
dockerImageVersion: containerVersionGet.dockerImageVersion,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// update version
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_InformCloudlyAboutNewContainerVersion>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'informCloudlyAboutNewContainerVersion',
|
||||
async (dataArg) => {
|
||||
console.log(`Got a container version announcement! "${dataArg.dockerImageUrl}"`);
|
||||
let containerVersion: ContainerVersion =
|
||||
await ContainerVersion.getInstance<ContainerVersion>({
|
||||
dockerImageUrl: dataArg.dockerImageUrl,
|
||||
});
|
||||
|
||||
if (containerVersion) {
|
||||
containerVersion.dockerImageVersion = dataArg.dockerImageVersion;
|
||||
await containerVersion.save();
|
||||
} else {
|
||||
containerVersion = await ContainerVersion.fromIVersionData(dataArg);
|
||||
await containerVersion.save();
|
||||
}
|
||||
|
||||
// lets push this info to the relevant clusters
|
||||
const clusters = await this.cloudlyRef.clusterManager.getAllClusters();
|
||||
let foundServices: plugins.servezoneInterfaces.data.IService;
|
||||
let relevantClusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier;
|
||||
for (const clusterArg of clusters) {
|
||||
console.log(clusterArg);
|
||||
for (const serviceArg of await clusterArg.getServices()) {
|
||||
if (serviceArg.image === containerVersion.dockerImageUrl) {
|
||||
foundServices = serviceArg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundServices) {
|
||||
relevantClusterIdentifier = {
|
||||
clusterName: clusterArg.data.name,
|
||||
secretKey: clusterArg.data.secretKey,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!relevantClusterIdentifier) {
|
||||
console.log('no cluster found that needs to update');
|
||||
return {};
|
||||
} else {
|
||||
console.log('found relevant cluster identifier:');
|
||||
console.log(relevantClusterIdentifier);
|
||||
}
|
||||
|
||||
const targetConnection =
|
||||
await this.cloudlyRef.server.typedsocketServer.findTargetConnection(
|
||||
async (connectionArg) => {
|
||||
const identityTag = await connectionArg.getTagById('identity');
|
||||
if (!identityTag) {
|
||||
return false;
|
||||
}
|
||||
const result =
|
||||
plugins.smartjson.stringify(identityTag.payload) ===
|
||||
plugins.smartjson.stringify(relevantClusterIdentifier);
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
if (targetConnection) {
|
||||
console.log(`the relevant cluster is connected and is now being informed.`);
|
||||
const informCoreflowTR =
|
||||
this.cloudlyRef.server.typedsocketServer.createTypedRequest<plugins.servezoneInterfaces.requests.version.IRequest_Cloudly_Coreflow_VersionManager_InformCoreflowAboutNewContainerVersion>(
|
||||
'informCoreflowAboutNewContainerVersion',
|
||||
targetConnection
|
||||
);
|
||||
informCoreflowTR.fire({
|
||||
dockerImageUrl: containerVersion.dockerImageUrl,
|
||||
dockerImageVersion: containerVersion.dockerImageVersion,
|
||||
});
|
||||
} else {
|
||||
console.log('the relevant cluster is not connected at this time.');
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// lets support the servezone standard
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.IRequest_InformAboutNewContainerImage>(
|
||||
'servezonestandard_InformAboutNewContainerVersion',
|
||||
async (dataArg) => {
|
||||
const result =
|
||||
await this.typedRouter.routeAndAddResponse<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_InformCloudlyAboutNewContainerVersion>(
|
||||
{
|
||||
method: 'informCloudlyAboutNewContainerVersion',
|
||||
request: {
|
||||
dockerImageUrl: dataArg.containerImageInfo.registryUrl,
|
||||
dockerImageVersion: dataArg.containerImageInfo.version,
|
||||
},
|
||||
response: null
|
||||
},
|
||||
true
|
||||
);
|
||||
return result.response;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets all versions
|
||||
*/
|
||||
public async getAllVersions() {
|
||||
const result = await ContainerVersion.getInstances<ContainerVersion>({});
|
||||
return result;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user