refactor: exception prompts during plugin installation (#3993)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.6.x

#### What this PR does / why we need it:
优化插件安装失败的提示信息

插件安装和升级时由于包格式不正确改为如下提示(Localization)
<img width="449" alt="image" src="https://github.com/halo-dev/halo/assets/38999863/37da0d42-88fa-40c5-a2b9-b8e2698a5930">

how to test it?
使用下面的插件安装和升级会提示 plugin.yaml 缺失
[failed-plugins.zip](https://github.com/halo-dev/halo/files/11560921/failed-plugins.zip)

see #3843 for more details

#### Which issue(s) this PR fixes:

Fixes #3843

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

```release-note
优化插件安装失败的提示信息
```
pull/4005/head
guqing 2023-05-26 22:56:12 +08:00 committed by GitHub
parent 710261b035
commit c8cc9f2710
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 51 additions and 38 deletions

View File

@ -28,6 +28,7 @@ import run.halo.app.extension.MetadataUtil;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.SystemVersionSupplier; import run.halo.app.infra.SystemVersionSupplier;
import run.halo.app.infra.exception.PluginAlreadyExistsException; import run.halo.app.infra.exception.PluginAlreadyExistsException;
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;
@ -70,49 +71,47 @@ public class PluginServiceImpl implements PluginService {
@Override @Override
public Mono<Plugin> install(Path path) { public Mono<Plugin> install(Path path) {
return Mono.defer(() -> { return findPluginManifest(path)
final var pluginFinder = new YamlPluginFinder(); .flatMap(pluginInPath -> {
final var pluginInPath = pluginFinder.find(path); // validate the plugin version
// validate the plugin version satisfiesRequiresVersion(pluginInPath);
satisfiesRequiresVersion(pluginInPath);
return client.fetch(Plugin.class, pluginInPath.getMetadata().getName()) return client.fetch(Plugin.class, pluginInPath.getMetadata().getName())
.flatMap(oldPlugin -> Mono.<Plugin>error( .flatMap(oldPlugin -> Mono.<Plugin>error(
new PluginAlreadyExistsException(oldPlugin.getMetadata().getName()))) new PluginAlreadyExistsException(oldPlugin.getMetadata().getName())))
.switchIfEmpty(Mono.defer( .switchIfEmpty(Mono.defer(
() -> copyToPluginHome(pluginInPath) () -> copyToPluginHome(pluginInPath)
.map(pluginFinder::find) .flatMap(this::findPluginManifest)
.doOnNext(p -> { .doOnNext(p -> {
// Disable auto enable after installation // Disable auto enable after installation
p.getSpec().setEnabled(false); p.getSpec().setEnabled(false);
}) })
.flatMap(client::create))); .flatMap(client::create))
);
}); });
} }
@Override @Override
public Mono<Plugin> upgrade(String name, Path path) { public Mono<Plugin> upgrade(String name, Path path) {
return Mono.defer(() -> { return findPluginManifest(path)
// pre-check the plugin in the path .flatMap(pluginInPath -> {
final var pluginFinder = new YamlPluginFinder(); // pre-check the plugin in the path
final var pluginInPath = pluginFinder.find(path); Validate.notNull(pluginInPath.statusNonNull().getLoadLocation());
Validate.notNull(pluginInPath.statusNonNull().getLoadLocation()); satisfiesRequiresVersion(pluginInPath);
satisfiesRequiresVersion(pluginInPath); if (!Objects.equals(name, pluginInPath.getMetadata().getName())) {
if (!Objects.equals(name, pluginInPath.getMetadata().getName())) { return Mono.error(new ServerWebInputException(
return Mono.error(new ServerWebInputException( "The provided plugin " + pluginInPath.getMetadata().getName()
"The provided plugin " + pluginInPath.getMetadata().getName() + " didn't match the given plugin " + name));
+ " didn't match the given plugin " + name)); }
}
// check if the plugin exists // check if the plugin exists
return client.fetch(Plugin.class, name) return client.fetch(Plugin.class, name)
.switchIfEmpty(Mono.error(() -> new ServerWebInputException( .switchIfEmpty(Mono.error(() -> new ServerWebInputException(
"The given plugin with name " + name + " was not found."))) "The given plugin with name " + name + " was not found.")))
// copy plugin into plugin home // copy plugin into plugin home
.flatMap(prevPlugin -> copyToPluginHome(pluginInPath)) .flatMap(prevPlugin -> copyToPluginHome(pluginInPath))
.flatMap(pluginPath -> updateReloadAnno(name, pluginPath)); .flatMap(pluginPath -> updateReloadAnno(name, pluginPath));
}); });
} }
@Override @Override
@ -125,6 +124,17 @@ public class PluginServiceImpl implements PluginService {
return updateReloadAnno(name, pluginWrapper.getPluginPath()); return updateReloadAnno(name, pluginWrapper.getPluginPath());
} }
Mono<Plugin> findPluginManifest(Path path) {
return Mono.fromSupplier(
() -> {
final var pluginFinder = new YamlPluginFinder();
return pluginFinder.find(path);
})
.onErrorMap(e -> new PluginInstallationException("Failed to parse the plugin manifest",
"problemDetail.plugin.missingManifest", null)
);
}
private Mono<Plugin> updateReloadAnno(String name, Path pluginPath) { private Mono<Plugin> updateReloadAnno(String name, Path pluginPath) {
return client.get(Plugin.class, name) return client.get(Plugin.class, name)
.flatMap(plugin -> { .flatMap(plugin -> {

View File

@ -75,8 +75,9 @@ public class YamlPluginFinder {
} }
protected Plugin readPluginDescriptor(Path pluginPath) { protected Plugin readPluginDescriptor(Path pluginPath) {
Path propertiesPath = getManifestPath(pluginPath, propertiesFileName); Path propertiesPath = null;
try { try {
propertiesPath = getManifestPath(pluginPath, propertiesFileName);
if (propertiesPath == null) { if (propertiesPath == null) {
throw new PluginRuntimeException("Cannot find the plugin manifest path"); throw new PluginRuntimeException("Cannot find the plugin manifest path");
} }

View File

@ -45,3 +45,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.missingManifest=Missing plugin manifest file "plugin.yaml" or manifest file does not conform to the specification.

View File

@ -14,6 +14,7 @@ problemDetail.user.signUpFailed.disallowed=系统不允许注册新用户。
problemDetail.user.duplicateName=用户名 {0} 已存在,请更换用户名后重试。 problemDetail.user.duplicateName=用户名 {0} 已存在,请更换用户名后重试。
problemDetail.plugin.version.unsatisfied.requires=插件要求一个最小的系统版本为 {0}, 但当前版本为 {1}。 problemDetail.plugin.version.unsatisfied.requires=插件要求一个最小的系统版本为 {0}, 但当前版本为 {1}。
problemDetail.plugin.missingManifest=缺少 plugin.yaml 配置文件或配置文件不符合规范。
problemDetail.theme.version.unsatisfied.requires=主题要求一个最小的系统版本为 {0}, 但当前版本为 {1}。 problemDetail.theme.version.unsatisfied.requires=主题要求一个最小的系统版本为 {0}, 但当前版本为 {1}。
problemDetail.theme.install.missingManifest=缺少 theme.yaml 配置文件或配置文件不符合规范。 problemDetail.theme.install.missingManifest=缺少 theme.yaml 配置文件或配置文件不符合规范。