fix: plugin disappeared after plugin with same name reinstalled (#3198)

#### What type of PR is this?
/kind bug
/area core
/milestone 2.2.x

#### What this PR does / why we need it:
修复重复安装插件时 JAR 文件被删除的问题

需要注意的场景:
prod 模式下,假如安装一个 sitemap 插件,版本为 1.0.0 ,而此时 plugins 目录已经存在同名文件 sitemap-1.0.0.jar 文件但 sitemap 的 plugin.yaml 没有被持久化过,则能正常安装,旧的 JAR 文件被覆盖。

see #3159 for more details
#### Which issue(s) this PR fixes:

Fixes #3159

#### Special notes for your reviewer:
/cc @halo-dev/sig-halo
#### Does this PR introduce a user-facing change?

```release-note
修复重复安装插件时 JAR 文件被删除的问题
```
pull/3209/head
guqing 2023-02-02 10:44:11 +08:00 committed by GitHub
parent 764b664fd8
commit 0fd023b8f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 38 additions and 13 deletions

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
@ -61,6 +62,7 @@ import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.router.IListRequest.QueryListRequest;
import run.halo.app.infra.SystemVersionSupplier;
import run.halo.app.infra.exception.PluginInstallationException;
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
import run.halo.app.infra.utils.FileUtils;
import run.halo.app.infra.utils.VersionUtils;
@ -455,25 +457,25 @@ public class PluginEndpoint implements CustomEndpoint {
// Disable auto enable during installation
plugin.getSpec().setEnabled(false);
return client.fetch(Plugin.class, plugin.getMetadata().getName())
.switchIfEmpty(Mono.defer(() -> client.create(plugin)))
.doOnNext(oldPlugin -> {
String pluginName = oldPlugin.getMetadata().getName();
throw new PluginInstallationException(
"Plugin [" + pluginName + "] already installed",
"problemDetail.plugin.install.alreadyInstalled",
new Object[] {pluginName});
})
.then(client.create(plugin))
.publishOn(Schedulers.boundedElastic())
.map(created -> {
.doOnNext(created -> {
String fileName = created.generateFileName();
var pluginRoot = Paths.get(pluginProperties.getPluginsRoot());
createDirectoriesIfNotExists(pluginRoot);
Path pluginFilePath = pluginRoot.resolve(fileName);
if (Files.exists(pluginFilePath)) {
throw new IllegalArgumentException(
"Plugin already installed : " + pluginFilePath);
}
FileUtils.copy(tempJarFilePath, pluginFilePath);
return created;
// move the plugin jar file to the plugin root
// replace the old plugin jar file if exists
FileUtils.copy(tempJarFilePath, pluginFilePath,
StandardCopyOption.REPLACE_EXISTING);
})
.onErrorResume(
error -> client.fetch(Plugin.class, plugin.getMetadata().getName())
.flatMap(client::delete)
.then(Mono.error(error))
)
.doFinally(signalType -> {
try {
Files.deleteIfExists(tempJarFilePath);

View File

@ -0,0 +1,19 @@
package run.halo.app.infra.exception;
import jakarta.validation.constraints.Null;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebInputException;
/**
* {@link ServerWebInputException} subclass that indicates plugin installation failure.
*
* @author guqing
* @since 2.0.0
*/
public class PluginInstallationException extends ServerWebInputException {
public PluginInstallationException(String reason, @Nullable String messageDetailCode,
@Null Object[] messageDetailArguments) {
super(reason, null, null, messageDetailCode, messageDetailArguments);
}
}

View File

@ -14,6 +14,7 @@ problemDetail.title.run.halo.app.infra.exception.AccessDeniedException=Access De
problemDetail.title.reactor.core.Exceptions.RetryExhaustedException=Retry Exhausted
problemDetail.title.run.halo.app.infra.exception.ThemeInstallationException=Theme Install Error
problemDetail.title.run.halo.app.infra.exception.ThemeUpgradeException=Theme Upgrade Error
problemDetail.title.run.halo.app.infra.exception.PluginInstallationException=Plugin Install Error
# Detail definitions
problemDetail.org.springframework.web.server.UnsupportedMediaTypeStatusException=Content type {0} is not supported. Supported media types: {1}.
@ -38,3 +39,4 @@ problemDetail.theme.version.unsatisfied.requires=The theme requires a minimum sy
problemDetail.directoryTraversal=Directory traversal detected. Base path is {0}, but real path is {1}.
problemDetail.plugin.version.unsatisfied.requires=Plugin requires a minimum system version of {0}, but the current version is {1}.
problemDetail.plugin.install.alreadyInstalled=Plugin {0} already installed.

View File

@ -1,9 +1,11 @@
problemDetail.title.org.springframework.web.server.ServerWebInputException=请求参数有误
problemDetail.title.run.halo.app.infra.exception.UnsatisfiedAttributeValueException=请求参数属性值不满足要求
problemDetail.title.run.halo.app.infra.exception.PluginInstallationException=插件安装失败
problemDetail.title.run.halo.app.infra.exception.AttachmentAlreadyExistsException=附件已存在
problemDetail.run.halo.app.infra.exception.AttachmentAlreadyExistsException=文件 {0} 已存在,建议更名后重试。
problemDetail.plugin.version.unsatisfied.requires=插件要求一个最小的系统版本为 {0}, 但当前版本为 {1}。
problemDetail.plugin.install.alreadyInstalled=插件 {0} 已经被安装。
problemDetail.theme.version.unsatisfied.requires=主题要求一个最小的系统版本为 {0}, 但当前版本为 {1}。