Support getting backup root path in plugin (#4422)

#### What type of PR is this?

/kind feature
/area core
/milestone 2.9.x

#### What this PR does / why we need it:

We already support backup and restore feature in Halo 2.8.0, but we cannot obtain backup files through regular channels in the plugin. For example, we want to upload backup files to OSS in the plugin.

This PR is aimed at solving this problem.

#### Does this PR introduce a user-facing change?

```release-note
支持在插件中获取备份文件根目录。
```
pull/4431/head
John Niang 2023-08-14 19:38:11 +08:00 committed by GitHub
parent 905e867eb3
commit 8e3bd7f3d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 98 additions and 13 deletions

View File

@ -0,0 +1,14 @@
package run.halo.app.infra;
import java.nio.file.Path;
import java.util.function.Supplier;
/**
* Utility of getting backup root path.
*
* @author johnniang
* @since 2.9.0
*/
public interface BackupRootGetter extends Supplier<Path> {
}

View File

@ -0,0 +1,20 @@
package run.halo.app.infra;
import java.nio.file.Path;
import org.springframework.stereotype.Component;
import run.halo.app.infra.properties.HaloProperties;
@Component
public class DefaultBackupRootGetter implements BackupRootGetter {
private final HaloProperties haloProperties;
public DefaultBackupRootGetter(HaloProperties haloProperties) {
this.haloProperties = haloProperties;
}
@Override
public Path get() {
return haloProperties.getWorkDir().resolve("backups");
}
}

View File

@ -34,6 +34,7 @@ import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import run.halo.app.extension.store.ExtensionStore;
import run.halo.app.extension.store.ExtensionStoreRepository;
import run.halo.app.infra.BackupRootGetter;
import run.halo.app.infra.exception.NotFoundException;
import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.infra.utils.FileUtils;
@ -48,6 +49,8 @@ public class MigrationServiceImpl implements MigrationService {
private final HaloProperties haloProperties;
private final BackupRootGetter backupRoot;
private final ObjectMapper objectMapper;
private final Set<String> excludes = Set.of(
@ -69,9 +72,10 @@ public class MigrationServiceImpl implements MigrationService {
private final Scheduler scheduler = Schedulers.boundedElastic();
public MigrationServiceImpl(ExtensionStoreRepository repository,
HaloProperties haloProperties) {
HaloProperties haloProperties, BackupRootGetter backupRoot) {
this.repository = repository;
this.haloProperties = haloProperties;
this.backupRoot = backupRoot;
this.objectMapper = JsonMapper.builder()
.defaultPrettyPrinter(new MinimalPrettyPrinter())
.build();
@ -90,7 +94,7 @@ public class MigrationServiceImpl implements MigrationService {
}
Path getBackupsRoot() {
return haloProperties.getWorkDir().resolve("backups");
return backupRoot.get();
}
@Override

View File

@ -8,6 +8,7 @@ import run.halo.app.core.extension.service.AttachmentService;
import run.halo.app.extension.DefaultSchemeManager;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.BackupRootGetter;
import run.halo.app.infra.ExternalUrlSupplier;
/**
@ -70,6 +71,8 @@ public class SharedApplicationContextHolder {
rootApplicationContext.getBean(ServerSecurityContextRepository.class));
beanFactory.registerSingleton("attachmentService",
rootApplicationContext.getBean(AttachmentService.class));
beanFactory.registerSingleton("backupRootGetter",
rootApplicationContext.getBean(BackupRootGetter.class));
// TODO add more shared instance here
return sharedApplicationContext;

View File

@ -0,0 +1,33 @@
package run.halo.app.infra;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import run.halo.app.infra.properties.HaloProperties;
@ExtendWith(MockitoExtension.class)
class DefaultBackupRootGetterTest {
@Mock
HaloProperties haloProperties;
@InjectMocks
DefaultBackupRootGetter backupRootGetter;
@Test
void shouldGetBackupRootFromWorkDir() {
when(haloProperties.getWorkDir()).thenReturn(Path.of("workdir"));
var backupRoot = this.backupRootGetter.get();
assertEquals(Path.of("workdir", "backups"), backupRoot);
verify(haloProperties).getWorkDir();
}
}

View File

@ -4,7 +4,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -31,6 +31,7 @@ import reactor.test.StepVerifier;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.store.ExtensionStore;
import run.halo.app.extension.store.ExtensionStoreRepository;
import run.halo.app.infra.BackupRootGetter;
import run.halo.app.infra.exception.NotFoundException;
import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.infra.utils.FileUtils;
@ -45,6 +46,9 @@ class MigrationServiceImplTest {
@Mock
HaloProperties haloProperties;
@Mock
BackupRootGetter backupRoot;
@InjectMocks
MigrationServiceImpl migrationService;
@ -53,21 +57,23 @@ class MigrationServiceImplTest {
@Test
void backupTest() throws IOException {
var startTimestamp = Instant.now();
var backup = createRunningBackup("fake-backup", startTimestamp);
Files.writeString(tempDir.resolve("fake-file"), "halo", StandardOpenOption.CREATE_NEW);
var extensionStores = List.of(
createExtensionStore("fake-extension-store", "fake-data")
);
when(repository.findAll()).thenReturn(Flux.fromIterable(extensionStores));
when(haloProperties.getWorkDir()).thenReturn(tempDir);
when(backupRoot.get()).thenReturn(tempDir.resolve("backups"));
var startTimestamp = Instant.now();
var backup = createRunningBackup("fake-backup", startTimestamp);
StepVerifier.create(migrationService.backup(backup))
.verifyComplete();
verify(repository).findAll();
// 1. backup workdir
// 2. package backup
verify(haloProperties, times(2)).getWorkDir();
verify(haloProperties).getWorkDir();
verify(backupRoot).get();
var status = backup.getStatus();
var datetimePart = migrationService.getDateTimeFormatter().format(startTimestamp);
@ -134,26 +140,26 @@ class MigrationServiceImplTest {
@Test
void cleanupBackupTest() throws IOException {
var backup = createSucceededBackup("fake-backup", "backup.zip");
var backupFile = tempDir.resolve("workdir").resolve("backups").resolve("backup.zip");
Files.createDirectories(backupFile.getParent());
Files.createFile(backupFile);
when(haloProperties.getWorkDir()).thenReturn(tempDir.resolve("workdir"));
when(backupRoot.get()).thenReturn(tempDir.resolve("workdir").resolve("backups"));
var backup = createSucceededBackup("fake-backup", "backup.zip");
StepVerifier.create(migrationService.cleanup(backup))
.verifyComplete();
verify(haloProperties).getWorkDir();
verify(haloProperties, never()).getWorkDir();
verify(backupRoot).get();
assertTrue(Files.notExists(backupFile));
}
@Test
void downloadBackupTest() throws IOException {
var backup = createSucceededBackup("fake-backup", "backup.zip");
var backupFile = tempDir.resolve("workdir").resolve("backups").resolve("backup.zip");
Files.createDirectories(backupFile.getParent());
Files.writeString(backupFile, "this is a backup file.", StandardOpenOption.CREATE_NEW);
when(haloProperties.getWorkDir()).thenReturn(tempDir.resolve("workdir"));
when(backupRoot.get()).thenReturn(tempDir.resolve("workdir").resolve("backups"));
var backup = createSucceededBackup("fake-backup", "backup.zip");
StepVerifier.create(migrationService.download(backup))
.assertNext(resource -> {
@ -166,16 +172,21 @@ class MigrationServiceImplTest {
}
})
.verifyComplete();
verify(haloProperties, never()).getWorkDir();
verify(backupRoot).get();
}
@Test
void downloadBackupWhichDoesNotExist() {
var backup = createSucceededBackup("fake-backup", "backup.zip");
when(haloProperties.getWorkDir()).thenReturn(tempDir.resolve("workdir"));
when(backupRoot.get()).thenReturn(tempDir.resolve("workdir").resolve("backups"));
StepVerifier.create(migrationService.download(backup))
.expectError(NotFoundException.class)
.verify();
verify(haloProperties, never()).getWorkDir();
verify(backupRoot).get();
}
Backup createSucceededBackup(String name, String filename) {