mirror of https://github.com/halo-dev/halo
feat: add reset config API for theme and plugin (#2964)
#### 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 ```pull/3003/head
parent
efc940df99
commit
27775c9ac9
|
@ -25,11 +25,13 @@ import java.time.Duration;
|
|||
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;
|
||||
|
@ -49,7 +51,10 @@ import reactor.core.publisher.Mono;
|
|||
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;
|
||||
|
@ -95,6 +100,19 @@ public class PluginEndpoint implements CustomEndpoint {
|
|||
.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)
|
||||
|
@ -105,6 +123,32 @@ public class PluginEndpoint implements CustomEndpoint {
|
|||
.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>();
|
||||
|
|
|
@ -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;
|
||||
|
@ -106,7 +102,7 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
|
||||
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;
|
||||
}
|
||||
|
@ -118,31 +114,6 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
});
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ import reactor.core.Exceptions;
|
|||
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;
|
||||
|
@ -105,6 +106,19 @@ public class ThemeEndpoint implements CustomEndpoint {
|
|||
.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")
|
||||
|
@ -222,6 +236,14 @@ public class ThemeEndpoint implements CustomEndpoint {
|
|||
.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) {
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.core.extension.theme;
|
|||
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 {
|
||||
|
||||
|
@ -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.
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.io.IOException;
|
|||
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;
|
||||
|
@ -18,6 +19,7 @@ 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;
|
||||
|
@ -206,6 +208,29 @@ public class ThemeServiceImpl implements ThemeService {
|
|||
});
|
||||
}
|
||||
|
||||
@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)
|
||||
|
|
|
@ -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" ]
|
||||
---
|
||||
|
|
|
@ -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" ]
|
||||
|
|
|
@ -174,21 +174,6 @@ class ThemeReconcilerTest {
|
|||
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 = """
|
||||
{
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package run.halo.app.core.extension.theme;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import run.halo.app.core.extension.Setting;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link SettingUtils}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.1
|
||||
*/
|
||||
class SettingUtilsTest {
|
||||
|
||||
@Test
|
||||
void settingDefinedDefaultValueMap() throws JSONException {
|
||||
Setting setting = getFakeSetting();
|
||||
var map = SettingUtils.settingDefinedDefaultValueMap(setting);
|
||||
JSONAssert.assertEquals("""
|
||||
{
|
||||
"sns": "{\\"email\\":\\"example@exmple.com\\"}"
|
||||
}
|
||||
""",
|
||||
JsonUtils.objectToJson(map),
|
||||
true);
|
||||
}
|
||||
|
||||
private static Setting getFakeSetting() {
|
||||
String settingJson = """
|
||||
{
|
||||
"apiVersion": "v1alpha1",
|
||||
"kind": "Setting",
|
||||
"metadata": {
|
||||
"name": "theme-default-setting"
|
||||
},
|
||||
"spec": {
|
||||
"forms": [{
|
||||
"formSchema": [
|
||||
{
|
||||
"$el": "h1",
|
||||
"children": "Register"
|
||||
},
|
||||
{
|
||||
"$formkit": "text",
|
||||
"label": "Email",
|
||||
"name": "email",
|
||||
"value": "example@exmple.com"
|
||||
},
|
||||
{
|
||||
"$formkit": "password",
|
||||
"label": "Password",
|
||||
"name": "password",
|
||||
"validation": "required|length:5,16",
|
||||
"value": null
|
||||
}
|
||||
],
|
||||
"group": "sns",
|
||||
"label": "社交资料"
|
||||
}]
|
||||
}
|
||||
}
|
||||
""";
|
||||
return JsonUtils.jsonToObject(settingJson, Setting.class);
|
||||
}
|
||||
}
|
|
@ -170,4 +170,13 @@ class ThemeEndpointTest {
|
|||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetSettingConfig() {
|
||||
when(themeService.resetSettingConfig(any())).thenReturn(Mono.empty());
|
||||
webTestClient.put()
|
||||
.uri("/themes/fake/reset-config")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package run.halo.app.core.extension.theme;
|
||||
|
||||
import static java.nio.file.Files.createTempDirectory;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
@ -17,6 +18,7 @@ import java.net.URISyntaxException;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -35,6 +37,7 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.Setting;
|
||||
import run.halo.app.core.extension.Theme;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
|
@ -375,4 +378,69 @@ class ThemeServiceImplTest {
|
|||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetSettingConfig() {
|
||||
Theme theme = new Theme();
|
||||
theme.setMetadata(new Metadata());
|
||||
theme.getMetadata().setName("fake-theme");
|
||||
theme.setSpec(new Theme.ThemeSpec());
|
||||
theme.getSpec().setSettingName("fake-setting");
|
||||
theme.getSpec().setConfigMapName("fake-config");
|
||||
theme.getSpec().setDisplayName("Hello");
|
||||
when(client.fetch(Theme.class, "fake-theme"))
|
||||
.thenReturn(Mono.just(theme));
|
||||
|
||||
Setting setting = new Setting();
|
||||
setting.setMetadata(new Metadata());
|
||||
setting.getMetadata().setName("fake-setting");
|
||||
setting.setSpec(new Setting.SettingSpec());
|
||||
var formSchemaItem = Map.of("name", "email", "value", "example@exmple.com");
|
||||
Setting.SettingForm settingForm = new Setting.SettingForm();
|
||||
settingForm.setGroup("basic");
|
||||
settingForm.setFormSchema(List.of(formSchemaItem));
|
||||
setting.getSpec().setForms(List.of(settingForm));
|
||||
when(client.fetch(eq(Setting.class), eq("fake-setting")))
|
||||
.thenReturn(Mono.just(setting));
|
||||
|
||||
ConfigMap configMap = new ConfigMap();
|
||||
configMap.setMetadata(new Metadata());
|
||||
configMap.getMetadata().setName("fake-config");
|
||||
when(client.fetch(eq(ConfigMap.class), eq("fake-config")))
|
||||
.thenReturn(Mono.just(configMap));
|
||||
|
||||
when(client.update(any(ConfigMap.class)))
|
||||
.thenAnswer((Answer<Mono<ConfigMap>>) invocation -> {
|
||||
ConfigMap argument = invocation.getArgument(0);
|
||||
JSONAssert.assertEquals("""
|
||||
{
|
||||
"data": {
|
||||
"basic": "{\\"email\\":\\"example@exmple.com\\"}"
|
||||
},
|
||||
"apiVersion": "v1alpha1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": {
|
||||
"name": "fake-config"
|
||||
}
|
||||
}
|
||||
""",
|
||||
JsonUtils.objectToJson(argument),
|
||||
true);
|
||||
return Mono.just(invocation.getArgument(0));
|
||||
});
|
||||
|
||||
themeService.resetSettingConfig("fake-theme")
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(next -> {
|
||||
assertThat(next).isNotNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(client, times(1))
|
||||
.fetch(eq(Setting.class), eq(setting.getMetadata().getName()));
|
||||
|
||||
verify(client, times(1)).fetch(eq(ConfigMap.class), eq("fake-config"));
|
||||
|
||||
verify(client, times(1)).update(any(ConfigMap.class));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue