fix: restore platform backup data
This commit is contained in:
@@ -206,14 +206,12 @@ export class BackupManager {
|
|||||||
for (const resourceType of resourceTypes) {
|
for (const resourceType of resourceTypes) {
|
||||||
const dataDir = `${tempDir}/data/${resourceType}`;
|
const dataDir = `${tempDir}/data/${resourceType}`;
|
||||||
try {
|
try {
|
||||||
for await (const entry of Deno.readDir(dataDir)) {
|
for await (const filePath of this.walkFiles(dataDir)) {
|
||||||
if (entry.isFile) {
|
items.push({
|
||||||
items.push({
|
stream: plugins.nodeFs.createReadStream(filePath),
|
||||||
stream: plugins.nodeFs.createReadStream(`${dataDir}/${entry.name}`),
|
name: plugins.path.relative(tempDir, filePath).replaceAll('\\', '/'),
|
||||||
name: `data/${resourceType}/${entry.name}`,
|
type: 'data',
|
||||||
type: 'data',
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Directory may not exist if export produced no files
|
// Directory may not exist if export produced no files
|
||||||
@@ -819,7 +817,7 @@ export class BackupManager {
|
|||||||
throw new Error('MongoDB service not running');
|
throw new Error('MongoDB service not running');
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectionUri = credentials.connectionUri || credentials.MONGODB_URI;
|
const connectionUri = credentials.connectionUri || credentials.connectionString || credentials.MONGODB_URI;
|
||||||
if (!connectionUri) {
|
if (!connectionUri) {
|
||||||
throw new Error('MongoDB connection URI not found in credentials');
|
throw new Error('MongoDB connection URI not found in credentials');
|
||||||
}
|
}
|
||||||
@@ -836,19 +834,8 @@ export class BackupManager {
|
|||||||
throw new Error(`mongodump failed: ${result.stderr}`);
|
throw new Error(`mongodump failed: ${result.stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = await this.oneboxRef.docker.getContainerById(mongoService.containerId);
|
|
||||||
if (!container) {
|
|
||||||
throw new Error('MongoDB container not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyResult = await this.oneboxRef.docker.execInContainer(mongoService.containerId, [
|
|
||||||
'cat',
|
|
||||||
archivePath,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const localPath = `${dataDir}/${resource.resourceName}.archive`;
|
const localPath = `${dataDir}/${resource.resourceName}.archive`;
|
||||||
const encoder = new TextEncoder();
|
await this.copyFromContainer(mongoService.containerId, archivePath, localPath);
|
||||||
await Deno.writeFile(localPath, encoder.encode(copyResult.stdout));
|
|
||||||
|
|
||||||
await this.oneboxRef.docker.execInContainer(mongoService.containerId, ['rm', archivePath]);
|
await this.oneboxRef.docker.execInContainer(mongoService.containerId, ['rm', archivePath]);
|
||||||
|
|
||||||
@@ -868,7 +855,7 @@ export class BackupManager {
|
|||||||
const bucketDir = `${dataDir}/${resource.resourceName}`;
|
const bucketDir = `${dataDir}/${resource.resourceName}`;
|
||||||
await Deno.mkdir(bucketDir, { recursive: true });
|
await Deno.mkdir(bucketDir, { recursive: true });
|
||||||
|
|
||||||
const s3Info = this.getS3ConnectionInfo(credentials);
|
const s3Info = await this.getReachableS3ConnectionInfo(credentials, resource.platformServiceId);
|
||||||
const s3Client = this.createS3Client(s3Info);
|
const s3Client = this.createS3Client(s3Info);
|
||||||
let objectCount = 0;
|
let objectCount = 0;
|
||||||
let continuationToken: string | undefined;
|
let continuationToken: string | undefined;
|
||||||
@@ -1137,6 +1124,36 @@ export class BackupManager {
|
|||||||
return imageName;
|
return imageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async copyFromContainer(
|
||||||
|
containerId: string,
|
||||||
|
containerPath: string,
|
||||||
|
outputPath: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.runDockerCp([`${containerId}:${containerPath}`, outputPath], 'docker cp from container failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async copyToContainer(
|
||||||
|
inputPath: string,
|
||||||
|
containerId: string,
|
||||||
|
containerPath: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.runDockerCp([inputPath, `${containerId}:${containerPath}`], 'docker cp to container failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runDockerCp(args: string[], errorMessage: string): Promise<void> {
|
||||||
|
const command = new Deno.Command('docker', {
|
||||||
|
args: ['cp', ...args],
|
||||||
|
stdout: 'piped',
|
||||||
|
stderr: 'piped',
|
||||||
|
});
|
||||||
|
const result = await command.output();
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
const stderr = new TextDecoder().decode(result.stderr).trim();
|
||||||
|
throw new Error(`${errorMessage}: ${stderr}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore platform resources for a service
|
* Restore platform resources for a service
|
||||||
*/
|
*/
|
||||||
@@ -1241,22 +1258,14 @@ export class BackupManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const archivePath = `${dataDir}/${backupResourceName}.archive`;
|
const archivePath = `${dataDir}/${backupResourceName}.archive`;
|
||||||
const connectionUri = credentials.connectionUri || credentials.MONGODB_URI;
|
const connectionUri = credentials.connectionUri || credentials.connectionString || credentials.MONGODB_URI;
|
||||||
|
|
||||||
if (!connectionUri) {
|
if (!connectionUri) {
|
||||||
throw new Error('MongoDB connection URI not found');
|
throw new Error('MongoDB connection URI not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const archiveData = await Deno.readFile(archivePath);
|
|
||||||
const containerArchivePath = `/tmp/${resource.resourceName}.archive`;
|
const containerArchivePath = `/tmp/${resource.resourceName}.archive`;
|
||||||
|
await this.copyToContainer(archivePath, mongoService.containerId, containerArchivePath);
|
||||||
const base64Data = btoa(String.fromCharCode(...archiveData));
|
|
||||||
|
|
||||||
await this.oneboxRef.docker.execInContainer(mongoService.containerId, [
|
|
||||||
'bash',
|
|
||||||
'-c',
|
|
||||||
`echo '${base64Data}' | base64 -d > ${containerArchivePath}`,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const result = await this.oneboxRef.docker.execInContainer(mongoService.containerId, [
|
const result = await this.oneboxRef.docker.execInContainer(mongoService.containerId, [
|
||||||
'mongorestore',
|
'mongorestore',
|
||||||
@@ -1288,7 +1297,7 @@ export class BackupManager {
|
|||||||
|
|
||||||
const bucketDir = `${dataDir}/${backupResourceName}`;
|
const bucketDir = `${dataDir}/${backupResourceName}`;
|
||||||
|
|
||||||
const s3Info = this.getS3ConnectionInfo(credentials);
|
const s3Info = await this.getReachableS3ConnectionInfo(credentials, resource.platformServiceId);
|
||||||
const s3Client = this.createS3Client(s3Info);
|
const s3Client = this.createS3Client(s3Info);
|
||||||
|
|
||||||
let uploadedCount = 0;
|
let uploadedCount = 0;
|
||||||
@@ -1619,6 +1628,39 @@ export class BackupManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getReachableS3ConnectionInfo(
|
||||||
|
credentials: Record<string, string>,
|
||||||
|
platformServiceId: number,
|
||||||
|
): Promise<IS3ConnectionInfo> {
|
||||||
|
const s3Info = this.getS3ConnectionInfo(credentials);
|
||||||
|
|
||||||
|
let endpointUrl: URL;
|
||||||
|
try {
|
||||||
|
endpointUrl = new URL(s3Info.endpoint);
|
||||||
|
} catch {
|
||||||
|
return s3Info;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointUrl.hostname !== 'onebox-minio') {
|
||||||
|
return s3Info;
|
||||||
|
}
|
||||||
|
|
||||||
|
const platformService = this.oneboxRef.database.getPlatformServiceById(platformServiceId);
|
||||||
|
const hostPort = platformService?.containerId
|
||||||
|
? await this.oneboxRef.docker.getContainerHostPort(platformService.containerId, 9000)
|
||||||
|
: null;
|
||||||
|
if (!hostPort) {
|
||||||
|
return s3Info;
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointUrl.hostname = '127.0.0.1';
|
||||||
|
endpointUrl.port = String(hostPort);
|
||||||
|
return {
|
||||||
|
...s3Info,
|
||||||
|
endpoint: endpointUrl.toString().replace(/\/$/, ''),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private createS3Client(s3Info: IS3ConnectionInfo) {
|
private createS3Client(s3Info: IS3ConnectionInfo) {
|
||||||
return new plugins.awsS3.S3Client({
|
return new plugins.awsS3.S3Client({
|
||||||
endpoint: s3Info.endpoint,
|
endpoint: s3Info.endpoint,
|
||||||
|
|||||||
Reference in New Issue
Block a user