mirror of https://github.com/halo-dev/halo
feat: the ConfigMap named system to store user-defined configurations (#2415)
#### What type of PR is this? /kind improvement /area core /milestone 2.0 #### What this PR does / why we need it: 将原来系统默认的系统配置 system 改名为 system-default, 并使用名为 system 的 ConfigMap 来存储用户自定义的系统配置。系统最终配置为用户自定义系统配置 Merge Patch 系统默认配置的结果。 see also #2304 #### Which issue(s) this PR fixes: Fixes #2304 #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note None ```pull/2443/head
parent
1714f8edb2
commit
ac8dd74211
|
@ -46,6 +46,7 @@ ext {
|
||||||
javaDiffUtils = "4.12"
|
javaDiffUtils = "4.12"
|
||||||
guava = "31.1-jre"
|
guava = "31.1-jre"
|
||||||
jsoup = "1.15.2"
|
jsoup = "1.15.2"
|
||||||
|
jsonPatch = "1.13"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -75,6 +76,7 @@ dependencies {
|
||||||
implementation "org.jsoup:jsoup:$jsoup"
|
implementation "org.jsoup:jsoup:$jsoup"
|
||||||
implementation "io.github.java-diff-utils:java-diff-utils:$javaDiffUtils"
|
implementation "io.github.java-diff-utils:java-diff-utils:$javaDiffUtils"
|
||||||
implementation "org.springframework.integration:spring-integration-core"
|
implementation "org.springframework.integration:spring-integration-core"
|
||||||
|
implementation "com.github.java-json-tools:json-patch:$jsonPatch"
|
||||||
|
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
testCompileOnly 'org.projectlombok:lombok'
|
testCompileOnly 'org.projectlombok:lombok'
|
||||||
|
|
|
@ -51,6 +51,7 @@ import run.halo.app.extension.controller.ControllerBuilder;
|
||||||
import run.halo.app.extension.controller.ControllerManager;
|
import run.halo.app.extension.controller.ControllerManager;
|
||||||
import run.halo.app.extension.router.ExtensionCompositeRouterFunction;
|
import run.halo.app.extension.router.ExtensionCompositeRouterFunction;
|
||||||
import run.halo.app.infra.ExternalUrlSupplier;
|
import run.halo.app.infra.ExternalUrlSupplier;
|
||||||
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.properties.HaloProperties;
|
import run.halo.app.infra.properties.HaloProperties;
|
||||||
import run.halo.app.plugin.HaloPluginManager;
|
import run.halo.app.plugin.HaloPluginManager;
|
||||||
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
||||||
|
@ -171,9 +172,11 @@ public class ExtensionConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
Controller systemSettingController(ExtensionClient client,
|
Controller systemSettingController(ExtensionClient client,
|
||||||
|
SystemConfigurableEnvironmentFetcher environmentFetcher,
|
||||||
ApplicationContext applicationContext) {
|
ApplicationContext applicationContext) {
|
||||||
return new ControllerBuilder("system-setting-controller", client)
|
return new ControllerBuilder("system-setting-controller", client)
|
||||||
.reconciler(new SystemSettingReconciler(client, applicationContext))
|
.reconciler(new SystemSettingReconciler(client, environmentFetcher,
|
||||||
|
applicationContext))
|
||||||
.extension(new ConfigMap())
|
.extension(new ConfigMap())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,16 @@ package run.halo.app.core.extension.reconciler;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import run.halo.app.extension.ConfigMap;
|
import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
import run.halo.app.infra.utils.PathUtils;
|
import run.halo.app.infra.utils.PathUtils;
|
||||||
|
@ -28,12 +31,16 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||||
public static final String FINALIZER_NAME = "system-setting-protection";
|
public static final String FINALIZER_NAME = "system-setting-protection";
|
||||||
|
|
||||||
private final ExtensionClient client;
|
private final ExtensionClient client;
|
||||||
|
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
private final RouteRuleReconciler routeRuleReconciler = new RouteRuleReconciler();
|
private final RouteRuleReconciler routeRuleReconciler = new RouteRuleReconciler();
|
||||||
|
|
||||||
public SystemSettingReconciler(ExtensionClient client, ApplicationContext applicationContext) {
|
public SystemSettingReconciler(ExtensionClient client,
|
||||||
|
SystemConfigurableEnvironmentFetcher environmentFetcher,
|
||||||
|
ApplicationContext applicationContext) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.environmentFetcher = environmentFetcher;
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,11 +54,44 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||||
.ifPresent(configMap -> {
|
.ifPresent(configMap -> {
|
||||||
addFinalizerIfNecessary(configMap);
|
addFinalizerIfNecessary(configMap);
|
||||||
routeRuleReconciler.reconcile(name);
|
routeRuleReconciler.reconcile(name);
|
||||||
|
customizeSystem(name);
|
||||||
});
|
});
|
||||||
return new Result(false, null);
|
return new Result(false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void customizeSystem(String name) {
|
||||||
|
if (!SystemSetting.SYSTEM_CONFIG_DEFAULT.equals(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// configMap named system not found then create it by system-default
|
||||||
|
Optional<ConfigMap> systemOpt = client.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG);
|
||||||
|
if (systemOpt.isPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ConfigMap system = client.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG_DEFAULT)
|
||||||
|
.map(configMap -> {
|
||||||
|
// create a new configMap named system by system-default
|
||||||
|
ConfigMap systemConfigMap = new ConfigMap();
|
||||||
|
systemConfigMap.setMetadata(new Metadata());
|
||||||
|
systemConfigMap.getMetadata().setName(SystemSetting.SYSTEM_CONFIG);
|
||||||
|
systemConfigMap.setData(configMap.getData());
|
||||||
|
return systemConfigMap;
|
||||||
|
})
|
||||||
|
.orElseGet(() -> {
|
||||||
|
// empty configMap named system
|
||||||
|
ConfigMap configMap = new ConfigMap();
|
||||||
|
configMap.setMetadata(new Metadata());
|
||||||
|
configMap.getMetadata().setName(SystemSetting.SYSTEM_CONFIG);
|
||||||
|
configMap.setData(new HashMap<>());
|
||||||
|
return configMap;
|
||||||
|
});
|
||||||
|
client.create(system);
|
||||||
|
}
|
||||||
|
|
||||||
private void addFinalizerIfNecessary(ConfigMap oldConfigMap) {
|
private void addFinalizerIfNecessary(ConfigMap oldConfigMap) {
|
||||||
|
if (SystemSetting.SYSTEM_CONFIG.equals(oldConfigMap.getMetadata().getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Set<String> finalizers = oldConfigMap.getMetadata().getFinalizers();
|
Set<String> finalizers = oldConfigMap.getMetadata().getFinalizers();
|
||||||
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||||
return;
|
return;
|
||||||
|
@ -78,7 +118,7 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reconcileArchivesRule(String name) {
|
private void reconcileArchivesRule(String name) {
|
||||||
client.fetch(ConfigMap.class, name).ifPresent(configMap -> {
|
getConfigMap(name).ifPresent(configMap -> {
|
||||||
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
||||||
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
||||||
|
|
||||||
|
@ -128,7 +168,7 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reconcileTagsRule(String name) {
|
private void reconcileTagsRule(String name) {
|
||||||
client.fetch(ConfigMap.class, name).ifPresent(configMap -> {
|
getConfigMap(name).ifPresent(configMap -> {
|
||||||
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
||||||
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
||||||
final String oldTagsPrefix = oldRules.getTags();
|
final String oldTagsPrefix = oldRules.getTags();
|
||||||
|
@ -147,7 +187,7 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reconcileCategoriesRule(String name) {
|
private void reconcileCategoriesRule(String name) {
|
||||||
client.fetch(ConfigMap.class, name).ifPresent(configMap -> {
|
getConfigMap(name).ifPresent(configMap -> {
|
||||||
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
||||||
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
||||||
final String oldCategoriesPrefix = oldRules.getCategories();
|
final String oldCategoriesPrefix = oldRules.getCategories();
|
||||||
|
@ -166,7 +206,7 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reconcilePostRule(String name) {
|
private void reconcilePostRule(String name) {
|
||||||
client.fetch(ConfigMap.class, name).ifPresent(configMap -> {
|
getConfigMap(name).ifPresent(configMap -> {
|
||||||
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
||||||
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
||||||
|
|
||||||
|
@ -239,6 +279,11 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSystemSetting(String name) {
|
public boolean isSystemSetting(String name) {
|
||||||
return SystemSetting.SYSTEM_CONFIG.equals(name);
|
return SystemSetting.SYSTEM_CONFIG.equals(name)
|
||||||
|
|| SystemSetting.SYSTEM_CONFIG_DEFAULT.equals(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ConfigMap> getConfigMap(String name) {
|
||||||
|
return environmentFetcher.getConfigMapBlocking();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
package run.halo.app.infra;
|
package run.halo.app.infra;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import com.github.fge.jsonpatch.JsonPatchException;
|
||||||
|
import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.core.convert.ConversionService;
|
import org.springframework.core.convert.ConversionService;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.extension.ConfigMap;
|
import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.infra.utils.JsonParseException;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A fetcher that fetches the system configuration from the extension client.
|
||||||
|
* If there are {@link ConfigMap}s named <code>system-default</code> and <code>system</code> at
|
||||||
|
* the same time, the {@link ConfigMap} named system will be json merge patch to
|
||||||
|
* {@link ConfigMap} named <code>system-default</code>
|
||||||
|
*
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
|
@ -49,7 +62,80 @@ public class SystemConfigurableEnvironmentFetcher {
|
||||||
.defaultIfEmpty(Map.of());
|
.defaultIfEmpty(Map.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets config map.
|
||||||
|
*
|
||||||
|
* @return a new {@link ConfigMap} named <code>system</code> by json merge patch.
|
||||||
|
*/
|
||||||
public Mono<ConfigMap> getConfigMap() {
|
public Mono<ConfigMap> getConfigMap() {
|
||||||
return extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG);
|
return extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG_DEFAULT)
|
||||||
|
.flatMap(systemDefault ->
|
||||||
|
extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG)
|
||||||
|
.map(system -> {
|
||||||
|
Map<String, String> defaultData = systemDefault.getData();
|
||||||
|
Map<String, String> data = system.getData();
|
||||||
|
Map<String, String> mergedData = mergeData(defaultData, data);
|
||||||
|
system.setData(mergedData);
|
||||||
|
return system;
|
||||||
|
})
|
||||||
|
.switchIfEmpty(Mono.just(systemDefault)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ConfigMap> getConfigMapBlocking() {
|
||||||
|
return getConfigMap().blockOptional();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> mergeData(Map<String, String> defaultData,
|
||||||
|
Map<String, String> data) {
|
||||||
|
if (defaultData == null) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
if (data == null) {
|
||||||
|
return defaultData;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> copiedDefault = new LinkedHashMap<>(defaultData);
|
||||||
|
// // merge the data map entries into the default map
|
||||||
|
data.forEach((group, dataValue) -> {
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7386
|
||||||
|
String defaultV = copiedDefault.get(group);
|
||||||
|
String newValue;
|
||||||
|
if (dataValue == null) {
|
||||||
|
if (copiedDefault.containsKey(group)) {
|
||||||
|
newValue = null;
|
||||||
|
} else {
|
||||||
|
newValue = defaultV;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newValue = mergeRemappingFunction(dataValue, defaultV);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue == null) {
|
||||||
|
copiedDefault.remove(group);
|
||||||
|
} else {
|
||||||
|
copiedDefault.put(group, newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return copiedDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mergeRemappingFunction(String dataV, String defaultV) {
|
||||||
|
JsonNode dataJsonValue = nullSafeToJsonNode(dataV);
|
||||||
|
// original
|
||||||
|
JsonNode defaultJsonValue = nullSafeToJsonNode(defaultV);
|
||||||
|
try {
|
||||||
|
// patch
|
||||||
|
JsonMergePatch jsonMergePatch = JsonMergePatch.fromJson(dataJsonValue);
|
||||||
|
// apply patch to original
|
||||||
|
JsonNode patchedNode = jsonMergePatch.apply(defaultJsonValue);
|
||||||
|
return JsonUtils.objectToJson(patchedNode);
|
||||||
|
} catch (JsonPatchException e) {
|
||||||
|
throw new JsonParseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode nullSafeToJsonNode(String json) {
|
||||||
|
return StringUtils.isBlank(json) ? JsonNodeFactory.instance.nullNode()
|
||||||
|
: JsonUtils.jsonToObject(json, JsonNode.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import lombok.Data;
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class SystemSetting {
|
public class SystemSetting {
|
||||||
|
public static final String SYSTEM_CONFIG_DEFAULT = "system-default";
|
||||||
public static final String SYSTEM_CONFIG = "system";
|
public static final String SYSTEM_CONFIG = "system";
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|
|
@ -16,7 +16,7 @@ import run.halo.app.infra.utils.JsonParseException;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>A value fetcher for pPlugin form configuration.</p>
|
* <p>A value fetcher for plugin form configuration.</p>
|
||||||
*
|
*
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
|
|
|
@ -2,8 +2,7 @@ package run.halo.app.theme.router;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import run.halo.app.extension.ConfigMap;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
import run.halo.app.theme.DefaultTemplateEnum;
|
import run.halo.app.theme.DefaultTemplateEnum;
|
||||||
|
@ -18,14 +17,14 @@ import run.halo.app.theme.DefaultTemplateEnum;
|
||||||
@Component
|
@Component
|
||||||
public class PermalinkPatternProvider {
|
public class PermalinkPatternProvider {
|
||||||
|
|
||||||
private final ExtensionClient client;
|
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
|
||||||
public PermalinkPatternProvider(ExtensionClient client) {
|
public PermalinkPatternProvider(SystemConfigurableEnvironmentFetcher environmentFetcher) {
|
||||||
this.client = client;
|
this.environmentFetcher = environmentFetcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SystemSetting.ThemeRouteRules getPermalinkRules() {
|
private SystemSetting.ThemeRouteRules getPermalinkRules() {
|
||||||
return client.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG)
|
return environmentFetcher.getConfigMapBlocking()
|
||||||
.map(configMap -> {
|
.map(configMap -> {
|
||||||
Map<String, String> data = configMap.getData();
|
Map<String, String> data = configMap.getData();
|
||||||
return data.get(SystemSetting.ThemeRouteRules.GROUP);
|
return data.get(SystemSetting.ThemeRouteRules.GROUP);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
apiVersion: v1alpha1
|
apiVersion: v1alpha1
|
||||||
kind: "ConfigMap"
|
kind: "ConfigMap"
|
||||||
metadata:
|
metadata:
|
||||||
name: system
|
name: system-default
|
||||||
data:
|
data:
|
||||||
theme: |
|
theme: |
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,6 +36,7 @@ import run.halo.app.core.extension.service.UserService;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||||
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
|
@ -55,6 +56,9 @@ class UserEndpointTest {
|
||||||
@MockBean
|
@MockBean
|
||||||
UserService userService;
|
UserService userService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
// disable authorization
|
// disable authorization
|
||||||
|
|
|
@ -8,7 +8,6 @@ import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
@ -23,6 +22,7 @@ import run.halo.app.extension.ConfigMap;
|
||||||
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.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
|
@ -41,11 +41,15 @@ class SystemSettingReconcilerTest {
|
||||||
@Mock
|
@Mock
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
|
||||||
private SystemSettingReconciler systemSettingReconciler;
|
private SystemSettingReconciler systemSettingReconciler;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
systemSettingReconciler = new SystemSettingReconciler(client, applicationContext);
|
systemSettingReconciler = new SystemSettingReconciler(client, environmentFetcher,
|
||||||
|
applicationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -54,14 +58,14 @@ class SystemSettingReconcilerTest {
|
||||||
rules.setArchives("archives-new");
|
rules.setArchives("archives-new");
|
||||||
return rules;
|
return rules;
|
||||||
});
|
});
|
||||||
|
when(environmentFetcher.getConfigMapBlocking()).thenReturn(Optional.of(configMap));
|
||||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
||||||
.thenReturn(Optional.of(configMap));
|
.thenReturn(Optional.of(configMap));
|
||||||
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
||||||
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
||||||
verify(client, times(2)).update(captor.capture());
|
verify(client, times(1)).update(captor.capture());
|
||||||
|
|
||||||
List<ConfigMap> allValues = captor.getAllValues();
|
ConfigMap updatedConfigMap = captor.getValue();
|
||||||
ConfigMap updatedConfigMap = allValues.get(1);
|
|
||||||
assertThat(rulesFrom(updatedConfigMap).getArchives()).isEqualTo("archives-new");
|
assertThat(rulesFrom(updatedConfigMap).getArchives()).isEqualTo("archives-new");
|
||||||
assertThat(rulesFrom(updatedConfigMap).getPost()).isEqualTo("/archives-new/{slug}");
|
assertThat(rulesFrom(updatedConfigMap).getPost()).isEqualTo("/archives-new/{slug}");
|
||||||
|
|
||||||
|
@ -78,14 +82,14 @@ class SystemSettingReconcilerTest {
|
||||||
rules.setTags("tags-new");
|
rules.setTags("tags-new");
|
||||||
return rules;
|
return rules;
|
||||||
});
|
});
|
||||||
|
when(environmentFetcher.getConfigMapBlocking()).thenReturn(Optional.of(configMap));
|
||||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
||||||
.thenReturn(Optional.of(configMap));
|
.thenReturn(Optional.of(configMap));
|
||||||
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
||||||
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
||||||
verify(client, times(2)).update(captor.capture());
|
verify(client, times(1)).update(captor.capture());
|
||||||
|
|
||||||
List<ConfigMap> allValues = captor.getAllValues();
|
ConfigMap updatedConfigMap = captor.getValue();
|
||||||
ConfigMap updatedConfigMap = allValues.get(1);
|
|
||||||
assertThat(rulesFrom(updatedConfigMap).getTags()).isEqualTo("tags-new");
|
assertThat(rulesFrom(updatedConfigMap).getTags()).isEqualTo("tags-new");
|
||||||
|
|
||||||
assertThat(oldRulesFromAnno(updatedConfigMap).getTags()).isEqualTo("tags-new");
|
assertThat(oldRulesFromAnno(updatedConfigMap).getTags()).isEqualTo("tags-new");
|
||||||
|
@ -99,14 +103,14 @@ class SystemSettingReconcilerTest {
|
||||||
rules.setCategories("categories-new");
|
rules.setCategories("categories-new");
|
||||||
return rules;
|
return rules;
|
||||||
});
|
});
|
||||||
|
when(environmentFetcher.getConfigMapBlocking()).thenReturn(Optional.of(configMap));
|
||||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
||||||
.thenReturn(Optional.of(configMap));
|
.thenReturn(Optional.of(configMap));
|
||||||
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
||||||
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
||||||
verify(client, times(2)).update(captor.capture());
|
verify(client, times(1)).update(captor.capture());
|
||||||
|
|
||||||
List<ConfigMap> allValues = captor.getAllValues();
|
ConfigMap updatedConfigMap = captor.getValue();
|
||||||
ConfigMap updatedConfigMap = allValues.get(1);
|
|
||||||
assertThat(rulesFrom(updatedConfigMap).getCategories()).isEqualTo("categories-new");
|
assertThat(rulesFrom(updatedConfigMap).getCategories()).isEqualTo("categories-new");
|
||||||
|
|
||||||
assertThat(oldRulesFromAnno(updatedConfigMap).getCategories()).isEqualTo("categories-new");
|
assertThat(oldRulesFromAnno(updatedConfigMap).getCategories()).isEqualTo("categories-new");
|
||||||
|
@ -120,14 +124,14 @@ class SystemSettingReconcilerTest {
|
||||||
rules.setPost("/post-new/{slug}");
|
rules.setPost("/post-new/{slug}");
|
||||||
return rules;
|
return rules;
|
||||||
});
|
});
|
||||||
|
when(environmentFetcher.getConfigMapBlocking()).thenReturn(Optional.of(configMap));
|
||||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
||||||
.thenReturn(Optional.of(configMap));
|
.thenReturn(Optional.of(configMap));
|
||||||
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
||||||
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
||||||
verify(client, times(2)).update(captor.capture());
|
verify(client, times(1)).update(captor.capture());
|
||||||
|
|
||||||
List<ConfigMap> allValues = captor.getAllValues();
|
ConfigMap updatedConfigMap = captor.getValue();
|
||||||
ConfigMap updatedConfigMap = allValues.get(1);
|
|
||||||
assertThat(rulesFrom(updatedConfigMap).getPost()).isEqualTo("/post-new/{slug}");
|
assertThat(rulesFrom(updatedConfigMap).getPost()).isEqualTo("/post-new/{slug}");
|
||||||
|
|
||||||
assertThat(oldRulesFromAnno(updatedConfigMap).getPost()).isEqualTo("/post-new/{slug}");
|
assertThat(oldRulesFromAnno(updatedConfigMap).getPost()).isEqualTo("/post-new/{slug}");
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
package run.halo.app.infra;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.skyscreamer.jsonassert.JSONAssert;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
import run.halo.app.extension.ConfigMap;
|
||||||
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SystemConfigurableEnvironmentFetcher}.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class SystemConfigurableEnvironmentFetcherTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ReactiveExtensionClient client;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
lenient().when(client.fetch(eq(ConfigMap.class), eq("system-default")))
|
||||||
|
.thenReturn(Mono.just(systemDefault()));
|
||||||
|
lenient().when(client.fetch(eq(ConfigMap.class), eq("system")))
|
||||||
|
.thenReturn(Mono.just(system()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getConfigMap() {
|
||||||
|
environmentFetcher.getConfigMap()
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
.consumeNextWith(configMap -> {
|
||||||
|
assertThat(configMap.getMetadata().getName())
|
||||||
|
.isEqualTo(SystemSetting.SYSTEM_CONFIG);
|
||||||
|
try {
|
||||||
|
JSONAssert.assertEquals(expectedJson(),
|
||||||
|
JsonUtils.objectToJson(configMap),
|
||||||
|
true);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
String expectedJson() {
|
||||||
|
String routeRules =
|
||||||
|
"{\\\"categories\\\":\\\"topics\\\",\\\"archives\\\":\\\"archives-new\\\","
|
||||||
|
+ "\\\"post\\\":\\\"/archives-new/{slug}\\\"}";
|
||||||
|
String fakeArray = "{\\\"select\\\":[{\\\"label\\\":\\\"Hello\\\","
|
||||||
|
+ "\\\"value\\\":\\\"hello\\\"},{\\\"label\\\":\\\"Awesome\\\","
|
||||||
|
+ "\\\"value\\\":\\\"awesome\\\"}]}";
|
||||||
|
return """
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"routeRules": "%s",
|
||||||
|
"seo": "{\\"blockSpiders\\":\\"true\\",\\"keywords\\":\\"Hello,Test,Fake\\"}",
|
||||||
|
"fakeArray": "%s"
|
||||||
|
},
|
||||||
|
"apiVersion": "v1alpha1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": {
|
||||||
|
"name": "system"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".formatted(routeRules, fakeArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigMap systemDefault() {
|
||||||
|
ConfigMap configMap = new ConfigMap();
|
||||||
|
configMap.setMetadata(new Metadata());
|
||||||
|
configMap.getMetadata().setName("system-default");
|
||||||
|
configMap.setData(new LinkedHashMap<>());
|
||||||
|
configMap.getData().put("routeRules", """
|
||||||
|
{
|
||||||
|
"categories": "categories",
|
||||||
|
"archives": "archives",
|
||||||
|
"post": "/archives/{slug}",
|
||||||
|
"tags": "tags"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
configMap.getData().put("seo", """
|
||||||
|
{
|
||||||
|
"blockSpiders": "false",
|
||||||
|
"keywords": "Hello,Test,Fake"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
configMap.getData().put("post", """
|
||||||
|
{
|
||||||
|
"pageSize": "10"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
configMap.getData().put("fakeArray", """
|
||||||
|
{
|
||||||
|
"select": [{
|
||||||
|
"label": "Hello",
|
||||||
|
"value": "hello"
|
||||||
|
}, {
|
||||||
|
"label": "Test",
|
||||||
|
"value": "test"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
return configMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigMap system() {
|
||||||
|
ConfigMap configMap = new ConfigMap();
|
||||||
|
configMap.setMetadata(new Metadata());
|
||||||
|
configMap.getMetadata().setName("system");
|
||||||
|
configMap.setData(new LinkedHashMap<>());
|
||||||
|
// will delete the tags key and replace some values
|
||||||
|
configMap.getData().put("routeRules", """
|
||||||
|
{
|
||||||
|
"categories": "topics",
|
||||||
|
"archives": "archives-new",
|
||||||
|
"post": "/archives-new/{slug}",
|
||||||
|
"tags": null
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
configMap.getData().put("seo", """
|
||||||
|
{
|
||||||
|
"blockSpiders": "true"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
|
||||||
|
// deleted post group here
|
||||||
|
configMap.getData().put("post", null);
|
||||||
|
|
||||||
|
configMap.getData().put("fakeArray", """
|
||||||
|
{
|
||||||
|
"select": [{
|
||||||
|
"label": "Hello",
|
||||||
|
"value": "hello"
|
||||||
|
}, {
|
||||||
|
"label": "Awesome",
|
||||||
|
"value": "awesome"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
return configMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,18 @@
|
||||||
package run.halo.app.theme.router;
|
package run.halo.app.theme.router;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import run.halo.app.extension.ConfigMap;
|
import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
import run.halo.app.theme.DefaultTemplateEnum;
|
import run.halo.app.theme.DefaultTemplateEnum;
|
||||||
|
@ -28,18 +27,14 @@ import run.halo.app.theme.DefaultTemplateEnum;
|
||||||
class PermalinkPatternProviderTest {
|
class PermalinkPatternProviderTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ExtensionClient client;
|
private SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
private PermalinkPatternProvider permalinkPatternProvider;
|
private PermalinkPatternProvider permalinkPatternProvider;
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
permalinkPatternProvider = new PermalinkPatternProvider(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getPatternThenDefault() {
|
void getPatternThenDefault() {
|
||||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
when(environmentFetcher.getConfigMapBlocking())
|
||||||
.thenReturn(Optional.empty());
|
.thenReturn(Optional.empty());
|
||||||
|
|
||||||
String pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST);
|
String pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST);
|
||||||
|
@ -79,7 +74,7 @@ class PermalinkPatternProviderTest {
|
||||||
|
|
||||||
configMap.setData(Map.of("routeRules", JsonUtils.objectToJson(themeRouteRules)));
|
configMap.setData(Map.of("routeRules", JsonUtils.objectToJson(themeRouteRules)));
|
||||||
|
|
||||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
when(environmentFetcher.getConfigMapBlocking())
|
||||||
.thenReturn(Optional.of(configMap));
|
.thenReturn(Optional.of(configMap));
|
||||||
|
|
||||||
String pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST);
|
String pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST);
|
||||||
|
|
Loading…
Reference in New Issue