mirror of https://github.com/halo-dev/halo
feat: add plugin status manage (#2177)
* feat: add plugin status manage * feat: add plugin state changed listener * refactor: plugin status * refactor: pluginpull/2190/head
parent
c24df6fb05
commit
273ffaad48
|
@ -1,7 +1,8 @@
|
||||||
package run.halo.app.plugin;
|
package run.halo.app.core.extension;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -10,8 +11,10 @@ import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
import org.pf4j.PluginState;
|
||||||
import run.halo.app.extension.AbstractExtension;
|
import run.halo.app.extension.AbstractExtension;
|
||||||
import run.halo.app.extension.GVK;
|
import run.halo.app.extension.GVK;
|
||||||
|
import run.halo.app.plugin.BasePlugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom resource for Plugin.
|
* A custom resource for Plugin.
|
||||||
|
@ -29,6 +32,8 @@ public class Plugin extends AbstractExtension {
|
||||||
@Schema(required = true)
|
@Schema(required = true)
|
||||||
private PluginSpec spec;
|
private PluginSpec spec;
|
||||||
|
|
||||||
|
private PluginStatus status;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class PluginSpec {
|
public static class PluginSpec {
|
||||||
|
|
||||||
|
@ -55,6 +60,8 @@ public class Plugin extends AbstractExtension {
|
||||||
private String requires = "*";
|
private String requires = "*";
|
||||||
|
|
||||||
private String pluginClass = BasePlugin.class.getName();
|
private String pluginClass = BasePlugin.class.getName();
|
||||||
|
|
||||||
|
private Boolean enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -71,4 +78,22 @@ public class Plugin extends AbstractExtension {
|
||||||
this.url = "";
|
this.url = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class PluginStatus {
|
||||||
|
|
||||||
|
private PluginState phase;
|
||||||
|
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
private Instant lastStartTime;
|
||||||
|
|
||||||
|
private Instant lastTransitionTime;
|
||||||
|
|
||||||
|
private String entry;
|
||||||
|
|
||||||
|
private String stylesheet;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package run.halo.app.plugin.resources;
|
package run.halo.app.core.extension;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -21,9 +21,9 @@ import run.halo.app.extension.GVK;
|
||||||
public class ReverseProxy extends AbstractExtension {
|
public class ReverseProxy extends AbstractExtension {
|
||||||
private List<ReverseProxyRule> rules;
|
private List<ReverseProxyRule> rules;
|
||||||
|
|
||||||
record ReverseProxyRule(String path, FileReverseProxyProvider file) {
|
public record ReverseProxyRule(String path, FileReverseProxyProvider file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
record FileReverseProxyProvider(String directory, String filename) {
|
public record FileReverseProxyProvider(String directory, String filename) {
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,11 +4,12 @@ import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
|
import run.halo.app.core.extension.ReverseProxy;
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.RoleBinding;
|
import run.halo.app.core.extension.RoleBinding;
|
||||||
import run.halo.app.core.extension.User;
|
import run.halo.app.core.extension.User;
|
||||||
import run.halo.app.extension.SchemeManager;
|
import run.halo.app.extension.SchemeManager;
|
||||||
import run.halo.app.plugin.Plugin;
|
|
||||||
import run.halo.app.security.authentication.pat.PersonalAccessToken;
|
import run.halo.app.security.authentication.pat.PersonalAccessToken;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@ -27,5 +28,6 @@ public class SchemeInitializer implements ApplicationListener<ApplicationStarted
|
||||||
schemeManager.register(Plugin.class);
|
schemeManager.register(Plugin.class);
|
||||||
schemeManager.register(RoleBinding.class);
|
schemeManager.register(RoleBinding.class);
|
||||||
schemeManager.register(User.class);
|
schemeManager.register(User.class);
|
||||||
|
schemeManager.register(ReverseProxy.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ package run.halo.app.plugin;
|
||||||
|
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load plugins after application ready.
|
* Load plugins after application ready.
|
||||||
|
@ -14,14 +17,29 @@ import org.springframework.stereotype.Component;
|
||||||
public class PluginInitializationLoadOnApplicationReady
|
public class PluginInitializationLoadOnApplicationReady
|
||||||
implements ApplicationListener<ApplicationReadyEvent> {
|
implements ApplicationListener<ApplicationReadyEvent> {
|
||||||
|
|
||||||
|
private final PluginService pluginService;
|
||||||
|
|
||||||
private final HaloPluginManager haloPluginManager;
|
private final HaloPluginManager haloPluginManager;
|
||||||
|
|
||||||
public PluginInitializationLoadOnApplicationReady(HaloPluginManager haloPluginManager) {
|
private final ExtensionClient extensionClient;
|
||||||
|
|
||||||
|
public PluginInitializationLoadOnApplicationReady(PluginService pluginService,
|
||||||
|
HaloPluginManager haloPluginManager, ExtensionClient extensionClient) {
|
||||||
|
this.pluginService = pluginService;
|
||||||
this.haloPluginManager = haloPluginManager;
|
this.haloPluginManager = haloPluginManager;
|
||||||
|
this.extensionClient = extensionClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(ApplicationReadyEvent event) {
|
public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
|
||||||
haloPluginManager.loadPlugins();
|
haloPluginManager.loadPlugins();
|
||||||
|
initStartupPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initStartupPlugins() {
|
||||||
|
extensionClient.list(Plugin.class,
|
||||||
|
predicate -> predicate.getSpec().getEnabled(),
|
||||||
|
null)
|
||||||
|
.forEach(plugin -> pluginService.startup(plugin.getMetadata().getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ package run.halo.app.plugin;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.PluginState;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin manager controller.
|
* Plugin manager controller.
|
||||||
|
@ -20,13 +21,10 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
@RequestMapping("/apis/plugin.halo.run/v1alpha1/plugins")
|
@RequestMapping("/apis/plugin.halo.run/v1alpha1/plugins")
|
||||||
public class PluginLifeCycleManagerController {
|
public class PluginLifeCycleManagerController {
|
||||||
|
|
||||||
private final PluginServiceImpl pluginService;
|
private final PluginService pluginService;
|
||||||
private final HaloPluginManager pluginManager;
|
|
||||||
|
|
||||||
public PluginLifeCycleManagerController(PluginServiceImpl pluginService,
|
public PluginLifeCycleManagerController(PluginService pluginService) {
|
||||||
HaloPluginManager pluginManager) {
|
|
||||||
this.pluginService = pluginService;
|
this.pluginService = pluginService;
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@ -34,13 +32,13 @@ public class PluginLifeCycleManagerController {
|
||||||
return pluginService.list();
|
return pluginService.list();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{pluginName}/startup")
|
@PutMapping("/{pluginName}/startup")
|
||||||
public PluginState start(@PathVariable String pluginName) {
|
public Plugin start(@PathVariable String pluginName) {
|
||||||
return pluginManager.startPlugin(pluginName);
|
return pluginService.startup(pluginName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{pluginName}/stop")
|
@PutMapping("/{pluginName}/stop")
|
||||||
public PluginState stop(@PathVariable String pluginName) {
|
public Plugin stop(@PathVariable String pluginName) {
|
||||||
return pluginManager.stopPlugin(pluginName);
|
return pluginService.stop(pluginName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,10 @@ package run.halo.app.plugin;
|
||||||
|
|
||||||
import org.pf4j.PluginWrapper;
|
import org.pf4j.PluginWrapper;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.extension.SchemeManager;
|
|
||||||
import run.halo.app.infra.utils.YamlUnstructuredLoader;
|
|
||||||
import run.halo.app.plugin.event.HaloPluginLoadedEvent;
|
import run.halo.app.plugin.event.HaloPluginLoadedEvent;
|
||||||
import run.halo.app.plugin.resources.ReverseProxy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author guqing
|
* @author guqing
|
||||||
|
@ -17,15 +13,13 @@ import run.halo.app.plugin.resources.ReverseProxy;
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class PluginLoadedListener implements ApplicationListener<HaloPluginLoadedEvent> {
|
public class PluginLoadedListener implements ApplicationListener<HaloPluginLoadedEvent> {
|
||||||
private static final String REVERSE_PROXY_NAME = "extensions/reverseProxy.yaml";
|
|
||||||
private final ExtensionClient extensionClient;
|
private final ExtensionClient extensionClient;
|
||||||
|
|
||||||
public PluginLoadedListener(ExtensionClient extensionClient, SchemeManager schemeManager) {
|
private final PluginUnstructuredResourceLoader pluginUnstructuredResourceLoader;
|
||||||
this.extensionClient = extensionClient;
|
|
||||||
|
|
||||||
// TODO Optimize schemes register
|
public PluginLoadedListener(ExtensionClient extensionClient) {
|
||||||
schemeManager.register(Plugin.class);
|
this.extensionClient = extensionClient;
|
||||||
schemeManager.register(ReverseProxy.class);
|
pluginUnstructuredResourceLoader = new PluginUnstructuredResourceLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -35,14 +29,10 @@ public class PluginLoadedListener implements ApplicationListener<HaloPluginLoade
|
||||||
// load plugin.yaml
|
// load plugin.yaml
|
||||||
YamlPluginFinder yamlPluginFinder = new YamlPluginFinder();
|
YamlPluginFinder yamlPluginFinder = new YamlPluginFinder();
|
||||||
Plugin plugin = yamlPluginFinder.find(pluginWrapper.getPluginPath());
|
Plugin plugin = yamlPluginFinder.find(pluginWrapper.getPluginPath());
|
||||||
DefaultResourceLoader defaultResourceLoader =
|
|
||||||
new DefaultResourceLoader(pluginWrapper.getPluginClassLoader());
|
|
||||||
extensionClient.create(plugin);
|
extensionClient.create(plugin);
|
||||||
// load reverse proxy
|
|
||||||
Resource resource = defaultResourceLoader.getResource(REVERSE_PROXY_NAME);
|
// load plugin unstructured resource
|
||||||
if (resource.exists()) {
|
pluginUnstructuredResourceLoader.loadUnstructured(pluginWrapper)
|
||||||
YamlUnstructuredLoader unstructuredLoader = new YamlUnstructuredLoader(resource);
|
.forEach(extensionClient::create);
|
||||||
unstructuredLoader.load().forEach(extensionClient::create);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package run.halo.app.plugin;
|
||||||
|
|
||||||
|
import org.pf4j.PluginRuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception for plugin not found.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class PluginNotFoundException extends PluginRuntimeException {
|
||||||
|
public PluginNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginNotFoundException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package run.halo.app.plugin;
|
package run.halo.app.plugin;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for plugin.
|
* Service for plugin.
|
||||||
|
@ -16,4 +17,22 @@ public interface PluginService {
|
||||||
* @return all loaded plugins.
|
* @return all loaded plugins.
|
||||||
*/
|
*/
|
||||||
List<Plugin> list();
|
List<Plugin> list();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the plugin according to the plugin name.
|
||||||
|
*
|
||||||
|
* @param pluginName plugin name
|
||||||
|
* @return plugin custom resource
|
||||||
|
*/
|
||||||
|
Plugin startup(String pluginName);
|
||||||
|
|
||||||
|
Plugin stop(String pluginName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets {@link Plugin} by plugin name.
|
||||||
|
*
|
||||||
|
* @param pluginName plugin name
|
||||||
|
* @return plugin custom resource
|
||||||
|
*/
|
||||||
|
Plugin getByName(String pluginName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package run.halo.app.plugin;
|
package run.halo.app.plugin;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.pf4j.PluginState;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
||||||
|
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of {@link PluginService}.
|
* Default implementation of {@link PluginService}.
|
||||||
|
@ -15,8 +20,15 @@ public class PluginServiceImpl implements PluginService {
|
||||||
|
|
||||||
private final ExtensionClient extensionClient;
|
private final ExtensionClient extensionClient;
|
||||||
|
|
||||||
public PluginServiceImpl(ExtensionClient extensionClient) {
|
private final HaloPluginManager haloPluginManager;
|
||||||
|
|
||||||
|
private final JsBundleRuleProvider jsBundleRule;
|
||||||
|
|
||||||
|
public PluginServiceImpl(ExtensionClient extensionClient,
|
||||||
|
HaloPluginManager haloPluginManager, JsBundleRuleProvider jsBundleRule) {
|
||||||
this.extensionClient = extensionClient;
|
this.extensionClient = extensionClient;
|
||||||
|
this.haloPluginManager = haloPluginManager;
|
||||||
|
this.jsBundleRule = jsBundleRule;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,4 +39,59 @@ public class PluginServiceImpl implements PluginService {
|
||||||
public List<Plugin> list() {
|
public List<Plugin> list() {
|
||||||
return extensionClient.list(Plugin.class, null, null);
|
return extensionClient.list(Plugin.class, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plugin startup(String pluginName) {
|
||||||
|
Assert.notNull(pluginName, "The pluginName must not be null.");
|
||||||
|
PluginState currentState = haloPluginManager.startPlugin(pluginName);
|
||||||
|
|
||||||
|
Plugin plugin = handleStatus(pluginName, currentState, PluginState.STARTED);
|
||||||
|
Plugin.PluginStatus status = plugin.getStatus();
|
||||||
|
// TODO Check whether the JS bundle rule exists. If it does not exist, do not populate
|
||||||
|
// populate stylesheet path
|
||||||
|
String jsBundleRoute = ReverseProxyRouterFunctionFactory.buildRoutePath(pluginName,
|
||||||
|
jsBundleRule.jsRule(pluginName));
|
||||||
|
String cssBundleRoute = ReverseProxyRouterFunctionFactory.buildRoutePath(pluginName,
|
||||||
|
jsBundleRule.cssRule(pluginName));
|
||||||
|
status.setEntry(jsBundleRoute);
|
||||||
|
status.setStylesheet(cssBundleRoute);
|
||||||
|
extensionClient.update(plugin);
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Plugin handleStatus(String pluginName, PluginState currentState,
|
||||||
|
PluginState desiredState) {
|
||||||
|
Plugin plugin = getByName(pluginName);
|
||||||
|
Plugin.PluginStatus status = plugin.getStatus();
|
||||||
|
if (status == null) {
|
||||||
|
status = new Plugin.PluginStatus();
|
||||||
|
}
|
||||||
|
status.setPhase(currentState);
|
||||||
|
if (desiredState.equals(currentState)) {
|
||||||
|
plugin.getSpec().setEnabled(true);
|
||||||
|
} else {
|
||||||
|
PluginStartingError startingError =
|
||||||
|
haloPluginManager.getPluginStartingError(pluginName);
|
||||||
|
status.setReason(startingError.getMessage());
|
||||||
|
status.setMessage(startingError.getDevMessage());
|
||||||
|
}
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plugin stop(String pluginName) {
|
||||||
|
Assert.notNull(pluginName, "The pluginName must not be null.");
|
||||||
|
PluginState currentState = haloPluginManager.stopPlugin(pluginName);
|
||||||
|
Plugin plugin = handleStatus(pluginName, currentState, PluginState.STOPPED);
|
||||||
|
extensionClient.update(plugin);
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plugin getByName(String pluginName) {
|
||||||
|
Assert.notNull(pluginName, "The pluginName must not be null.");
|
||||||
|
return extensionClient.fetch(Plugin.class, pluginName)
|
||||||
|
.orElseThrow(() ->
|
||||||
|
new PluginNotFoundException(String.format("Plugin [%s] not found", pluginName)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.pf4j.PluginDescriptor;
|
||||||
import org.pf4j.PluginDescriptorFinder;
|
import org.pf4j.PluginDescriptorFinder;
|
||||||
import org.pf4j.util.FileUtils;
|
import org.pf4j.util.FileUtils;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a plugin descriptor for a plugin path.
|
* Find a plugin descriptor for a plugin path.
|
||||||
|
|
|
@ -7,9 +7,11 @@ import java.nio.file.Paths;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.PluginRuntimeException;
|
import org.pf4j.PluginRuntimeException;
|
||||||
|
import org.pf4j.PluginState;
|
||||||
import org.pf4j.util.FileUtils;
|
import org.pf4j.util.FileUtils;
|
||||||
import org.springframework.core.io.FileSystemResource;
|
import org.springframework.core.io.FileSystemResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
import run.halo.app.extension.Unstructured;
|
import run.halo.app.extension.Unstructured;
|
||||||
import run.halo.app.infra.utils.YamlUnstructuredLoader;
|
import run.halo.app.infra.utils.YamlUnstructuredLoader;
|
||||||
|
|
||||||
|
@ -58,7 +60,13 @@ public class YamlPluginFinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plugin find(Path pluginPath) {
|
public Plugin find(Path pluginPath) {
|
||||||
return readPluginDescriptor(pluginPath);
|
Plugin plugin = readPluginDescriptor(pluginPath);
|
||||||
|
if (plugin.getStatus() == null) {
|
||||||
|
Plugin.PluginStatus pluginStatus = new Plugin.PluginStatus();
|
||||||
|
pluginStatus.setPhase(PluginState.RESOLVED);
|
||||||
|
plugin.setStatus(pluginStatus);
|
||||||
|
}
|
||||||
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Plugin readPluginDescriptor(Path pluginPath) {
|
protected Plugin readPluginDescriptor(Path pluginPath) {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package run.halo.app.plugin.resources;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.core.extension.ReverseProxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Optimize code to support user customize js bundle rules.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class JsBundleRuleProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets plugin js bundle rule.
|
||||||
|
*
|
||||||
|
* @param pluginName plugin name
|
||||||
|
* @return a js bundle rule
|
||||||
|
*/
|
||||||
|
public ReverseProxy.ReverseProxyRule jsRule(String pluginName) {
|
||||||
|
ReverseProxy.FileReverseProxyProvider
|
||||||
|
file = new ReverseProxy.FileReverseProxyProvider("admin", "main.js");
|
||||||
|
return new ReverseProxy.ReverseProxyRule("/admin/main.js", file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets plugin stylesheet rule.
|
||||||
|
*
|
||||||
|
* @param pluginName plugin name
|
||||||
|
* @return a stylesheet bundle rule
|
||||||
|
*/
|
||||||
|
public ReverseProxy.ReverseProxyRule cssRule(String pluginName) {
|
||||||
|
ReverseProxy.FileReverseProxyProvider
|
||||||
|
file = new ReverseProxy.FileReverseProxyProvider("admin", "style.css");
|
||||||
|
return new ReverseProxy.ReverseProxyRule("/admin/style.css", file);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import static org.springframework.http.MediaType.ALL;
|
||||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -18,11 +19,12 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import run.halo.app.core.extension.ReverseProxy;
|
||||||
|
import run.halo.app.core.extension.ReverseProxy.FileReverseProxyProvider;
|
||||||
|
import run.halo.app.core.extension.ReverseProxy.ReverseProxyRule;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.infra.utils.PathUtils;
|
import run.halo.app.infra.utils.PathUtils;
|
||||||
import run.halo.app.plugin.PluginApplicationContext;
|
import run.halo.app.plugin.PluginApplicationContext;
|
||||||
import run.halo.app.plugin.resources.ReverseProxy.FileReverseProxyProvider;
|
|
||||||
import run.halo.app.plugin.resources.ReverseProxy.ReverseProxyRule;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Plugin's reverse proxy router factory.</p>
|
* <p>Plugin's reverse proxy router factory.</p>
|
||||||
|
@ -41,8 +43,12 @@ public class ReverseProxyRouterFunctionFactory {
|
||||||
|
|
||||||
private final ExtensionClient extensionClient;
|
private final ExtensionClient extensionClient;
|
||||||
|
|
||||||
public ReverseProxyRouterFunctionFactory(ExtensionClient extensionClient) {
|
private final JsBundleRuleProvider jsBundleRuleProvider;
|
||||||
|
|
||||||
|
public ReverseProxyRouterFunctionFactory(ExtensionClient extensionClient,
|
||||||
|
JsBundleRuleProvider jsBundleRuleProvider) {
|
||||||
this.extensionClient = extensionClient;
|
this.extensionClient = extensionClient;
|
||||||
|
this.jsBundleRuleProvider = jsBundleRuleProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,7 +95,7 @@ public class ReverseProxyRouterFunctionFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ReverseProxyRule> getReverseProxyRules(String pluginId) {
|
private List<ReverseProxyRule> getReverseProxyRules(String pluginId) {
|
||||||
return extensionClient.list(ReverseProxy.class,
|
List<ReverseProxyRule> rules = extensionClient.list(ReverseProxy.class,
|
||||||
reverseProxy -> {
|
reverseProxy -> {
|
||||||
String pluginName = reverseProxy.getMetadata()
|
String pluginName = reverseProxy.getMetadata()
|
||||||
.getLabels()
|
.getLabels()
|
||||||
|
@ -101,9 +107,27 @@ public class ReverseProxyRouterFunctionFactory {
|
||||||
.map(ReverseProxy::getRules)
|
.map(ReverseProxy::getRules)
|
||||||
.flatMap(List::stream)
|
.flatMap(List::stream)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// populate plugin js bundle rules.
|
||||||
|
rules.addAll(getJsBundleRules(pluginId));
|
||||||
|
return rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildRoutePath(String pluginId, ReverseProxyRule reverseProxyRule) {
|
private List<ReverseProxyRule> getJsBundleRules(String pluginId) {
|
||||||
|
List<ReverseProxyRule> rules = new ArrayList<>(2);
|
||||||
|
ReverseProxyRule jsRule = jsBundleRuleProvider.jsRule(pluginId);
|
||||||
|
if (jsRule != null) {
|
||||||
|
rules.add(jsRule);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReverseProxyRule cssRule = jsBundleRuleProvider.cssRule(pluginId);
|
||||||
|
if (cssRule != null) {
|
||||||
|
rules.add(cssRule);
|
||||||
|
}
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String buildRoutePath(String pluginId, ReverseProxyRule reverseProxyRule) {
|
||||||
return PathUtils.combinePath(REVERSE_PROXY_API_PREFIX, pluginId, reverseProxyRule.path());
|
return PathUtils.combinePath(REVERSE_PROXY_API_PREFIX, pluginId, reverseProxyRule.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,19 +68,19 @@ class PluginLifeCycleManagerControllerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void start() {
|
void start() {
|
||||||
webClient.get()
|
webClient.put()
|
||||||
.uri(prefix + "/apples/startup")
|
.uri(prefix + "/apples/startup")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus()
|
.expectStatus()
|
||||||
.isOk();
|
.is5xxServerError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void stop() {
|
void stop() {
|
||||||
webClient.get()
|
webClient.put()
|
||||||
.uri(prefix + "/apples/stop")
|
.uri(prefix + "/apples/stop")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus()
|
.expectStatus()
|
||||||
.isOk();
|
.is5xxServerError();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,6 +16,7 @@ import org.skyscreamer.jsonassert.JSONAssert;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.security.util.InMemoryResource;
|
import org.springframework.security.util.InMemoryResource;
|
||||||
import org.springframework.util.ResourceUtils;
|
import org.springframework.util.ResourceUtils;
|
||||||
|
import run.halo.app.core.extension.Plugin;
|
||||||
import run.halo.app.extension.Unstructured;
|
import run.halo.app.extension.Unstructured;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
|
@ -52,12 +53,24 @@ class YamlPluginFinderTest {
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/guqing/halo-plugin-1",
|
"homepage": "https://github.com/guqing/halo-plugin-1",
|
||||||
"description": "Tell me more about this plugin.",
|
"description": "Tell me more about this plugin.",
|
||||||
"license": [{
|
"license": [
|
||||||
|
{
|
||||||
"name": "MIT",
|
"name": "MIT",
|
||||||
"url": ""
|
"url": ""
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
"requires": ">=2.0.0",
|
"requires": ">=2.0.0",
|
||||||
"pluginClass": "run.halo.app.plugin.BasePlugin"
|
"pluginClass": "run.halo.app.plugin.BasePlugin",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"phase": "RESOLVED",
|
||||||
|
"reason": null,
|
||||||
|
"message": null,
|
||||||
|
"lastStartTime": null,
|
||||||
|
"lastTransitionTime": null,
|
||||||
|
"entry": null,
|
||||||
|
"stylesheet": null
|
||||||
},
|
},
|
||||||
"apiVersion": "plugin.halo.run/v1alpha1",
|
"apiVersion": "plugin.halo.run/v1alpha1",
|
||||||
"kind": "Plugin",
|
"kind": "Plugin",
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import run.halo.app.core.extension.ReverseProxy;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.plugin.PluginApplicationContext;
|
import run.halo.app.plugin.PluginApplicationContext;
|
||||||
|
@ -37,7 +38,9 @@ class ReverseProxyRouterFunctionFactoryTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
reverseProxyRouterFunctionFactory = new ReverseProxyRouterFunctionFactory(extensionClient);
|
JsBundleRuleProvider jsBundleRuleProvider = new JsBundleRuleProvider();
|
||||||
|
reverseProxyRouterFunctionFactory = new ReverseProxyRouterFunctionFactory(extensionClient,
|
||||||
|
jsBundleRuleProvider);
|
||||||
|
|
||||||
ReverseProxy reverseProxy = mockReverseProxy();
|
ReverseProxy reverseProxy = mockReverseProxy();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue