/** * 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 { getErrorMessage } from '../../../utils/error.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 { 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: ${getErrorMessage(e)}`); } } // 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 { logger.info(`Stopping MongoDB container ${containerId}...`); await this.oneboxRef.docker.stopContainer(containerId); logger.success('MongoDB container stopped'); } async healthCheck(): Promise { 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: ${getErrorMessage(error)}`); return false; } } async provisionResource(userService: IService): Promise { 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 = { 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 = {}; 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): Promise { 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: ${getErrorMessage(e)}`); } // Drop the database await db.dropDatabase(); logger.success(`MongoDB database '${resource.resourceName}' dropped`); } finally { await client.close(); } } }