- Added base interface and abstract class for platform service providers. - Created MinIOProvider class for S3-compatible storage with deployment, provisioning, and deprovisioning functionalities. - Implemented MongoDBProvider class for MongoDB service with similar capabilities. - Introduced error handling utilities for better error management. - Developed TokensComponent for managing registry tokens in the UI, including creation, deletion, and display of tokens.
247 lines
8.2 KiB
TypeScript
247 lines
8.2 KiB
TypeScript
/**
|
|
* MongoDB Platform Service Provider
|
|
*/
|
|
|
|
import { BasePlatformServiceProvider } from './base.ts';
|
|
import type {
|
|
IService,
|
|
IPlatformResource,
|
|
IPlatformServiceConfig,
|
|
IProvisionedResource,
|
|
IEnvVarMapping,
|
|
TPlatformServiceType,
|
|
TPlatformResourceType,
|
|
} from '../../../types.ts';
|
|
import { logger } from '../../../logging.ts';
|
|
import { credentialEncryption } from '../../encryption.ts';
|
|
import type { Onebox } from '../../onebox.ts';
|
|
|
|
export class MongoDBProvider extends BasePlatformServiceProvider {
|
|
readonly type: TPlatformServiceType = 'mongodb';
|
|
readonly displayName = 'MongoDB';
|
|
readonly resourceTypes: TPlatformResourceType[] = ['database'];
|
|
|
|
constructor(oneboxRef: Onebox) {
|
|
super(oneboxRef);
|
|
}
|
|
|
|
getDefaultConfig(): IPlatformServiceConfig {
|
|
return {
|
|
image: 'mongo:7',
|
|
port: 27017,
|
|
volumes: ['/var/lib/onebox/mongodb:/data/db'],
|
|
environment: {
|
|
MONGO_INITDB_ROOT_USERNAME: 'admin',
|
|
// Password will be generated and stored encrypted
|
|
},
|
|
};
|
|
}
|
|
|
|
getEnvVarMappings(): IEnvVarMapping[] {
|
|
return [
|
|
{ envVar: 'MONGODB_URI', credentialPath: 'connectionString' },
|
|
{ envVar: 'MONGODB_HOST', credentialPath: 'host' },
|
|
{ envVar: 'MONGODB_PORT', credentialPath: 'port' },
|
|
{ envVar: 'MONGODB_DATABASE', credentialPath: 'database' },
|
|
{ envVar: 'MONGODB_USERNAME', credentialPath: 'username' },
|
|
{ envVar: 'MONGODB_PASSWORD', credentialPath: 'password' },
|
|
];
|
|
}
|
|
|
|
async deployContainer(): Promise<string> {
|
|
const config = this.getDefaultConfig();
|
|
const containerName = this.getContainerName();
|
|
|
|
// Generate admin password
|
|
const adminPassword = credentialEncryption.generatePassword(32);
|
|
|
|
// Store admin credentials encrypted in the platform service record
|
|
const adminCredentials = {
|
|
username: 'admin',
|
|
password: adminPassword,
|
|
};
|
|
|
|
logger.info(`Deploying MongoDB platform service as ${containerName}...`);
|
|
|
|
// Ensure data directory exists
|
|
try {
|
|
await Deno.mkdir('/var/lib/onebox/mongodb', { recursive: true });
|
|
} catch (e) {
|
|
// Directory might already exist
|
|
if (!(e instanceof Deno.errors.AlreadyExists)) {
|
|
logger.warn(`Could not create MongoDB data directory: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
// Create container using Docker API
|
|
const envVars = [
|
|
`MONGO_INITDB_ROOT_USERNAME=${adminCredentials.username}`,
|
|
`MONGO_INITDB_ROOT_PASSWORD=${adminCredentials.password}`,
|
|
];
|
|
|
|
// Use Docker to create the container
|
|
const containerId = await this.oneboxRef.docker.createPlatformContainer({
|
|
name: containerName,
|
|
image: config.image,
|
|
port: config.port,
|
|
env: envVars,
|
|
volumes: config.volumes,
|
|
network: this.getNetworkName(),
|
|
});
|
|
|
|
// Store encrypted admin credentials
|
|
const encryptedCreds = await credentialEncryption.encrypt(adminCredentials);
|
|
const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
|
|
if (platformService) {
|
|
this.oneboxRef.database.updatePlatformService(platformService.id!, {
|
|
containerId,
|
|
adminCredentialsEncrypted: encryptedCreds,
|
|
status: 'starting',
|
|
});
|
|
}
|
|
|
|
logger.success(`MongoDB container created: ${containerId}`);
|
|
return containerId;
|
|
}
|
|
|
|
async stopContainer(containerId: string): Promise<void> {
|
|
logger.info(`Stopping MongoDB container ${containerId}...`);
|
|
await this.oneboxRef.docker.stopContainer(containerId);
|
|
logger.success('MongoDB container stopped');
|
|
}
|
|
|
|
async healthCheck(): Promise<boolean> {
|
|
try {
|
|
const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
|
|
if (!platformService || !platformService.adminCredentialsEncrypted) {
|
|
return false;
|
|
}
|
|
|
|
const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
|
const containerName = this.getContainerName();
|
|
|
|
// Try to connect to MongoDB using mongosh ping
|
|
const { MongoClient } = await import('npm:mongodb@6');
|
|
const uri = `mongodb://${adminCreds.username}:${adminCreds.password}@${containerName}:27017/?authSource=admin`;
|
|
|
|
const client = new MongoClient(uri, {
|
|
serverSelectionTimeoutMS: 5000,
|
|
connectTimeoutMS: 5000,
|
|
});
|
|
|
|
await client.connect();
|
|
await client.db('admin').command({ ping: 1 });
|
|
await client.close();
|
|
|
|
return true;
|
|
} catch (error) {
|
|
logger.debug(`MongoDB health check failed: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async provisionResource(userService: IService): Promise<IProvisionedResource> {
|
|
const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
|
|
if (!platformService || !platformService.adminCredentialsEncrypted) {
|
|
throw new Error('MongoDB platform service not found or not configured');
|
|
}
|
|
|
|
const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
|
const containerName = this.getContainerName();
|
|
|
|
// Generate resource names and credentials
|
|
const dbName = this.generateResourceName(userService.name);
|
|
const username = this.generateResourceName(userService.name);
|
|
const password = credentialEncryption.generatePassword(32);
|
|
|
|
logger.info(`Provisioning MongoDB database '${dbName}' for service '${userService.name}'...`);
|
|
|
|
// Connect to MongoDB and create database/user
|
|
const { MongoClient } = await import('npm:mongodb@6');
|
|
const adminUri = `mongodb://${adminCreds.username}:${adminCreds.password}@${containerName}:27017/?authSource=admin`;
|
|
|
|
const client = new MongoClient(adminUri);
|
|
await client.connect();
|
|
|
|
try {
|
|
// Create the database by switching to it (MongoDB creates on first write)
|
|
const db = client.db(dbName);
|
|
|
|
// Create a collection to ensure the database exists
|
|
await db.createCollection('_onebox_init');
|
|
|
|
// Create user with readWrite access to this database
|
|
await db.command({
|
|
createUser: username,
|
|
pwd: password,
|
|
roles: [{ role: 'readWrite', db: dbName }],
|
|
});
|
|
|
|
logger.success(`MongoDB database '${dbName}' provisioned with user '${username}'`);
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
|
|
// Build the credentials and env vars
|
|
const credentials: Record<string, string> = {
|
|
host: containerName,
|
|
port: '27017',
|
|
database: dbName,
|
|
username,
|
|
password,
|
|
connectionString: `mongodb://${username}:${password}@${containerName}:27017/${dbName}?authSource=${dbName}`,
|
|
};
|
|
|
|
// Map credentials to env vars
|
|
const envVars: Record<string, string> = {};
|
|
for (const mapping of this.getEnvVarMappings()) {
|
|
if (credentials[mapping.credentialPath]) {
|
|
envVars[mapping.envVar] = credentials[mapping.credentialPath];
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: 'database',
|
|
name: dbName,
|
|
credentials,
|
|
envVars,
|
|
};
|
|
}
|
|
|
|
async deprovisionResource(resource: IPlatformResource, credentials: Record<string, string>): Promise<void> {
|
|
const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
|
|
if (!platformService || !platformService.adminCredentialsEncrypted) {
|
|
throw new Error('MongoDB platform service not found or not configured');
|
|
}
|
|
|
|
const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
|
const containerName = this.getContainerName();
|
|
|
|
logger.info(`Deprovisioning MongoDB database '${resource.resourceName}'...`);
|
|
|
|
const { MongoClient } = await import('npm:mongodb@6');
|
|
const adminUri = `mongodb://${adminCreds.username}:${adminCreds.password}@${containerName}:27017/?authSource=admin`;
|
|
|
|
const client = new MongoClient(adminUri);
|
|
await client.connect();
|
|
|
|
try {
|
|
const db = client.db(resource.resourceName);
|
|
|
|
// Drop the user
|
|
try {
|
|
await db.command({ dropUser: credentials.username });
|
|
logger.info(`Dropped MongoDB user '${credentials.username}'`);
|
|
} catch (e) {
|
|
logger.warn(`Could not drop MongoDB user: ${e.message}`);
|
|
}
|
|
|
|
// Drop the database
|
|
await db.dropDatabase();
|
|
logger.success(`MongoDB database '${resource.resourceName}' dropped`);
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
}
|
|
}
|