feat: Implement platform service providers for MinIO and MongoDB
- 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.
This commit is contained in:
123
ts/classes/platform-services/providers/base.ts
Normal file
123
ts/classes/platform-services/providers/base.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Base interface and types for platform service providers
|
||||
*/
|
||||
|
||||
import type {
|
||||
IService,
|
||||
IPlatformService,
|
||||
IPlatformResource,
|
||||
IPlatformServiceConfig,
|
||||
IProvisionedResource,
|
||||
IEnvVarMapping,
|
||||
TPlatformServiceType,
|
||||
TPlatformResourceType,
|
||||
} from '../../../types.ts';
|
||||
import type { Onebox } from '../../onebox.ts';
|
||||
|
||||
/**
|
||||
* Interface that all platform service providers must implement
|
||||
*/
|
||||
export interface IPlatformServiceProvider {
|
||||
/** Unique identifier for this provider type */
|
||||
readonly type: TPlatformServiceType;
|
||||
|
||||
/** Human-readable display name */
|
||||
readonly displayName: string;
|
||||
|
||||
/** Resource types this provider can provision */
|
||||
readonly resourceTypes: TPlatformResourceType[];
|
||||
|
||||
/**
|
||||
* Get the default configuration for this platform service
|
||||
*/
|
||||
getDefaultConfig(): IPlatformServiceConfig;
|
||||
|
||||
/**
|
||||
* Deploy the platform service container
|
||||
* @returns The container ID
|
||||
*/
|
||||
deployContainer(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Stop the platform service container
|
||||
*/
|
||||
stopContainer(containerId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Check if the platform service is healthy and ready to accept connections
|
||||
*/
|
||||
healthCheck(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Provision a resource for a user service (e.g., create database, bucket)
|
||||
* @param userService The user service requesting the resource
|
||||
* @returns Provisioned resource with credentials and env var mappings
|
||||
*/
|
||||
provisionResource(userService: IService): Promise<IProvisionedResource>;
|
||||
|
||||
/**
|
||||
* Deprovision a resource (e.g., drop database, delete bucket)
|
||||
* @param resource The resource to deprovision
|
||||
*/
|
||||
deprovisionResource(resource: IPlatformResource, credentials: Record<string, string>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get the environment variable mappings for this provider
|
||||
*/
|
||||
getEnvVarMappings(): IEnvVarMapping[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for platform service providers with common functionality
|
||||
*/
|
||||
export abstract class BasePlatformServiceProvider implements IPlatformServiceProvider {
|
||||
abstract readonly type: TPlatformServiceType;
|
||||
abstract readonly displayName: string;
|
||||
abstract readonly resourceTypes: TPlatformResourceType[];
|
||||
|
||||
protected oneboxRef: Onebox;
|
||||
|
||||
constructor(oneboxRef: Onebox) {
|
||||
this.oneboxRef = oneboxRef;
|
||||
}
|
||||
|
||||
abstract getDefaultConfig(): IPlatformServiceConfig;
|
||||
abstract deployContainer(): Promise<string>;
|
||||
abstract stopContainer(containerId: string): Promise<void>;
|
||||
abstract healthCheck(): Promise<boolean>;
|
||||
abstract provisionResource(userService: IService): Promise<IProvisionedResource>;
|
||||
abstract deprovisionResource(resource: IPlatformResource, credentials: Record<string, string>): Promise<void>;
|
||||
abstract getEnvVarMappings(): IEnvVarMapping[];
|
||||
|
||||
/**
|
||||
* Get the internal Docker network name for platform services
|
||||
*/
|
||||
protected getNetworkName(): string {
|
||||
return 'onebox-network';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the container name for this platform service
|
||||
*/
|
||||
protected getContainerName(): string {
|
||||
return `onebox-${this.type}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a resource name from a user service name
|
||||
*/
|
||||
protected generateResourceName(serviceName: string, prefix: string = 'onebox'): string {
|
||||
// Replace dashes with underscores for database compatibility
|
||||
const sanitized = serviceName.replace(/-/g, '_');
|
||||
return `${prefix}_${sanitized}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a bucket name from a user service name
|
||||
*/
|
||||
protected generateBucketName(serviceName: string, prefix: string = 'onebox'): string {
|
||||
// Buckets use dashes, lowercase
|
||||
const sanitized = serviceName.toLowerCase().replace(/_/g, '-');
|
||||
return `${prefix}-${sanitized}`;
|
||||
}
|
||||
}
|
||||
299
ts/classes/platform-services/providers/minio.ts
Normal file
299
ts/classes/platform-services/providers/minio.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* MinIO (S3-compatible) 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 MinioProvider extends BasePlatformServiceProvider {
|
||||
readonly type: TPlatformServiceType = 'minio';
|
||||
readonly displayName = 'S3 Storage (MinIO)';
|
||||
readonly resourceTypes: TPlatformResourceType[] = ['bucket'];
|
||||
|
||||
constructor(oneboxRef: Onebox) {
|
||||
super(oneboxRef);
|
||||
}
|
||||
|
||||
getDefaultConfig(): IPlatformServiceConfig {
|
||||
return {
|
||||
image: 'minio/minio:latest',
|
||||
port: 9000,
|
||||
volumes: ['/var/lib/onebox/minio:/data'],
|
||||
command: 'server /data --console-address :9001',
|
||||
environment: {
|
||||
MINIO_ROOT_USER: 'admin',
|
||||
// Password will be generated and stored encrypted
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getEnvVarMappings(): IEnvVarMapping[] {
|
||||
return [
|
||||
{ envVar: 'S3_ENDPOINT', credentialPath: 'endpoint' },
|
||||
{ envVar: 'S3_BUCKET', credentialPath: 'bucket' },
|
||||
{ envVar: 'S3_ACCESS_KEY', credentialPath: 'accessKey' },
|
||||
{ envVar: 'S3_SECRET_KEY', credentialPath: 'secretKey' },
|
||||
{ envVar: 'S3_REGION', credentialPath: 'region' },
|
||||
// AWS SDK compatible names
|
||||
{ envVar: 'AWS_ACCESS_KEY_ID', credentialPath: 'accessKey' },
|
||||
{ envVar: 'AWS_SECRET_ACCESS_KEY', credentialPath: 'secretKey' },
|
||||
{ envVar: 'AWS_ENDPOINT_URL', credentialPath: 'endpoint' },
|
||||
{ envVar: 'AWS_REGION', credentialPath: 'region' },
|
||||
];
|
||||
}
|
||||
|
||||
async deployContainer(): Promise<string> {
|
||||
const config = this.getDefaultConfig();
|
||||
const containerName = this.getContainerName();
|
||||
|
||||
// Generate admin credentials
|
||||
const adminUser = 'admin';
|
||||
const adminPassword = credentialEncryption.generatePassword(32);
|
||||
|
||||
const adminCredentials = {
|
||||
username: adminUser,
|
||||
password: adminPassword,
|
||||
};
|
||||
|
||||
logger.info(`Deploying MinIO platform service as ${containerName}...`);
|
||||
|
||||
// Ensure data directory exists
|
||||
try {
|
||||
await Deno.mkdir('/var/lib/onebox/minio', { recursive: true });
|
||||
} catch (e) {
|
||||
if (!(e instanceof Deno.errors.AlreadyExists)) {
|
||||
logger.warn(`Could not create MinIO data directory: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create container using Docker API
|
||||
const envVars = [
|
||||
`MINIO_ROOT_USER=${adminCredentials.username}`,
|
||||
`MINIO_ROOT_PASSWORD=${adminCredentials.password}`,
|
||||
];
|
||||
|
||||
const containerId = await this.oneboxRef.docker.createPlatformContainer({
|
||||
name: containerName,
|
||||
image: config.image,
|
||||
port: config.port,
|
||||
env: envVars,
|
||||
volumes: config.volumes,
|
||||
network: this.getNetworkName(),
|
||||
command: config.command?.split(' '),
|
||||
exposePorts: [9000, 9001], // API and Console ports
|
||||
});
|
||||
|
||||
// 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(`MinIO container created: ${containerId}`);
|
||||
return containerId;
|
||||
}
|
||||
|
||||
async stopContainer(containerId: string): Promise<void> {
|
||||
logger.info(`Stopping MinIO container ${containerId}...`);
|
||||
await this.oneboxRef.docker.stopContainer(containerId);
|
||||
logger.success('MinIO container stopped');
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<boolean> {
|
||||
try {
|
||||
const containerName = this.getContainerName();
|
||||
const endpoint = `http://${containerName}:9000/minio/health/live`;
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'GET',
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
logger.debug(`MinIO 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('MinIO platform service not found or not configured');
|
||||
}
|
||||
|
||||
const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
||||
const containerName = this.getContainerName();
|
||||
|
||||
// Generate bucket name and credentials
|
||||
const bucketName = this.generateBucketName(userService.name);
|
||||
const accessKey = credentialEncryption.generateAccessKey(20);
|
||||
const secretKey = credentialEncryption.generateSecretKey(40);
|
||||
|
||||
logger.info(`Provisioning MinIO bucket '${bucketName}' for service '${userService.name}'...`);
|
||||
|
||||
const endpoint = `http://${containerName}:9000`;
|
||||
|
||||
// Import AWS S3 client
|
||||
const { S3Client, CreateBucketCommand, PutBucketPolicyCommand } = await import('npm:@aws-sdk/client-s3@3');
|
||||
|
||||
// Create S3 client with admin credentials
|
||||
const s3Client = new S3Client({
|
||||
endpoint,
|
||||
region: 'us-east-1',
|
||||
credentials: {
|
||||
accessKeyId: adminCreds.username,
|
||||
secretAccessKey: adminCreds.password,
|
||||
},
|
||||
forcePathStyle: true,
|
||||
});
|
||||
|
||||
// Create the bucket
|
||||
try {
|
||||
await s3Client.send(new CreateBucketCommand({
|
||||
Bucket: bucketName,
|
||||
}));
|
||||
logger.info(`Created MinIO bucket '${bucketName}'`);
|
||||
} catch (e: any) {
|
||||
if (e.name !== 'BucketAlreadyOwnedByYou' && e.name !== 'BucketAlreadyExists') {
|
||||
throw e;
|
||||
}
|
||||
logger.warn(`Bucket '${bucketName}' already exists`);
|
||||
}
|
||||
|
||||
// Create service account/access key using MinIO Admin API
|
||||
// MinIO Admin API requires mc client or direct API calls
|
||||
// For simplicity, we'll use root credentials and bucket policy isolation
|
||||
// In production, you'd use MinIO's Admin API to create service accounts
|
||||
|
||||
// Set bucket policy to allow access only with this bucket's credentials
|
||||
const bucketPolicy = {
|
||||
Version: '2012-10-17',
|
||||
Statement: [
|
||||
{
|
||||
Effect: 'Allow',
|
||||
Principal: { AWS: ['*'] },
|
||||
Action: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject', 's3:ListBucket'],
|
||||
Resource: [
|
||||
`arn:aws:s3:::${bucketName}`,
|
||||
`arn:aws:s3:::${bucketName}/*`,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
await s3Client.send(new PutBucketPolicyCommand({
|
||||
Bucket: bucketName,
|
||||
Policy: JSON.stringify(bucketPolicy),
|
||||
}));
|
||||
logger.info(`Set bucket policy for '${bucketName}'`);
|
||||
} catch (e) {
|
||||
logger.warn(`Could not set bucket policy: ${e.message}`);
|
||||
}
|
||||
|
||||
// Note: For proper per-service credentials, MinIO Admin API should be used
|
||||
// For now, we're providing the bucket with root access
|
||||
// TODO: Implement MinIO service account creation
|
||||
logger.warn('Using root credentials for MinIO access. Consider implementing service accounts for production.');
|
||||
|
||||
const credentials: Record<string, string> = {
|
||||
endpoint,
|
||||
bucket: bucketName,
|
||||
accessKey: adminCreds.username, // Using root for now
|
||||
secretKey: adminCreds.password,
|
||||
region: 'us-east-1',
|
||||
};
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
logger.success(`MinIO bucket '${bucketName}' provisioned`);
|
||||
|
||||
return {
|
||||
type: 'bucket',
|
||||
name: bucketName,
|
||||
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('MinIO platform service not found or not configured');
|
||||
}
|
||||
|
||||
const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
|
||||
const containerName = this.getContainerName();
|
||||
const endpoint = `http://${containerName}:9000`;
|
||||
|
||||
logger.info(`Deprovisioning MinIO bucket '${resource.resourceName}'...`);
|
||||
|
||||
const { S3Client, DeleteBucketCommand, ListObjectsV2Command, DeleteObjectsCommand } = await import('npm:@aws-sdk/client-s3@3');
|
||||
|
||||
const s3Client = new S3Client({
|
||||
endpoint,
|
||||
region: 'us-east-1',
|
||||
credentials: {
|
||||
accessKeyId: adminCreds.username,
|
||||
secretAccessKey: adminCreds.password,
|
||||
},
|
||||
forcePathStyle: true,
|
||||
});
|
||||
|
||||
try {
|
||||
// First, delete all objects in the bucket
|
||||
let continuationToken: string | undefined;
|
||||
do {
|
||||
const listResponse = await s3Client.send(new ListObjectsV2Command({
|
||||
Bucket: resource.resourceName,
|
||||
ContinuationToken: continuationToken,
|
||||
}));
|
||||
|
||||
if (listResponse.Contents && listResponse.Contents.length > 0) {
|
||||
await s3Client.send(new DeleteObjectsCommand({
|
||||
Bucket: resource.resourceName,
|
||||
Delete: {
|
||||
Objects: listResponse.Contents.map(obj => ({ Key: obj.Key! })),
|
||||
},
|
||||
}));
|
||||
logger.info(`Deleted ${listResponse.Contents.length} objects from bucket`);
|
||||
}
|
||||
|
||||
continuationToken = listResponse.IsTruncated ? listResponse.NextContinuationToken : undefined;
|
||||
} while (continuationToken);
|
||||
|
||||
// Now delete the bucket
|
||||
await s3Client.send(new DeleteBucketCommand({
|
||||
Bucket: resource.resourceName,
|
||||
}));
|
||||
|
||||
logger.success(`MinIO bucket '${resource.resourceName}' deleted`);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to delete MinIO bucket: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
246
ts/classes/platform-services/providers/mongodb.ts
Normal file
246
ts/classes/platform-services/providers/mongodb.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user