mirror of https://github.com/halo-dev/halo
Check dependencies while installing and upgrading plugins (#5361)
#### What type of PR is this? /kind improvement /area core /milestone 2.13.x #### What this PR does / why we need it: This PR checks dependencies while installing and upgrading plugins. Steps to test: 1. Prepare a plugin with dependencies. ```diff apiVersion: plugin.halo.run/v1alpha1 kind: Plugin metadata: name: a-plugin ... + pluginDependencies: + app-store-integration: 1.* ``` 2. Build the plugin. 3. Try to uninstall `app-store` plugin. 4. Install `a-plugin`. 5. See the result #### Which issue(s) this PR fixes: Fixes #5345 #### Does this PR introduce a user-facing change? ```release-note 插件安装和更新时检查依赖是否合法 ```pull/5422/head^2
parent
827030dd68
commit
d2f569699b
|
@ -10,6 +10,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
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.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -17,6 +18,9 @@ import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.pf4j.DependencyResolver;
|
||||||
|
import org.pf4j.PluginDescriptor;
|
||||||
|
import org.pf4j.PluginManager;
|
||||||
import org.pf4j.PluginWrapper;
|
import org.pf4j.PluginWrapper;
|
||||||
import org.pf4j.RuntimeMode;
|
import org.pf4j.RuntimeMode;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
@ -37,14 +41,15 @@ import run.halo.app.core.extension.service.PluginService;
|
||||||
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.PluginDependencyException;
|
||||||
import run.halo.app.infra.exception.PluginInstallationException;
|
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;
|
||||||
import run.halo.app.plugin.HaloPluginManager;
|
|
||||||
import run.halo.app.plugin.PluginConst;
|
import run.halo.app.plugin.PluginConst;
|
||||||
import run.halo.app.plugin.PluginProperties;
|
import run.halo.app.plugin.PluginProperties;
|
||||||
import run.halo.app.plugin.PluginUtils;
|
import run.halo.app.plugin.PluginUtils;
|
||||||
|
import run.halo.app.plugin.YamlPluginDescriptorFinder;
|
||||||
import run.halo.app.plugin.YamlPluginFinder;
|
import run.halo.app.plugin.YamlPluginFinder;
|
||||||
import run.halo.app.plugin.resources.BundleResourceUtils;
|
import run.halo.app.plugin.resources.BundleResourceUtils;
|
||||||
|
|
||||||
|
@ -62,7 +67,7 @@ public class PluginServiceImpl implements PluginService {
|
||||||
|
|
||||||
private final PluginProperties pluginProperties;
|
private final PluginProperties pluginProperties;
|
||||||
|
|
||||||
private final HaloPluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<Plugin> getPresets() {
|
public Flux<Plugin> getPresets() {
|
||||||
|
@ -82,11 +87,13 @@ public class PluginServiceImpl implements PluginService {
|
||||||
@Override
|
@Override
|
||||||
public Mono<Plugin> install(Path path) {
|
public Mono<Plugin> install(Path path) {
|
||||||
return findPluginManifest(path)
|
return findPluginManifest(path)
|
||||||
.flatMap(pluginInPath -> {
|
.doOnNext(plugin -> {
|
||||||
// validate the plugin version
|
// validate the plugin version
|
||||||
satisfiesRequiresVersion(pluginInPath);
|
satisfiesRequiresVersion(plugin);
|
||||||
|
checkDependencies(plugin);
|
||||||
return client.fetch(Plugin.class, pluginInPath.getMetadata().getName())
|
})
|
||||||
|
.flatMap(pluginInPath ->
|
||||||
|
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(
|
||||||
|
@ -97,18 +104,47 @@ public class PluginServiceImpl implements PluginService {
|
||||||
p.getSpec().setEnabled(false);
|
p.getSpec().setEnabled(false);
|
||||||
})
|
})
|
||||||
.flatMap(client::create))
|
.flatMap(client::create))
|
||||||
);
|
));
|
||||||
});
|
}
|
||||||
|
|
||||||
|
private void checkDependencies(Plugin plugin) {
|
||||||
|
var resolvedPlugins = new ArrayList<>(pluginManager.getResolvedPlugins());
|
||||||
|
var pluginDescriptors = new ArrayList<PluginDescriptor>(resolvedPlugins.size() + 1);
|
||||||
|
|
||||||
|
resolvedPlugins.stream()
|
||||||
|
.map(PluginWrapper::getDescriptor)
|
||||||
|
.forEach(pluginDescriptors::add);
|
||||||
|
|
||||||
|
var pluginDescriptor = YamlPluginDescriptorFinder.convert(plugin);
|
||||||
|
pluginDescriptors.add(pluginDescriptor);
|
||||||
|
|
||||||
|
var deptResolver = new DependencyResolver(pluginManager.getVersionManager());
|
||||||
|
var result = deptResolver.resolve(pluginDescriptors);
|
||||||
|
if (result.hasCyclicDependency()) {
|
||||||
|
throw new PluginDependencyException.CyclicException();
|
||||||
|
}
|
||||||
|
var notFoundDependencies = result.getNotFoundDependencies();
|
||||||
|
if (!CollectionUtils.isEmpty(notFoundDependencies)) {
|
||||||
|
throw new PluginDependencyException.NotFoundException(notFoundDependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrongVersionDependencies = result.getWrongVersionDependencies();
|
||||||
|
if (!CollectionUtils.isEmpty(wrongVersionDependencies)) {
|
||||||
|
throw new PluginDependencyException.WrongVersionsException(wrongVersionDependencies);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Plugin> upgrade(String name, Path path) {
|
public Mono<Plugin> upgrade(String name, Path path) {
|
||||||
return findPluginManifest(path)
|
return findPluginManifest(path)
|
||||||
|
.doOnNext(plugin -> {
|
||||||
|
satisfiesRequiresVersion(plugin);
|
||||||
|
checkDependencies(plugin);
|
||||||
|
})
|
||||||
.flatMap(pluginInPath -> {
|
.flatMap(pluginInPath -> {
|
||||||
// pre-check the plugin in the path
|
// pre-check the plugin in the path
|
||||||
Assert.notNull(pluginInPath.statusNonNull().getLoadLocation(),
|
Assert.notNull(pluginInPath.statusNonNull().getLoadLocation(),
|
||||||
"plugin.status.load-location must not be null");
|
"plugin.status.load-location must not be null");
|
||||||
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()
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package run.halo.app.infra.exception;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pf4j.DependencyResolver.WrongDependencyVersion;
|
||||||
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
|
|
||||||
|
public abstract class PluginDependencyException extends ServerWebInputException {
|
||||||
|
|
||||||
|
public PluginDependencyException(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginDependencyException(String reason, Throwable cause) {
|
||||||
|
super(reason, null, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PluginDependencyException(String reason, Throwable cause,
|
||||||
|
String messageDetailCode, Object[] messageDetailArguments) {
|
||||||
|
super(reason, null, cause, messageDetailCode, messageDetailArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CyclicException extends PluginDependencyException {
|
||||||
|
|
||||||
|
public static final String TYPE = "https://halo.run/probs/plugin-cyclic-dependency";
|
||||||
|
|
||||||
|
public CyclicException() {
|
||||||
|
super("A cyclic dependency was detected.");
|
||||||
|
setType(URI.create(TYPE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NotFoundException extends PluginDependencyException {
|
||||||
|
|
||||||
|
public static final String TYPE = "https://halo.run/probs/plugin-dependencies-not-found";
|
||||||
|
|
||||||
|
public NotFoundException(List<String> dependencies) {
|
||||||
|
super("Dependencies were not found.", null, null, new Object[] {dependencies});
|
||||||
|
setType(URI.create(TYPE));
|
||||||
|
getBody().setProperty("dependencies", dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WrongVersionsException extends PluginDependencyException {
|
||||||
|
|
||||||
|
public static final String TYPE =
|
||||||
|
"https://halo.run/probs/plugin-dependencies-with-wrong-versions";
|
||||||
|
|
||||||
|
public WrongVersionsException(List<WrongDependencyVersion> versions) {
|
||||||
|
super("Dependencies have wrong version.", null, null, new Object[] {versions});
|
||||||
|
setType(URI.create(TYPE));
|
||||||
|
getBody().setProperty("versions", versions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ public class YamlPluginDescriptorFinder implements PluginDescriptorFinder {
|
||||||
return convert(plugin);
|
return convert(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultPluginDescriptor convert(Plugin plugin) {
|
public static PluginDescriptor convert(Plugin plugin) {
|
||||||
String pluginId = plugin.getMetadata().getName();
|
String pluginId = plugin.getMetadata().getName();
|
||||||
Plugin.PluginSpec spec = plugin.getSpec();
|
Plugin.PluginSpec spec = plugin.getSpec();
|
||||||
Plugin.PluginAuthor author = spec.getAuthor();
|
Plugin.PluginAuthor author = spec.getAuthor();
|
||||||
|
@ -66,7 +66,7 @@ public class YamlPluginDescriptorFinder implements PluginDescriptorFinder {
|
||||||
return defaultPluginDescriptor;
|
return defaultPluginDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String joinLicense(List<Plugin.License> licenses) {
|
private static String joinLicense(List<Plugin.License> licenses) {
|
||||||
if (CollectionUtils.isEmpty(licenses)) {
|
if (CollectionUtils.isEmpty(licenses)) {
|
||||||
return StringUtils.EMPTY;
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,9 @@ problemDetail.title.run.halo.app.infra.exception.DuplicateNameException=Duplicat
|
||||||
problemDetail.title.run.halo.app.infra.exception.RateLimitExceededException=Request Not Permitted
|
problemDetail.title.run.halo.app.infra.exception.RateLimitExceededException=Request Not Permitted
|
||||||
problemDetail.title.run.halo.app.infra.exception.NotFoundException=Resource Not Found
|
problemDetail.title.run.halo.app.infra.exception.NotFoundException=Resource Not Found
|
||||||
problemDetail.title.run.halo.app.infra.exception.EmailVerificationFailed=Email Verification Failed
|
problemDetail.title.run.halo.app.infra.exception.EmailVerificationFailed=Email Verification Failed
|
||||||
|
problemDetail.title.run.halo.app.infra.exception.PluginDependencyException$CyclicException=Cyclic Dependency Detected
|
||||||
|
problemDetail.title.run.halo.app.infra.exception.PluginDependencyException$NotFoundException=Dependencies Not Found
|
||||||
|
problemDetail.title.run.halo.app.infra.exception.PluginDependencyException$WrongVersionsException=Wrong Dependency Version
|
||||||
problemDetail.title.internalServerError=Internal Server Error
|
problemDetail.title.internalServerError=Internal Server Error
|
||||||
|
|
||||||
# Detail definitions
|
# Detail definitions
|
||||||
|
@ -40,6 +43,10 @@ problemDetail.run.halo.app.infra.exception.DuplicateNameException=Duplicate name
|
||||||
problemDetail.run.halo.app.infra.exception.PluginAlreadyExistsException=Plugin {0} already exists.
|
problemDetail.run.halo.app.infra.exception.PluginAlreadyExistsException=Plugin {0} already exists.
|
||||||
problemDetail.run.halo.app.infra.exception.RateLimitExceededException=API rate limit exceeded, please try again later.
|
problemDetail.run.halo.app.infra.exception.RateLimitExceededException=API rate limit exceeded, please try again later.
|
||||||
problemDetail.run.halo.app.infra.exception.EmailVerificationFailed=Invalid email verification code.
|
problemDetail.run.halo.app.infra.exception.EmailVerificationFailed=Invalid email verification code.
|
||||||
|
problemDetail.run.halo.app.infra.exception.PluginDependencyException$CyclicException=A cyclic dependency was detected.
|
||||||
|
problemDetail.run.halo.app.infra.exception.PluginDependencyException$NotFoundException=Dependencies "{0}" were not found.
|
||||||
|
problemDetail.run.halo.app.infra.exception.PluginDependencyException$WrongVersionsException=Dependencies have wrong version: {0}.
|
||||||
|
|
||||||
problemDetail.index.duplicateKey=The value of {0} already exists for unique index {1}, please rename it and retry.
|
problemDetail.index.duplicateKey=The value of {0} already exists for unique index {1}, please rename it and retry.
|
||||||
problemDetail.user.email.verify.maxAttempts=Too many verification attempts, please try again later.
|
problemDetail.user.email.verify.maxAttempts=Too many verification attempts, please try again later.
|
||||||
problemDetail.user.password.unsatisfied=The password does not meet the specifications.
|
problemDetail.user.password.unsatisfied=The password does not meet the specifications.
|
||||||
|
|
|
@ -9,6 +9,9 @@ problemDetail.title.run.halo.app.infra.exception.ThemeInstallationException=主
|
||||||
problemDetail.title.run.halo.app.infra.exception.RateLimitExceededException=请求限制
|
problemDetail.title.run.halo.app.infra.exception.RateLimitExceededException=请求限制
|
||||||
problemDetail.title.run.halo.app.infra.exception.NotFoundException=资源不存在
|
problemDetail.title.run.halo.app.infra.exception.NotFoundException=资源不存在
|
||||||
problemDetail.title.run.halo.app.infra.exception.EmailVerificationFailed=邮箱验证失败
|
problemDetail.title.run.halo.app.infra.exception.EmailVerificationFailed=邮箱验证失败
|
||||||
|
problemDetail.title.run.halo.app.infra.exception.PluginDependencyException$CyclicException=循环依赖
|
||||||
|
problemDetail.title.run.halo.app.infra.exception.PluginDependencyException$NotFoundException=依赖未找到
|
||||||
|
problemDetail.title.run.halo.app.infra.exception.PluginDependencyException$WrongVersionsException=依赖版本错误
|
||||||
problemDetail.title.internalServerError=服务器内部错误
|
problemDetail.title.internalServerError=服务器内部错误
|
||||||
|
|
||||||
problemDetail.org.springframework.security.authentication.BadCredentialsException=用户名或密码错误。
|
problemDetail.org.springframework.security.authentication.BadCredentialsException=用户名或密码错误。
|
||||||
|
@ -17,6 +20,10 @@ problemDetail.run.halo.app.infra.exception.DuplicateNameException=检测到有
|
||||||
problemDetail.run.halo.app.infra.exception.PluginAlreadyExistsException=插件 {0} 已经存在。
|
problemDetail.run.halo.app.infra.exception.PluginAlreadyExistsException=插件 {0} 已经存在。
|
||||||
problemDetail.run.halo.app.infra.exception.RateLimitExceededException=请求过于频繁,请稍候再试。
|
problemDetail.run.halo.app.infra.exception.RateLimitExceededException=请求过于频繁,请稍候再试。
|
||||||
problemDetail.run.halo.app.infra.exception.EmailVerificationFailed=验证码错误或已失效。
|
problemDetail.run.halo.app.infra.exception.EmailVerificationFailed=验证码错误或已失效。
|
||||||
|
problemDetail.run.halo.app.infra.exception.PluginDependencyException$CyclicException=检测到循环依赖。
|
||||||
|
problemDetail.run.halo.app.infra.exception.PluginDependencyException$NotFoundException=依赖“{0}”未找到。
|
||||||
|
problemDetail.run.halo.app.infra.exception.PluginDependencyException$WrongVersionsException=依赖版本有误:{0}。
|
||||||
|
|
||||||
problemDetail.index.duplicateKey=唯一索引 {1} 中的值 {0} 已存在,请更名后重试。
|
problemDetail.index.duplicateKey=唯一索引 {1} 中的值 {0} 已存在,请更名后重试。
|
||||||
problemDetail.user.email.verify.maxAttempts=尝试次数过多,请稍候再试。
|
problemDetail.user.email.verify.maxAttempts=尝试次数过多,请稍候再试。
|
||||||
problemDetail.user.password.unsatisfied=密码不符合规范。
|
problemDetail.user.password.unsatisfied=密码不符合规范。
|
||||||
|
|
Loading…
Reference in New Issue