176 lines
5.5 KiB
TypeScript
176 lines
5.5 KiB
TypeScript
|
import * as plugins from '../plugins.js';
|
||
|
import { Cloudly } from '../classes.cloudly.js';
|
||
|
import { BareMetal } from './classes.baremetal.js';
|
||
|
import { logger } from '../logger.js';
|
||
|
|
||
|
export class CloudlyBaremetalManager {
|
||
|
public cloudlyRef: Cloudly;
|
||
|
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||
|
|
||
|
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
|
||
|
|
||
|
public get db() {
|
||
|
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||
|
}
|
||
|
public CBareMetal = plugins.smartdata.setDefaultManagerForDoc(this, BareMetal);
|
||
|
|
||
|
constructor(cloudlyRefArg: Cloudly) {
|
||
|
this.cloudlyRef = cloudlyRefArg;
|
||
|
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||
|
|
||
|
// API endpoint to get baremetal servers
|
||
|
this.typedRouter.addTypedHandler(
|
||
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.baremetal.IRequest_Any_Cloudly_GetBaremetalServers>(
|
||
|
'getBaremetalServers',
|
||
|
async (requestData) => {
|
||
|
const baremetals = await this.getAllBaremetals();
|
||
|
return {
|
||
|
baremetals: await Promise.all(
|
||
|
baremetals.map((baremetal) => baremetal.createSavableObject())
|
||
|
),
|
||
|
};
|
||
|
},
|
||
|
),
|
||
|
);
|
||
|
|
||
|
// API endpoint to control baremetal via IPMI
|
||
|
this.typedRouter.addTypedHandler(
|
||
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.baremetal.IRequest_Any_Cloudly_ControlBaremetal>(
|
||
|
'controlBaremetal',
|
||
|
async (requestData) => {
|
||
|
const baremetal = await this.CBareMetal.getInstance({
|
||
|
id: requestData.baremetalId,
|
||
|
});
|
||
|
|
||
|
if (!baremetal) {
|
||
|
return {
|
||
|
success: false,
|
||
|
message: 'BareMetal not found',
|
||
|
};
|
||
|
}
|
||
|
|
||
|
let success = false;
|
||
|
switch (requestData.action) {
|
||
|
case 'powerOn':
|
||
|
success = await baremetal.powerOn();
|
||
|
break;
|
||
|
case 'powerOff':
|
||
|
success = await baremetal.powerOff();
|
||
|
break;
|
||
|
case 'reset':
|
||
|
success = await baremetal.reset();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
success,
|
||
|
message: success ? `Action ${requestData.action} completed` : `Action ${requestData.action} failed`,
|
||
|
};
|
||
|
},
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public async start() {
|
||
|
const hetznerToken = await this.cloudlyRef.settingsManager.getSetting('hetznerToken');
|
||
|
|
||
|
if (hetznerToken) {
|
||
|
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(hetznerToken);
|
||
|
}
|
||
|
|
||
|
logger.log('info', 'BareMetal manager started');
|
||
|
}
|
||
|
|
||
|
public async stop() {
|
||
|
logger.log('info', 'BareMetal manager stopped');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get all baremetal servers
|
||
|
*/
|
||
|
public async getAllBaremetals(): Promise<BareMetal[]> {
|
||
|
const baremetals = await this.CBareMetal.getInstances({});
|
||
|
return baremetals;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get baremetal by ID
|
||
|
*/
|
||
|
public async getBaremetalById(id: string): Promise<BareMetal | null> {
|
||
|
const baremetal = await this.CBareMetal.getInstance({
|
||
|
id,
|
||
|
});
|
||
|
return baremetal;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get baremetals by provider
|
||
|
*/
|
||
|
public async getBaremetalsByProvider(provider: 'hetzner' | 'aws' | 'digitalocean' | 'onpremise'): Promise<BareMetal[]> {
|
||
|
const baremetals = await this.CBareMetal.getInstances({
|
||
|
data: {
|
||
|
provider,
|
||
|
},
|
||
|
});
|
||
|
return baremetals;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create baremetal from Hetzner server
|
||
|
*/
|
||
|
public async createBaremetalFromHetznerServer(hetznerServer: plugins.hetznercloud.HetznerServer): Promise<BareMetal> {
|
||
|
// Check if baremetal already exists for this Hetzner server
|
||
|
const existingBaremetals = await this.CBareMetal.getInstances({});
|
||
|
for (const baremetal of existingBaremetals) {
|
||
|
if (baremetal.data.providerMetadata?.hetznerServerId === hetznerServer.data.id) {
|
||
|
logger.log('info', `BareMetal already exists for Hetzner server ${hetznerServer.data.id}`);
|
||
|
return baremetal;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create new baremetal
|
||
|
const newBaremetal = await BareMetal.createFromHetznerServer(hetznerServer);
|
||
|
logger.log('success', `Created new BareMetal ${newBaremetal.id} from Hetzner server ${hetznerServer.data.id}`);
|
||
|
return newBaremetal;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sync baremetals with Hetzner
|
||
|
*/
|
||
|
public async syncWithHetzner() {
|
||
|
if (!this.hetznerAccount) {
|
||
|
logger.log('warn', 'Cannot sync with Hetzner - no account configured');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const hetznerServers = await this.hetznerAccount.getServers();
|
||
|
|
||
|
for (const hetznerServer of hetznerServers) {
|
||
|
await this.createBaremetalFromHetznerServer(hetznerServer);
|
||
|
}
|
||
|
|
||
|
logger.log('success', `Synced ${hetznerServers.length} servers from Hetzner`);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Provision a new baremetal server
|
||
|
*/
|
||
|
public async provisionBaremetal(options: {
|
||
|
provider: 'hetzner' | 'aws' | 'digitalocean';
|
||
|
location: any; // TODO: Import proper type from hetznercloud when available
|
||
|
type: any; // TODO: Import proper type from hetznercloud when available
|
||
|
}): Promise<BareMetal> {
|
||
|
if (options.provider === 'hetzner' && this.hetznerAccount) {
|
||
|
const hetznerServer = await this.hetznerAccount.createServer({
|
||
|
name: plugins.smartunique.uniSimple('baremetal'),
|
||
|
location: options.location,
|
||
|
type: options.type,
|
||
|
});
|
||
|
|
||
|
const baremetal = await this.createBaremetalFromHetznerServer(hetznerServer);
|
||
|
return baremetal;
|
||
|
}
|
||
|
|
||
|
throw new Error(`Provider ${options.provider} not supported or not configured`);
|
||
|
}
|
||
|
}
|