refactor: initialize default config value by settings after plugin installation (#3161)

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

#### What this PR does / why we need it:
插件安装后根据配置的 settingName 读取默认值创建 ConfigMap
如果配置了 settingName 而没有配置 configMapName 则会自动填充为 uuid 并创建一个 ConfigMap
#### Which issue(s) this PR fixes:

Fixes #3160

#### Special notes for your reviewer:
/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

```release-note
插件安装后自动初始化 Setting 的默认值
```
pull/3142/head^2
guqing 2023-01-19 16:56:15 +08:00 committed by GitHub
parent 2241c08371
commit ca4e93d4bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 174 additions and 1 deletions

View File

@ -18,9 +18,14 @@ import org.pf4j.PluginRuntimeException;
import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import run.halo.app.core.extension.Plugin;
import run.halo.app.core.extension.ReverseProxy;
import run.halo.app.core.extension.Setting;
import run.halo.app.core.extension.theme.SettingUtils;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.controller.Controller;
@ -32,6 +37,7 @@ import run.halo.app.infra.utils.PathUtils;
import run.halo.app.plugin.HaloPluginManager;
import run.halo.app.plugin.PluginConst;
import run.halo.app.plugin.PluginStartingError;
import run.halo.app.plugin.event.PluginCreatedEvent;
import run.halo.app.plugin.resources.BundleResourceUtils;
/**
@ -47,6 +53,7 @@ public class PluginReconciler implements Reconciler<Request> {
private static final String FINALIZER_NAME = "plugin-protection";
private final ExtensionClient client;
private final HaloPluginManager haloPluginManager;
private final ApplicationEventPublisher eventPublisher;
@Override
public Result reconcile(Request request) {
@ -146,6 +153,8 @@ public class PluginReconciler implements Reconciler<Request> {
plugin.statusNonNull().setLastStartTime(Instant.now());
}
settingDefaultConfig(plugin);
Plugin.PluginStatus status = plugin.statusNonNull();
String jsBundlePath =
BundleResourceUtils.getJsBundlePath(haloPluginManager, pluginName);
@ -257,6 +266,9 @@ public class PluginReconciler implements Reconciler<Request> {
}
newFinalizers.add(FINALIZER_NAME);
client.update(plugin);
eventPublisher.publishEvent(
new PluginCreatedEvent(this, plugin.getMetadata().getName()));
});
}
@ -321,6 +333,35 @@ public class PluginReconciler implements Reconciler<Request> {
}, () -> client.create(reverseProxy));
}
private void settingDefaultConfig(Plugin plugin) {
Assert.notNull(plugin, "The plugin must not be null.");
if (StringUtils.isBlank(plugin.getSpec().getSettingName())) {
return;
}
final String configMapNameToUse = plugin.getSpec().getConfigMapName();
if (StringUtils.isBlank(configMapNameToUse)) {
return;
}
boolean existConfigMap = client.fetch(ConfigMap.class, configMapNameToUse)
.isPresent();
if (existConfigMap) {
return;
}
client.fetch(Setting.class, plugin.getSpec().getSettingName())
.ifPresent(setting -> {
var data = SettingUtils.settingDefinedDefaultValueMap(setting);
// Create with or without default value
ConfigMap configMap = new ConfigMap();
configMap.setMetadata(new Metadata());
configMap.getMetadata().setName(configMapNameToUse);
configMap.setData(data);
client.create(configMap);
});
}
static String initialReverseProxyName(String pluginName) {
return pluginName + "-system-generated-reverse-proxy";
}

View File

@ -0,0 +1,107 @@
package run.halo.app.plugin;
import io.micrometer.common.util.StringUtils;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import reactor.core.Exceptions;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Plugin;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.DefaultController;
import run.halo.app.extension.controller.DefaultDelayQueue;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.RequestQueue;
import run.halo.app.plugin.event.PluginCreatedEvent;
/**
* Plugin event reconciler.
* If other plugin events need to be reconciled, consider sharing this reconciler.
*
* @author guqing
* @since 2.2.0
*/
@Slf4j
@Component
public class PluginCreatedEventReconciler
implements Reconciler<PluginCreatedEvent>, SmartLifecycle {
private final RequestQueue<PluginCreatedEvent> pluginEventQueue;
private final ReactiveExtensionClient client;
private final Controller pluginEventController;
private boolean running = false;
public PluginCreatedEventReconciler(ReactiveExtensionClient client) {
this.client = client;
pluginEventQueue = new DefaultDelayQueue<>(Instant::now);
pluginEventController = this.setupWith(null);
}
@Override
public Result reconcile(PluginCreatedEvent pluginCreatedEvent) {
String pluginName = pluginCreatedEvent.getPluginName();
try {
ensureConfigMapNameNotEmptyIfSettingIsNotBlank(pluginName);
} catch (InterruptedException e) {
throw Exceptions.propagate(e);
}
return null;
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return new DefaultController<>(
this.getClass().getName(),
this,
pluginEventQueue,
null,
Duration.ofMillis(100),
Duration.ofSeconds(1000)
);
}
@EventListener(PluginCreatedEvent.class)
public void handlePluginCreated(PluginCreatedEvent pluginCreatedEvent) {
pluginEventQueue.addImmediately(pluginCreatedEvent);
}
void ensureConfigMapNameNotEmptyIfSettingIsNotBlank(String pluginName)
throws InterruptedException {
client.fetch(Plugin.class, pluginName)
.switchIfEmpty(Mono.error(new IllegalStateException("Plugin not found: " + pluginName)))
.filter(plugin -> StringUtils.isNotBlank(plugin.getSpec().getSettingName()))
.filter(plugin -> StringUtils.isBlank(plugin.getSpec().getConfigMapName()))
.doOnNext(plugin -> {
// has settingName value but configMapName not configured
plugin.getSpec().setConfigMapName(UUID.randomUUID().toString());
})
.flatMap(client::update)
.block();
}
@Override
public void start() {
pluginEventController.start();
running = true;
}
@Override
public void stop() {
running = false;
pluginEventController.dispose();
}
@Override
public boolean isRunning() {
return running;
}
}

View File

@ -0,0 +1,21 @@
package run.halo.app.plugin.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import run.halo.app.core.extension.Plugin;
/**
* The {@link Plugin} created event.
*
* @author guqing
* @since 2.0.0
*/
@Getter
public class PluginCreatedEvent extends ApplicationEvent {
private final String pluginName;
public PluginCreatedEvent(Object source, String pluginName) {
super(source);
this.pluginName = pluginName;
}
}

View File

@ -28,6 +28,7 @@ import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.context.ApplicationEventPublisher;
import run.halo.app.core.extension.Plugin;
import run.halo.app.core.extension.ReverseProxy;
import run.halo.app.extension.ExtensionClient;
@ -55,11 +56,14 @@ class PluginReconcilerTest {
@Mock
PluginWrapper pluginWrapper;
@Mock
ApplicationEventPublisher eventPublisher;
PluginReconciler pluginReconciler;
@BeforeEach
void setUp() {
pluginReconciler = new PluginReconciler(extensionClient, haloPluginManager);
pluginReconciler = new PluginReconciler(extensionClient, haloPluginManager, eventPublisher);
lenient().when(haloPluginManager.validatePluginVersion(any())).thenReturn(true);
lenient().when(haloPluginManager.getSystemVersion()).thenReturn("0.0.0");
lenient().when(haloPluginManager.getPlugin(any())).thenReturn(pluginWrapper);