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.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -17,6 +18,9 @@ import java.util.Objects;
|
|||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.DependencyResolver;
|
||||
import org.pf4j.PluginDescriptor;
|
||||
import org.pf4j.PluginManager;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.RuntimeMode;
|
||||
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.infra.SystemVersionSupplier;
|
||||
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.UnsatisfiedAttributeValueException;
|
||||
import run.halo.app.infra.utils.FileUtils;
|
||||
import run.halo.app.infra.utils.VersionUtils;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.PluginConst;
|
||||
import run.halo.app.plugin.PluginProperties;
|
||||
import run.halo.app.plugin.PluginUtils;
|
||||
import run.halo.app.plugin.YamlPluginDescriptorFinder;
|
||||
import run.halo.app.plugin.YamlPluginFinder;
|
||||
import run.halo.app.plugin.resources.BundleResourceUtils;
|
||||
|
||||
|
@ -62,7 +67,7 @@ public class PluginServiceImpl implements PluginService {
|
|||
|
||||
private final PluginProperties pluginProperties;
|
||||
|
||||
private final HaloPluginManager pluginManager;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
@Override
|
||||
public Flux<Plugin> getPresets() {
|
||||
|
@ -82,11 +87,13 @@ public class PluginServiceImpl implements PluginService {
|
|||
@Override
|
||||
public Mono<Plugin> install(Path path) {
|
||||
return findPluginManifest(path)
|
||||
.flatMap(pluginInPath -> {
|
||||
.doOnNext(plugin -> {
|
||||
// validate the plugin version
|
||||
satisfiesRequiresVersion(pluginInPath);
|
||||
|
||||
return client.fetch(Plugin.class, pluginInPath.getMetadata().getName())
|
||||
satisfiesRequiresVersion(plugin);
|
||||
checkDependencies(plugin);
|
||||
})
|
||||
.flatMap(pluginInPath ->
|
||||
client.fetch(Plugin.class, pluginInPath.getMetadata().getName())
|
||||
.flatMap(oldPlugin -> Mono.<Plugin>error(
|
||||
new PluginAlreadyExistsException(oldPlugin.getMetadata().getName())))
|
||||
.switchIfEmpty(Mono.defer(
|
||||
|
@ -97,18 +104,47 @@ public class PluginServiceImpl implements PluginService {
|
|||
p.getSpec().setEnabled(false);
|
||||
})
|
||||
.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
|
||||
public Mono<Plugin> upgrade(String name, Path path) {
|
||||
return findPluginManifest(path)
|
||||
.doOnNext(plugin -> {
|
||||
satisfiesRequiresVersion(plugin);
|
||||
checkDependencies(plugin);
|
||||
})
|
||||
.flatMap(pluginInPath -> {
|
||||
// pre-check the plugin in the path
|
||||
Assert.notNull(pluginInPath.statusNonNull().getLoadLocation(),
|
||||
"plugin.status.load-location must not be null");
|
||||
satisfiesRequiresVersion(pluginInPath);
|
||||
if (!Objects.equals(name, pluginInPath.getMetadata().getName())) {
|
||||
return Mono.error(new ServerWebInputException(
|
||||
"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);
|
||||
}
|
||||
|
||||
private DefaultPluginDescriptor convert(Plugin plugin) {
|
||||
public static PluginDescriptor convert(Plugin plugin) {
|
||||
String pluginId = plugin.getMetadata().getName();
|
||||
Plugin.PluginSpec spec = plugin.getSpec();
|
||||
Plugin.PluginAuthor author = spec.getAuthor();
|
||||
|
@ -66,7 +66,7 @@ public class YamlPluginDescriptorFinder implements PluginDescriptorFinder {
|
|||
return defaultPluginDescriptor;
|
||||
}
|
||||
|
||||
private String joinLicense(List<Plugin.License> licenses) {
|
||||
private static String joinLicense(List<Plugin.License> licenses) {
|
||||
if (CollectionUtils.isEmpty(licenses)) {
|
||||
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.NotFoundException=Resource Not Found
|
||||
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
|
||||
|
||||
# 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.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.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.user.email.verify.maxAttempts=Too many verification attempts, please try again later.
|
||||
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.NotFoundException=资源不存在
|
||||
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.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.RateLimitExceededException=请求过于频繁,请稍候再试。
|
||||
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.user.email.verify.maxAttempts=尝试次数过多,请稍候再试。
|
||||
problemDetail.user.password.unsatisfied=密码不符合规范。
|
||||
|
|
Loading…
Reference in New Issue