Skip to content

Commit

Permalink
feat: add reset config API for theme and plugin (#2964)
Browse files Browse the repository at this point in the history
#### What type of PR is this?
/kind feature
/kind api-change
/area core

#### What this PR does / why we need it:
为主题和插件提供重置设置项 API

此 PR 会重新读取配置对应的 Setting 资源,从其中读取默认值后更新到现有的 ConfigMap 中替换其 data
see #2789 for more details
#### Which issue(s) this PR fixes:

Fixes #2789

#### Special notes for your reviewer:
how to test it?
1. 在主题设置或插件设置配置一些设置项后保存
2. 执行重置配置
3. 配置恢复为了 Setting 中指定的默认值

/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

```release-note
为主题和插件提供重置设置项 API
```
  • Loading branch information
guqing authored Dec 19, 2022
1 parent efc940d commit 27775c9
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
Expand All @@ -49,7 +51,10 @@
import reactor.core.scheduler.Schedulers;
import reactor.util.retry.Retry;
import run.halo.app.core.extension.Plugin;
import run.halo.app.core.extension.Setting;
import run.halo.app.core.extension.theme.SettingUtils;
import run.halo.app.extension.Comparators;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.router.IListRequest.QueryListRequest;
import run.halo.app.infra.utils.FileUtils;
Expand Down Expand Up @@ -95,6 +100,19 @@ public RouterFunction<ServerResponse> endpoint() {
.content(contentBuilder().mediaType(MediaType.MULTIPART_FORM_DATA_VALUE)
.schema(schemaBuilder().implementation(InstallRequest.class))))
)
.PUT("plugins/{name}/reset-config", this::resetSettingConfig,
builder -> builder.operationId("ResetPluginConfig")
.description("Reset the configMap of plugin setting.")
.tag(tag)
.parameter(parameterBuilder()
.name("name")
.in(ParameterIn.PATH)
.required(true)
.implementation(String.class)
)
.response(responseBuilder()
.implementation(ConfigMap.class))
)
.GET("plugins", this::list, builder -> {
builder.operationId("ListPlugins")
.tag(tag)
Expand All @@ -105,6 +123,32 @@ public RouterFunction<ServerResponse> endpoint() {
.build();
}

private Mono<ServerResponse> resetSettingConfig(ServerRequest request) {
String name = request.pathVariable("name");
return client.fetch(Plugin.class, name)
.filter(plugin -> StringUtils.hasText(plugin.getSpec().getSettingName()))
.flatMap(plugin -> {
String configMapName = plugin.getSpec().getConfigMapName();
String settingName = plugin.getSpec().getSettingName();
return client.fetch(Setting.class, settingName)
.map(SettingUtils::settingDefinedDefaultValueMap)
.flatMap(data -> updateConfigMapData(configMapName, data));
})
.flatMap(configMap -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(configMap));
}

private Mono<ConfigMap> updateConfigMapData(String configMapName, Map<String, String> data) {
return client.fetch(ConfigMap.class, configMapName)
.flatMap(configMap -> {
configMap.setData(data);
return client.update(configMap);
})
.retryWhen(Retry.fixedDelay(10, Duration.ofMillis(100))
.filter(t -> t instanceof OptimisticLockingFailureException));
}

private Mono<ServerResponse> upgrade(ServerRequest request) {
var pluginNameInPath = request.pathVariable("name");
var tempDirRef = new AtomicReference<Path>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
package run.halo.app.core.extension.reconciler;

import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.FileSystemUtils;
import run.halo.app.core.extension.Setting;
import run.halo.app.core.extension.Theme;
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;
Expand Down Expand Up @@ -106,7 +102,7 @@ private void themeSettingDefaultConfig(Theme theme) {

client.fetch(Setting.class, theme.getSpec().getSettingName())
.ifPresent(setting -> {
Map<String, String> data = settingDefinedDefaultValueMap(setting);
var data = SettingUtils.settingDefinedDefaultValueMap(setting);
if (CollectionUtils.isEmpty(data)) {
return;
}
Expand All @@ -118,31 +114,6 @@ private void themeSettingDefaultConfig(Theme theme) {
});
}

Map<String, String> settingDefinedDefaultValueMap(Setting setting) {
final String defaultValueField = "value";
final String nameField = "name";
List<Setting.SettingForm> forms = setting.getSpec().getForms();
if (CollectionUtils.isEmpty(forms)) {
return null;
}
Map<String, String> data = new LinkedHashMap<>();
for (Setting.SettingForm form : forms) {
String group = form.getGroup();
Map<String, JsonNode> groupValue = form.getFormSchema().stream()
.map(o -> JsonUtils.DEFAULT_JSON_MAPPER.convertValue(o, JsonNode.class))
.filter(jsonNode -> jsonNode.isObject() && jsonNode.has(nameField)
&& jsonNode.has(defaultValueField))
.map(jsonNode -> {
String name = jsonNode.findValue(nameField).asText();
JsonNode value = jsonNode.findValue(defaultValueField);
return Map.entry(name, value);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
data.put(group, JsonUtils.objectToJson(groupValue));
}
return data;
}

private void reconcileThemeDeletion(Theme theme) {
deleteThemeFiles(theme);
// delete theme setting form
Expand Down
48 changes: 48 additions & 0 deletions src/main/java/run/halo/app/core/extension/theme/SettingUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package run.halo.app.core.extension.theme;

import com.fasterxml.jackson.databind.JsonNode;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.experimental.UtilityClass;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
import run.halo.app.core.extension.Setting;
import run.halo.app.infra.utils.JsonUtils;

@UtilityClass
public class SettingUtils {
private static final String VALUE_FIELD = "value";
private static final String NAME_FIELD = "name";

/**
* Read setting default value from {@link Setting} forms.
*
* @param setting {@link Setting} extension
* @return a map of setting default value
*/
@NonNull
public static Map<String, String> settingDefinedDefaultValueMap(Setting setting) {
List<Setting.SettingForm> forms = setting.getSpec().getForms();
if (CollectionUtils.isEmpty(forms)) {
return Map.of();
}
Map<String, String> data = new LinkedHashMap<>();
for (Setting.SettingForm form : forms) {
String group = form.getGroup();
Map<String, JsonNode> groupValue = form.getFormSchema().stream()
.map(o -> JsonUtils.DEFAULT_JSON_MAPPER.convertValue(o, JsonNode.class))
.filter(jsonNode -> jsonNode.isObject() && jsonNode.has(NAME_FIELD)
&& jsonNode.has(VALUE_FIELD))
.map(jsonNode -> {
String name = jsonNode.get(NAME_FIELD).asText();
JsonNode value = jsonNode.get(VALUE_FIELD);
return Map.entry(name, value);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
data.put(group, JsonUtils.objectToJson(groupValue));
}
return data;
}
}
22 changes: 22 additions & 0 deletions src/main/java/run/halo/app/core/extension/theme/ThemeEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Theme;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ListResult;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.router.IListRequest;
Expand Down Expand Up @@ -105,6 +106,19 @@ public RouterFunction<ServerResponse> endpoint() {
.response(responseBuilder()
.implementation(Theme.class))
)
.PUT("themes/{name}/reset-config", this::resetSettingConfig,
builder -> builder.operationId("ResetThemeConfig")
.description("Reset the configMap of theme setting.")
.tag(tag)
.parameter(parameterBuilder()
.name("name")
.in(ParameterIn.PATH)
.required(true)
.implementation(String.class)
)
.response(responseBuilder()
.implementation(ConfigMap.class))
)
.GET("themes", this::listThemes,
builder -> {
builder.operationId("ListThemes")
Expand Down Expand Up @@ -222,6 +236,14 @@ Mono<ServerResponse> reloadTheme(ServerRequest request) {
.bodyValue(theme));
}

Mono<ServerResponse> resetSettingConfig(ServerRequest request) {
String name = request.pathVariable("name");
return themeService.resetSettingConfig(name)
.flatMap(theme -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(theme));
}

public record InstallRequest(
@Schema(required = true, description = "Theme zip file.") FilePart file) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.InputStream;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Theme;
import run.halo.app.extension.ConfigMap;

public interface ThemeService {

Expand All @@ -11,6 +12,8 @@ public interface ThemeService {
Mono<Theme> upgrade(String themeName, InputStream is);

Mono<Theme> reloadTheme(String name);

Mono<ConfigMap> resetSettingConfig(String name);
// TODO Migrate other useful methods in ThemeEndpoint in the future.

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
import java.io.InputStream;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.zip.ZipInputStream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.retry.RetryException;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -206,6 +208,29 @@ public Mono<Theme> reloadTheme(String name) {
});
}

@Override
public Mono<ConfigMap> resetSettingConfig(String name) {
return client.fetch(Theme.class, name)
.filter(theme -> StringUtils.isNotBlank(theme.getSpec().getSettingName()))
.flatMap(theme -> {
String configMapName = theme.getSpec().getConfigMapName();
String settingName = theme.getSpec().getSettingName();
return client.fetch(Setting.class, settingName)
.map(SettingUtils::settingDefinedDefaultValueMap)
.flatMap(data -> updateConfigMapData(configMapName, data));
});
}

private Mono<ConfigMap> updateConfigMapData(String configMapName, Map<String, String> data) {
return client.fetch(ConfigMap.class, configMapName)
.flatMap(configMap -> {
configMap.setData(data);
return client.update(configMap);
})
.retryWhen(Retry.fixedDelay(10, Duration.ofMillis(100))
.filter(t -> t instanceof OptimisticLockingFailureException));
}

private Mono<Void> waitForSettingDeleted(String settingName) {
return client.fetch(Setting.class, settingName)
.flatMap(setting -> client.delete(setting)
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/extensions/role-template-plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ rules:
- apiGroups: [ "plugin.halo.run" ]
resources: [ "plugins" ]
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
- apiGroups: [ "api.console.halo.run" ]
resources: [ "plugins/upgrade", "plugins/resetconfig" ]
verbs: [ "*" ]
- nonResourceURLs: [ "/apis/api.console.halo.run/v1alpha1/plugins/*" ]
verbs: [ "create" ]
---
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/extensions/role-template-theme.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ rules:
resources: [ "themes" ]
verbs: [ "*" ]
- apiGroups: [ "api.console.halo.run" ]
resources: [ "themes", "themes/reload" ]
resources: [ "themes", "themes/reload", "themes/resetconfig" ]
verbs: [ "*" ]
- nonResourceURLs: [ "/apis/api.console.halo.run/themes/install" ]
verbs: [ "create" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,21 +174,6 @@ void themeSettingDefaultValue() throws IOException, JSONException {
true);
}

@Test
void settingDefinedDefaultValueMap() throws JSONException {
Setting setting = getFakeSetting();
when(haloProperties.getWorkDir()).thenReturn(tempDirectory);
Map<String, String> map = new ThemeReconciler(extensionClient, haloProperties)
.settingDefinedDefaultValueMap(setting);
JSONAssert.assertEquals("""
{
"sns": "{\\"email\\":\\"example@exmple.com\\"}"
}
""",
JsonUtils.objectToJson(map),
true);
}

private static Setting getFakeSetting() {
String settingJson = """
{
Expand Down
Loading

0 comments on commit 27775c9

Please sign in to comment.