mirror of https://github.com/halo-dev/halo
feat: add annotation setting extension (#3028)
#### What type of PR is this? /kind feature /milestone 2.1.x /area core #### What this PR does / why we need it: 新增 AnnotationSetting 自定义模型以扩展自定义元数据设置表单 主题安装/更新/重载时都会重新加载与 theme.yaml 同层级的其他 yaml,但只会保存 kind 为 Setting 和 AnnotationSetting的,主题卸载时会删除这些 yaml 资源 #### Which issue(s) this PR fixes: Fixes #3005 #### Special notes for your reviewer: how to test it? - 修改影响到了主题安装、更新、重载和删除,需要检查这些功能是否正确加载了 Setting 和 AnnotationSetting - 插件启动时初始化的 AnnotationSetting 在插件停止时会被删除 - 主题添加了 annotation setting 资源,使用非超级管理员也可以获取 /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note 新增 AnnotationSetting 以扩展自定义元数据设置表单 ```pull/3042/head^2
parent
9b9a57b427
commit
ddf47f6600
|
@ -0,0 +1,36 @@
|
|||
package run.halo.app.core.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static run.halo.app.core.extension.AnnotationSetting.KIND;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.GVK;
|
||||
import run.halo.app.extension.GroupKind;
|
||||
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@GVK(group = "", version = "v1alpha1", kind = KIND,
|
||||
plural = "annotationsettings", singular = "annotationsetting")
|
||||
public class AnnotationSetting extends AbstractExtension {
|
||||
public static final String TARGET_REF_LABEL = "halo.run/target-ref";
|
||||
|
||||
public static final String KIND = "AnnotationSetting";
|
||||
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private AnnotationSettingSpec spec;
|
||||
|
||||
@Data
|
||||
public static class AnnotationSettingSpec {
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private GroupKind targetRef;
|
||||
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private List<Object> formSchema;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,8 @@ public class Theme extends AbstractExtension {
|
|||
|
||||
public static final String KIND = "Theme";
|
||||
|
||||
public static final String THEME_NAME_LABEL = "theme.halo.run/theme-name";
|
||||
|
||||
@Schema(required = true)
|
||||
private ThemeSpec spec;
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thymeleaf.util.StringUtils;
|
||||
import run.halo.app.core.extension.AnnotationSetting;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.GroupKind;
|
||||
import run.halo.app.extension.controller.Controller;
|
||||
import run.halo.app.extension.controller.ControllerBuilder;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
|
||||
/**
|
||||
* Reconciler for {@link AnnotationSetting}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class AnnotationSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
@Override
|
||||
public Result reconcile(Request request) {
|
||||
populateDefaultLabels(request.name());
|
||||
return new Result(false, null);
|
||||
}
|
||||
|
||||
private void populateDefaultLabels(String name) {
|
||||
client.fetch(AnnotationSetting.class, name).ifPresent(annotationSetting -> {
|
||||
Map<String, String> labels = ExtensionUtil.nullSafeLabels(annotationSetting);
|
||||
String oldTargetRef = labels.get(AnnotationSetting.TARGET_REF_LABEL);
|
||||
|
||||
GroupKind targetRef = annotationSetting.getSpec().getTargetRef();
|
||||
String targetRefLabel = targetRef.group() + "/" + targetRef.kind();
|
||||
labels.put(AnnotationSetting.TARGET_REF_LABEL, targetRefLabel);
|
||||
|
||||
if (!StringUtils.equals(oldTargetRef, targetRefLabel)) {
|
||||
client.update(annotationSetting);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Controller setupWith(ControllerBuilder builder) {
|
||||
return builder
|
||||
.extension(new AnnotationSetting())
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -2,16 +2,22 @@ package run.halo.app.core.extension.reconciler;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
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.AnnotationSetting;
|
||||
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.ExtensionUtil;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.controller.Controller;
|
||||
import run.halo.app.extension.controller.ControllerBuilder;
|
||||
|
@ -30,6 +36,7 @@ import run.halo.app.theme.ThemePathPolicy;
|
|||
*/
|
||||
@Component
|
||||
public class ThemeReconciler implements Reconciler<Request> {
|
||||
private static final String FINALIZER_NAME = "theme-protection";
|
||||
|
||||
private final ExtensionClient client;
|
||||
private final ThemePathPolicy themePathPolicy;
|
||||
|
@ -44,8 +51,10 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
client.fetch(Theme.class, request.name())
|
||||
.ifPresent(theme -> {
|
||||
if (isDeleted(theme)) {
|
||||
reconcileThemeDeletion(theme);
|
||||
cleanUpResourcesAndRemoveFinalizer(request.name());
|
||||
return;
|
||||
}
|
||||
addFinalizerIfNecessary(theme);
|
||||
themeSettingDefaultConfig(theme);
|
||||
reconcileStatus(request.name());
|
||||
});
|
||||
|
@ -114,6 +123,33 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
});
|
||||
}
|
||||
|
||||
private void addFinalizerIfNecessary(Theme oldTheme) {
|
||||
Set<String> finalizers = oldTheme.getMetadata().getFinalizers();
|
||||
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||
return;
|
||||
}
|
||||
client.fetch(Theme.class, oldTheme.getMetadata().getName())
|
||||
.ifPresent(theme -> {
|
||||
Set<String> newFinalizers = theme.getMetadata().getFinalizers();
|
||||
if (newFinalizers == null) {
|
||||
newFinalizers = new HashSet<>();
|
||||
theme.getMetadata().setFinalizers(newFinalizers);
|
||||
}
|
||||
newFinalizers.add(FINALIZER_NAME);
|
||||
client.update(theme);
|
||||
});
|
||||
}
|
||||
|
||||
private void cleanUpResourcesAndRemoveFinalizer(String themeName) {
|
||||
client.fetch(Theme.class, themeName).ifPresent(theme -> {
|
||||
reconcileThemeDeletion(theme);
|
||||
if (theme.getMetadata().getFinalizers() != null) {
|
||||
theme.getMetadata().getFinalizers().remove(FINALIZER_NAME);
|
||||
}
|
||||
client.update(theme);
|
||||
});
|
||||
}
|
||||
|
||||
private void reconcileThemeDeletion(Theme theme) {
|
||||
deleteThemeFiles(theme);
|
||||
// delete theme setting form
|
||||
|
@ -122,6 +158,19 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
client.fetch(Setting.class, settingName)
|
||||
.ifPresent(client::delete);
|
||||
}
|
||||
// delete annotation setting
|
||||
deleteAnnotationSettings(theme.getMetadata().getName());
|
||||
}
|
||||
|
||||
private void deleteAnnotationSettings(String themeName) {
|
||||
List<AnnotationSetting> result = client.list(AnnotationSetting.class, annotationSetting -> {
|
||||
Map<String, String> labels = ExtensionUtil.nullSafeLabels(annotationSetting);
|
||||
return themeName.equals(labels.get(Theme.THEME_NAME_LABEL));
|
||||
}, null);
|
||||
|
||||
for (AnnotationSetting annotationSetting : result) {
|
||||
client.delete(annotationSetting);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteThemeFiles(Theme theme) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
@ -30,9 +31,11 @@ import reactor.core.publisher.Flux;
|
|||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import reactor.util.retry.Retry;
|
||||
import run.halo.app.core.extension.AnnotationSetting;
|
||||
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.ExtensionUtil;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
import run.halo.app.infra.ThemeRootGetter;
|
||||
|
@ -149,21 +152,26 @@ public class ThemeServiceImpl implements ThemeService {
|
|||
return Mono.error(new IllegalStateException(
|
||||
"Theme must only have one config.yaml or config.yml."));
|
||||
}
|
||||
var spec = theme.getSpec();
|
||||
return Flux.fromIterable(unstructureds)
|
||||
.flatMap(unstructured -> {
|
||||
var spec = theme.getSpec();
|
||||
.filter(unstructured -> {
|
||||
String name = unstructured.getMetadata().getName();
|
||||
|
||||
boolean isThemeSetting = unstructured.getKind().equals(Setting.KIND)
|
||||
&& StringUtils.equals(spec.getSettingName(), name);
|
||||
|
||||
boolean isThemeConfig = unstructured.getKind().equals(ConfigMap.KIND)
|
||||
&& StringUtils.equals(spec.getConfigMapName(), name);
|
||||
if (isThemeSetting || isThemeConfig) {
|
||||
return client.create(unstructured);
|
||||
}
|
||||
return Mono.empty();
|
||||
|
||||
boolean isAnnotationSetting = unstructured.getKind()
|
||||
.equals(AnnotationSetting.KIND);
|
||||
return isThemeSetting || isThemeConfig || isAnnotationSetting;
|
||||
})
|
||||
.doOnNext(unstructured ->
|
||||
populateThemeNameLabel(unstructured, theme.getMetadata().getName()))
|
||||
.flatMap(unstructured -> client.create(unstructured)
|
||||
.retryWhen(Retry.backoff(5, Duration.ofMillis(100))
|
||||
.filter(OptimisticLockingFailureException.class::isInstance))
|
||||
)
|
||||
.then(Mono.just(theme));
|
||||
});
|
||||
}
|
||||
|
@ -178,7 +186,14 @@ public class ThemeServiceImpl implements ThemeService {
|
|||
log.error("Failed to delete setting: {}", settingName,
|
||||
ExceptionUtils.getRootCause(error));
|
||||
throw new AsyncRequestTimeoutException("Reload theme timeout.");
|
||||
});
|
||||
})
|
||||
.then(waitForAnnotationSettingsDeleted(name)
|
||||
.doOnError(error -> {
|
||||
log.error("Failed to delete AnnotationSetting by theme [{}]", name,
|
||||
ExceptionUtils.getRootCause(error));
|
||||
throw new AsyncRequestTimeoutException("Reload theme timeout.");
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(Mono.defer(() -> {
|
||||
Path themePath = themeRoot.get().resolve(name);
|
||||
|
@ -199,15 +214,26 @@ public class ThemeServiceImpl implements ThemeService {
|
|||
}))
|
||||
.flatMap(theme -> {
|
||||
String settingName = theme.getSpec().getSettingName();
|
||||
return Flux.fromIterable(ThemeUtils.loadThemeSetting(getThemePath(theme)))
|
||||
.map(setting -> Unstructured.OBJECT_MAPPER.convertValue(setting, Setting.class))
|
||||
.filter(setting -> setting.getMetadata().getName().equals(settingName))
|
||||
.next()
|
||||
return Flux.fromIterable(ThemeUtils.loadThemeResources(getThemePath(theme)))
|
||||
.filter(unstructured -> (Setting.KIND.equals(unstructured.getKind())
|
||||
&& unstructured.getMetadata().getName().equals(settingName))
|
||||
|| AnnotationSetting.KIND.equals(unstructured.getKind())
|
||||
)
|
||||
.doOnNext(unstructured -> populateThemeNameLabel(unstructured, name))
|
||||
.flatMap(client::create)
|
||||
.thenReturn(theme);
|
||||
.then(Mono.just(theme));
|
||||
});
|
||||
}
|
||||
|
||||
private static void populateThemeNameLabel(Unstructured unstructured, String themeName) {
|
||||
Map<String, String> labels = unstructured.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
labels = new HashMap<>();
|
||||
unstructured.getMetadata().setLabels(labels);
|
||||
}
|
||||
labels.put(Theme.THEME_NAME_LABEL, themeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ConfigMap> resetSettingConfig(String name) {
|
||||
return client.fetch(Theme.class, name)
|
||||
|
@ -245,6 +271,25 @@ public class ThemeServiceImpl implements ThemeService {
|
|||
.then();
|
||||
}
|
||||
|
||||
private Mono<Void> waitForAnnotationSettingsDeleted(String themeName) {
|
||||
return client.list(AnnotationSetting.class,
|
||||
annotationSetting -> {
|
||||
Map<String, String> labels = ExtensionUtil.nullSafeLabels(annotationSetting);
|
||||
return StringUtils.equals(themeName, labels.get(Theme.THEME_NAME_LABEL));
|
||||
}, null)
|
||||
.flatMap(annotationSetting -> client.delete(annotationSetting)
|
||||
.flatMap(deleted -> client.fetch(AnnotationSetting.class,
|
||||
annotationSetting.getMetadata().getName())
|
||||
.doOnNext(latest -> {
|
||||
throw new RetryException("AnnotationSetting is not deleted yet.");
|
||||
})
|
||||
.retryWhen(Retry.fixedDelay(10, Duration.ofMillis(100))
|
||||
.filter(t -> t instanceof RetryException))
|
||||
)
|
||||
)
|
||||
.then();
|
||||
}
|
||||
|
||||
private Path getThemePath(Theme theme) {
|
||||
return themeRoot.get().resolve(theme.getMetadata().getName());
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import java.util.stream.BaseStream;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
@ -38,19 +37,12 @@ class ThemeUtils {
|
|||
private static final String THEME_TMP_PREFIX = "halo-theme-";
|
||||
private static final String[] THEME_MANIFESTS = {"theme.yaml", "theme.yml"};
|
||||
|
||||
private static final String[] THEME_CONFIG = {"config.yaml", "config.yml"};
|
||||
|
||||
private static final String[] THEME_SETTING = {"settings.yaml", "settings.yml"};
|
||||
|
||||
static List<Unstructured> loadThemeSetting(Path themePath) {
|
||||
return loadUnstructured(themePath, THEME_SETTING);
|
||||
}
|
||||
|
||||
static Flux<Theme> listAllThemesFromThemeDir(Path themesDir) {
|
||||
return walkThemesFromPath(themesDir)
|
||||
.filter(Files::isDirectory)
|
||||
.map(themePath -> loadUnstructured(themePath, THEME_MANIFESTS))
|
||||
.map(ThemeUtils::findThemeManifest)
|
||||
.flatMap(Flux::fromIterable)
|
||||
.filter(unstructured -> unstructured.getKind().equals(Theme.KIND))
|
||||
.map(unstructured -> Unstructured.OBJECT_MAPPER.convertValue(unstructured,
|
||||
Theme.class))
|
||||
.sort(Comparator.comparing(theme -> theme.getMetadata().getName()));
|
||||
|
@ -64,10 +56,9 @@ class ThemeUtils {
|
|||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
private static List<Unstructured> loadUnstructured(Path themePath,
|
||||
String[] themeSetting) {
|
||||
private static List<Unstructured> findThemeManifest(Path themePath) {
|
||||
List<Resource> resources = new ArrayList<>(4);
|
||||
for (String themeResource : themeSetting) {
|
||||
for (String themeResource : THEME_MANIFESTS) {
|
||||
Path resourcePath = themePath.resolve(themeResource);
|
||||
if (Files.exists(resourcePath)) {
|
||||
resources.add(new FileSystemResource(resourcePath));
|
||||
|
@ -81,8 +72,28 @@ class ThemeUtils {
|
|||
}
|
||||
|
||||
static List<Unstructured> loadThemeResources(Path themePath) {
|
||||
String[] resourceNames = ArrayUtils.addAll(THEME_SETTING, THEME_CONFIG);
|
||||
return loadUnstructured(themePath, resourceNames);
|
||||
try (Stream<Path> paths = Files.list(themePath)) {
|
||||
List<FileSystemResource> resources = paths
|
||||
.filter(path -> {
|
||||
String pathString = path.toString();
|
||||
return pathString.endsWith(".yaml") || pathString.endsWith(".yml");
|
||||
})
|
||||
.filter(path -> {
|
||||
String pathString = path.toString();
|
||||
for (String themeManifest : THEME_MANIFESTS) {
|
||||
if (pathString.endsWith(themeManifest)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(FileSystemResource::new)
|
||||
.toList();
|
||||
return new YamlUnstructuredLoader(resources.toArray(new Resource[0]))
|
||||
.load();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Mono<Unstructured> unzipThemeTo(InputStream inputStream, Path themeWorkDir) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.springframework.context.ApplicationEventPublisher;
|
|||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.core.extension.AnnotationSetting;
|
||||
import run.halo.app.core.extension.Counter;
|
||||
import run.halo.app.core.extension.Menu;
|
||||
import run.halo.app.core.extension.MenuItem;
|
||||
|
@ -57,6 +58,7 @@ public class SchemeInitializer implements ApplicationListener<ApplicationStarted
|
|||
schemeManager.register(User.class);
|
||||
schemeManager.register(ReverseProxy.class);
|
||||
schemeManager.register(Setting.class);
|
||||
schemeManager.register(AnnotationSetting.class);
|
||||
schemeManager.register(ConfigMap.class);
|
||||
schemeManager.register(Theme.class);
|
||||
schemeManager.register(Menu.class);
|
||||
|
|
|
@ -7,7 +7,13 @@ metadata:
|
|||
halo.run/hidden: "true"
|
||||
annotations:
|
||||
rbac.authorization.halo.run/dependencies: |
|
||||
[ "role-template-own-user-info", "role-template-own-permissions", "role-template-change-own-password" ]
|
||||
[
|
||||
"role-template-own-user-info",
|
||||
"role-template-own-permissions",
|
||||
"role-template-change-own-password",
|
||||
"role-template-stats",
|
||||
"role-template-annotation-setting"
|
||||
]
|
||||
rules:
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "configmaps" ]
|
||||
|
@ -63,4 +69,17 @@ metadata:
|
|||
rules:
|
||||
- apiGroups: [ "api.console.halo.run" ]
|
||||
resources: [ "stats" ]
|
||||
verbs: [ "get", "list" ]
|
||||
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: role-template-annotation-setting
|
||||
labels:
|
||||
halo.run/role-template: "true"
|
||||
halo.run/hidden: "true"
|
||||
rules:
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "annotationsettings" ]
|
||||
verbs: [ "get", "list" ]
|
|
@ -26,6 +26,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import run.halo.app.core.extension.AnnotationSetting;
|
||||
import run.halo.app.core.extension.Setting;
|
||||
import run.halo.app.core.extension.Theme;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
|
@ -103,8 +104,10 @@ class ThemeReconcilerTest {
|
|||
|
||||
themeReconciler.reconcile(new Reconciler.Request(metadata.getName()));
|
||||
|
||||
verify(extensionClient, times(3)).fetch(eq(Theme.class), eq(metadata.getName()));
|
||||
verify(extensionClient, times(2)).fetch(eq(Setting.class), eq(themeSpec.getSettingName()));
|
||||
verify(extensionClient, times(2)).fetch(eq(Theme.class), eq(metadata.getName()));
|
||||
verify(extensionClient, times(1)).fetch(eq(Setting.class), eq(themeSpec.getSettingName()));
|
||||
|
||||
verify(extensionClient, times(1)).list(eq(AnnotationSetting.class), any(), any());
|
||||
|
||||
assertThat(Files.exists(testWorkDir)).isTrue();
|
||||
assertThat(Files.exists(defaultThemePath)).isFalse();
|
||||
|
@ -134,7 +137,7 @@ class ThemeReconcilerTest {
|
|||
Reconciler.Result reconcile =
|
||||
themeReconciler.reconcile(new Reconciler.Request(metadata.getName()));
|
||||
assertThat(reconcile.reEnqueue()).isFalse();
|
||||
verify(extensionClient, times(2)).fetch(eq(Theme.class), eq(metadata.getName()));
|
||||
verify(extensionClient, times(3)).fetch(eq(Theme.class), eq(metadata.getName()));
|
||||
|
||||
// setting exists
|
||||
themeSpec.setSettingName("theme-test-setting");
|
||||
|
@ -143,9 +146,9 @@ class ThemeReconcilerTest {
|
|||
assertThat(theme.getSpec().getConfigMapName()).isNull();
|
||||
ArgumentCaptor<Theme> captor = ArgumentCaptor.forClass(Theme.class);
|
||||
themeReconciler.reconcile(new Reconciler.Request(metadata.getName()));
|
||||
verify(extensionClient, times(5))
|
||||
verify(extensionClient, times(6))
|
||||
.fetch(eq(Theme.class), eq(metadata.getName()));
|
||||
verify(extensionClient, times(2))
|
||||
verify(extensionClient, times(3))
|
||||
.update(captor.capture());
|
||||
Theme value = captor.getValue();
|
||||
assertThat(value.getSpec().getConfigMapName()).isNotNull();
|
||||
|
|
|
@ -33,8 +33,10 @@ import org.mockito.stubbing.Answer;
|
|||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.AnnotationSetting;
|
||||
import run.halo.app.core.extension.Setting;
|
||||
import run.halo.app.core.extension.Theme;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
|
@ -240,6 +242,8 @@ class ThemeServiceImplTest {
|
|||
return Mono.just(argument);
|
||||
});
|
||||
|
||||
when(client.list(eq(AnnotationSetting.class), any(), eq(null))).thenReturn(Flux.empty());
|
||||
|
||||
themeService.reloadTheme("fake-theme")
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(themeUpdated -> {
|
||||
|
@ -320,9 +324,9 @@ class ThemeServiceImplTest {
|
|||
return Mono.just(argument);
|
||||
});
|
||||
|
||||
when(client.create(any(Setting.class)))
|
||||
.thenAnswer((Answer<Mono<Setting>>) invocation -> {
|
||||
Setting argument = invocation.getArgument(0);
|
||||
when(client.create(any(Unstructured.class)))
|
||||
.thenAnswer((Answer<Mono<Unstructured>>) invocation -> {
|
||||
Unstructured argument = invocation.getArgument(0);
|
||||
JSONAssert.assertEquals("""
|
||||
{
|
||||
"spec": {
|
||||
|
@ -342,7 +346,10 @@ class ThemeServiceImplTest {
|
|||
"apiVersion": "v1alpha1",
|
||||
"kind": "Setting",
|
||||
"metadata": {
|
||||
"name": "fake-setting"
|
||||
"name": "fake-setting",
|
||||
"labels": {
|
||||
"theme.halo.run/theme-name": "fake-theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
@ -351,6 +358,8 @@ class ThemeServiceImplTest {
|
|||
return Mono.just(invocation.getArgument(0));
|
||||
});
|
||||
|
||||
when(client.list(eq(AnnotationSetting.class), any(), eq(null))).thenReturn(Flux.empty());
|
||||
|
||||
themeService.reloadTheme("fake-theme")
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(themeUpdated -> {
|
||||
|
|
Loading…
Reference in New Issue