Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add annotation setting extension #3028

Merged
merged 8 commits into from
Dec 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/main/java/run/halo/app/core/extension/AnnotationSetting.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 2 additions & 0 deletions src/main/java/run/halo/app/core/extension/Theme.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

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;
Expand All @@ -30,6 +36,7 @@
*/
@Component
public class ThemeReconciler implements Reconciler<Request> {
private static final String FINALIZER_NAME = "theme-protection";

private final ExtensionClient client;
private final ThemePathPolicy themePathPolicy;
Expand All @@ -44,8 +51,10 @@ public Result reconcile(Request 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());
});
Expand Down Expand Up @@ -114,6 +123,33 @@ private void themeSettingDefaultConfig(Theme theme) {
});
}

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
Expand All @@ -122,6 +158,19 @@ private void reconcileThemeDeletion(Theme theme) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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;
Expand All @@ -30,9 +31,11 @@
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;
Expand Down Expand Up @@ -149,21 +152,26 @@ public Mono<Theme> persistent(Unstructured themeManifest) {
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));
});
}
Expand All @@ -178,7 +186,14 @@ public Mono<Theme> reloadTheme(String name) {
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);
Expand All @@ -199,15 +214,26 @@ public Mono<Theme> reloadTheme(String name) {
}))
.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));
guqing marked this conversation as resolved.
Show resolved Hide resolved
});
}

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)
Expand Down Expand Up @@ -245,6 +271,25 @@ private Mono<Void> waitForSettingDeleted(String settingName) {
.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());
}
Expand Down
41 changes: 26 additions & 15 deletions src/main/java/run/halo/app/core/extension/theme/ThemeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
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;
Expand All @@ -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()));
Expand All @@ -64,10 +56,9 @@ private static Flux<Path> walkThemesFromPath(Path path) {
.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));
Expand All @@ -81,8 +72,28 @@ private static List<Unstructured> loadUnstructured(Path themePath,
}

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)) {
guqing marked this conversation as resolved.
Show resolved Hide resolved
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) {
Expand Down
Loading