mirror of https://github.com/halo-dev/halo
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
parent
764b664fd8
commit
0fd023b8f6
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
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.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
||||||
import run.halo.app.infra.SystemVersionSupplier;
|
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.exception.UnsatisfiedAttributeValueException;
|
||||||
import run.halo.app.infra.utils.FileUtils;
|
import run.halo.app.infra.utils.FileUtils;
|
||||||
import run.halo.app.infra.utils.VersionUtils;
|
import run.halo.app.infra.utils.VersionUtils;
|
||||||
|
@ -455,25 +457,25 @@ public class PluginEndpoint implements CustomEndpoint {
|
||||||
// Disable auto enable during installation
|
// Disable auto enable during installation
|
||||||
plugin.getSpec().setEnabled(false);
|
plugin.getSpec().setEnabled(false);
|
||||||
return client.fetch(Plugin.class, plugin.getMetadata().getName())
|
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())
|
.publishOn(Schedulers.boundedElastic())
|
||||||
.map(created -> {
|
.doOnNext(created -> {
|
||||||
String fileName = created.generateFileName();
|
String fileName = created.generateFileName();
|
||||||
var pluginRoot = Paths.get(pluginProperties.getPluginsRoot());
|
var pluginRoot = Paths.get(pluginProperties.getPluginsRoot());
|
||||||
createDirectoriesIfNotExists(pluginRoot);
|
createDirectoriesIfNotExists(pluginRoot);
|
||||||
Path pluginFilePath = pluginRoot.resolve(fileName);
|
Path pluginFilePath = pluginRoot.resolve(fileName);
|
||||||
if (Files.exists(pluginFilePath)) {
|
// move the plugin jar file to the plugin root
|
||||||
throw new IllegalArgumentException(
|
// replace the old plugin jar file if exists
|
||||||
"Plugin already installed : " + pluginFilePath);
|
FileUtils.copy(tempJarFilePath, pluginFilePath,
|
||||||
}
|
StandardCopyOption.REPLACE_EXISTING);
|
||||||
FileUtils.copy(tempJarFilePath, pluginFilePath);
|
|
||||||
return created;
|
|
||||||
})
|
})
|
||||||
.onErrorResume(
|
|
||||||
error -> client.fetch(Plugin.class, plugin.getMetadata().getName())
|
|
||||||
.flatMap(client::delete)
|
|
||||||
.then(Mono.error(error))
|
|
||||||
)
|
|
||||||
.doFinally(signalType -> {
|
.doFinally(signalType -> {
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(tempJarFilePath);
|
Files.deleteIfExists(tempJarFilePath);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ problemDetail.title.run.halo.app.infra.exception.AccessDeniedException=Access De
|
||||||
problemDetail.title.reactor.core.Exceptions.RetryExhaustedException=Retry Exhausted
|
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.ThemeInstallationException=Theme Install Error
|
||||||
problemDetail.title.run.halo.app.infra.exception.ThemeUpgradeException=Theme Upgrade 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
|
# Detail definitions
|
||||||
problemDetail.org.springframework.web.server.UnsupportedMediaTypeStatusException=Content type {0} is not supported. Supported media types: {1}.
|
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.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.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.
|
|
@ -1,9 +1,11 @@
|
||||||
problemDetail.title.org.springframework.web.server.ServerWebInputException=请求参数有误
|
problemDetail.title.org.springframework.web.server.ServerWebInputException=请求参数有误
|
||||||
problemDetail.title.run.halo.app.infra.exception.UnsatisfiedAttributeValueException=请求参数属性值不满足要求
|
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.title.run.halo.app.infra.exception.AttachmentAlreadyExistsException=附件已存在
|
||||||
|
|
||||||
problemDetail.run.halo.app.infra.exception.AttachmentAlreadyExistsException=文件 {0} 已存在,建议更名后重试。
|
problemDetail.run.halo.app.infra.exception.AttachmentAlreadyExistsException=文件 {0} 已存在,建议更名后重试。
|
||||||
|
|
||||||
problemDetail.plugin.version.unsatisfied.requires=插件要求一个最小的系统版本为 {0}, 但当前版本为 {1}。
|
problemDetail.plugin.version.unsatisfied.requires=插件要求一个最小的系统版本为 {0}, 但当前版本为 {1}。
|
||||||
|
problemDetail.plugin.install.alreadyInstalled=插件 {0} 已经被安装。
|
||||||
|
|
||||||
problemDetail.theme.version.unsatisfied.requires=主题要求一个最小的系统版本为 {0}, 但当前版本为 {1}。
|
problemDetail.theme.version.unsatisfied.requires=主题要求一个最小的系统版本为 {0}, 但当前版本为 {1}。
|
Loading…
Reference in New Issue