f40ef6b7c0
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import { Cloudly } from '../classes.cloudly.js';
|
|
import { Cluster } from '../manager.cluster/classes.cluster.js';
|
|
import { ClusterNode } from './classes.clusternode.js';
|
|
import { CurlFresh } from './classes.curlfresh.js';
|
|
|
|
export class CloudlyNodeManager {
|
|
public cloudlyRef: Cloudly;
|
|
public typedRouter = new plugins.typedrequest.TypedRouter();
|
|
public curlfreshInstance = new CurlFresh(this);
|
|
|
|
public hetznerAccount?: plugins.hetznercloud.HetznerAccount;
|
|
|
|
public get db() {
|
|
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
|
}
|
|
public CClusterNode = plugins.smartdata.setDefaultManagerForDoc(this, ClusterNode);
|
|
|
|
constructor(cloudlyRefArg: Cloudly) {
|
|
this.cloudlyRef = cloudlyRefArg;
|
|
|
|
/**
|
|
* is used be serverconfig module on the node to get the actual node config
|
|
*/
|
|
this.typedRouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.node.IRequest_Any_Cloudly_GetNodeConfig>(
|
|
'getNodeConfig',
|
|
async (requestData) => {
|
|
const nodeId = requestData.nodeId;
|
|
const node = await this.CClusterNode.getInstance({
|
|
id: nodeId,
|
|
});
|
|
return {
|
|
configData: await node.createSavableObject(),
|
|
};
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
public async start() {
|
|
const hetznerToken = await this.cloudlyRef.settingsManager.getSetting('hetznerToken');
|
|
|
|
if (!hetznerToken) {
|
|
console.log('warn', 'No Hetzner token configured in settings. Hetzner features will be disabled.');
|
|
return;
|
|
}
|
|
|
|
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(hetznerToken);
|
|
}
|
|
|
|
public async stop() {}
|
|
|
|
/**
|
|
* creates the node infrastructure on hetzner
|
|
* ensures that there are exactly the resources that are needed
|
|
* no more, no less
|
|
*/
|
|
public async ensureNodeInfrastructure() {
|
|
// get all clusters
|
|
const allClusters = await this.cloudlyRef.clusterManager.getAllClusters();
|
|
for (const cluster of allClusters) {
|
|
// Skip clusters that are not set up for Hetzner auto-provisioning
|
|
if (cluster.data.setupMode !== 'hetzner') {
|
|
console.log(`Skipping node provisioning for cluster ${cluster.id} - setupMode is ${cluster.data.setupMode || 'manual'}`);
|
|
continue;
|
|
}
|
|
const hetznerAccount = this.hetznerAccount;
|
|
if (!hetznerAccount) {
|
|
throw new Error('Hetzner account is not configured');
|
|
}
|
|
|
|
// get existing nodes
|
|
const nodes = await this.getNodesByCluster(cluster);
|
|
|
|
// if there is no node, create one
|
|
if (nodes.length === 0) {
|
|
const hetznerServer = await hetznerAccount.createServer({
|
|
name: plugins.smartunique.uniSimple('node'),
|
|
location: 'nbg1',
|
|
type: 'cpx41',
|
|
labels: {
|
|
clusterId: cluster.id,
|
|
priority: '1',
|
|
},
|
|
userData: await this.curlfreshInstance.getServerUserData(cluster),
|
|
});
|
|
|
|
// First create BareMetal record
|
|
const baremetal = await this.cloudlyRef.baremetalManager.createBaremetalFromHetznerServer(hetznerServer);
|
|
|
|
const newNode = await ClusterNode.createFromHetznerServer(hetznerServer, cluster.id, baremetal.id);
|
|
await baremetal.assignNode(newNode.id);
|
|
console.log(`cluster created new node for cluster ${cluster.id}`);
|
|
} else {
|
|
console.log(
|
|
`cluster ${cluster.id} already has nodes. Making sure that they actually exist in the real world...`,
|
|
);
|
|
// if there is a node, make sure that it exists
|
|
for (const node of nodes) {
|
|
const hetznerServers = await hetznerAccount.getServersByLabel({
|
|
clusterId: cluster.id,
|
|
});
|
|
if (!hetznerServers || hetznerServers.length === 0) {
|
|
console.log(`node ${node.id} does not exist in the real world. Creating it now...`);
|
|
const hetznerServer = await hetznerAccount.createServer({
|
|
name: plugins.smartunique.uniSimple('node'),
|
|
location: 'nbg1',
|
|
type: 'cpx41',
|
|
labels: {
|
|
clusterId: cluster.id,
|
|
priority: '1',
|
|
},
|
|
userData: await this.curlfreshInstance.getServerUserData(cluster),
|
|
});
|
|
|
|
// First create BareMetal record
|
|
const baremetal = await this.cloudlyRef.baremetalManager.createBaremetalFromHetznerServer(hetznerServer);
|
|
|
|
const newNode = await ClusterNode.createFromHetznerServer(hetznerServer, cluster.id, baremetal.id);
|
|
await baremetal.assignNode(newNode.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public async getNodesByCluster(clusterArg: Cluster) {
|
|
const results = await this.CClusterNode.getInstances({
|
|
data: {
|
|
clusterId: clusterArg.id,
|
|
},
|
|
});
|
|
return results;
|
|
}
|
|
}
|