Refactor reconcilers registration (#2737)

#### What type of PR is this?

/kind cleanup
/kind improvement
/area core
/milestone 2.0.0

#### What this PR does / why we need it:

This PR mainly refactors registration of reconcilers to register reconciler more convinient. After that, it is possible to reigster and start reconciler in plugin.

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/2305

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/2746/head^2
John Niang 2022-11-25 19:13:09 +08:00 committed by GitHub
parent 98db7c6aff
commit 0df7857ef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 337 additions and 302 deletions

View File

@ -1,65 +1,18 @@
package run.halo.app.config;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.content.ContentService;
import run.halo.app.content.PostService;
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
import run.halo.app.content.permalinks.PostPermalinkPolicy;
import run.halo.app.content.permalinks.TagPermalinkPolicy;
import run.halo.app.core.extension.Category;
import run.halo.app.core.extension.Comment;
import run.halo.app.core.extension.Menu;
import run.halo.app.core.extension.MenuItem;
import run.halo.app.core.extension.Plugin;
import run.halo.app.core.extension.Post;
import run.halo.app.core.extension.ReverseProxy;
import run.halo.app.core.extension.Role;
import run.halo.app.core.extension.RoleBinding;
import run.halo.app.core.extension.SinglePage;
import run.halo.app.core.extension.Tag;
import run.halo.app.core.extension.Theme;
import run.halo.app.core.extension.User;
import run.halo.app.core.extension.attachment.Attachment;
import run.halo.app.core.extension.reconciler.CategoryReconciler;
import run.halo.app.core.extension.reconciler.CommentReconciler;
import run.halo.app.core.extension.reconciler.MenuItemReconciler;
import run.halo.app.core.extension.reconciler.MenuReconciler;
import run.halo.app.core.extension.reconciler.PluginReconciler;
import run.halo.app.core.extension.reconciler.PostReconciler;
import run.halo.app.core.extension.reconciler.ReverseProxyReconciler;
import run.halo.app.core.extension.reconciler.RoleBindingReconciler;
import run.halo.app.core.extension.reconciler.RoleReconciler;
import run.halo.app.core.extension.reconciler.SinglePageReconciler;
import run.halo.app.core.extension.reconciler.SystemSettingReconciler;
import run.halo.app.core.extension.reconciler.TagReconciler;
import run.halo.app.core.extension.reconciler.ThemeReconciler;
import run.halo.app.core.extension.reconciler.UserReconciler;
import run.halo.app.core.extension.reconciler.attachment.AttachmentReconciler;
import run.halo.app.core.extension.service.RoleService;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.DefaultSchemeManager;
import run.halo.app.extension.DefaultSchemeWatcherManager;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.SchemeManager;
import run.halo.app.extension.SchemeWatcherManager;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.ControllerManager;
import run.halo.app.extension.controller.DefaultControllerManager;
import run.halo.app.extension.router.ExtensionCompositeRouterFunction;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.metrics.CounterService;
import run.halo.app.plugin.ExtensionComponentsFinder;
import run.halo.app.plugin.HaloPluginManager;
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
@Configuration(proxyBeanMethods = false)
public class ExtensionConfiguration {
@ -87,150 +40,10 @@ public class ExtensionConfiguration {
static class ExtensionControllerConfiguration {
@Bean
ControllerManager controllerManager() {
return new ControllerManager();
}
@Bean
Controller userController(ExtensionClient client) {
return new ControllerBuilder("user", client)
.reconciler(new UserReconciler(client))
.extension(new User())
.build();
}
@Bean
Controller roleController(ExtensionClient client, RoleService roleService) {
return new ControllerBuilder("role", client)
.reconciler(new RoleReconciler(client, roleService))
.extension(new Role())
.build();
}
@Bean
Controller roleBindingController(ExtensionClient client) {
return new ControllerBuilder("role-binding", client)
.reconciler(new RoleBindingReconciler(client))
.extension(new RoleBinding())
.build();
}
@Bean
Controller pluginController(ExtensionClient client, HaloPluginManager haloPluginManager) {
return new ControllerBuilder("plugin", client)
.reconciler(new PluginReconciler(client, haloPluginManager))
.extension(new Plugin())
.build();
}
@Bean
Controller menuController(ExtensionClient client) {
return new ControllerBuilder("menu", client)
.reconciler(new MenuReconciler(client))
.extension(new Menu())
.build();
}
@Bean
Controller menuItemController(ExtensionClient client) {
return new ControllerBuilder("menu-item", client)
.reconciler(new MenuItemReconciler(client))
.extension(new MenuItem())
.build();
}
@Bean
Controller themeController(ExtensionClient client, HaloProperties haloProperties) {
return new ControllerBuilder("theme", client)
.reconciler(new ThemeReconciler(client, haloProperties))
.extension(new Theme())
.build();
}
@Bean
Controller postController(ExtensionClient client, ContentService contentService,
PostPermalinkPolicy postPermalinkPolicy, CounterService counterService,
PostService postService, ApplicationContext applicationContext) {
return new ControllerBuilder("post", client)
.reconciler(new PostReconciler(client, contentService, postService,
postPermalinkPolicy,
counterService, applicationContext))
.extension(new Post())
// TODO Make it configurable
.workerCount(10)
.build();
}
@Bean
Controller categoryController(ExtensionClient client,
CategoryPermalinkPolicy categoryPermalinkPolicy) {
return new ControllerBuilder("category", client)
.reconciler(new CategoryReconciler(client, categoryPermalinkPolicy))
.extension(new Category())
.build();
}
@Bean
Controller tagController(ExtensionClient client, TagPermalinkPolicy tagPermalinkPolicy) {
return new ControllerBuilder("tag", client)
.reconciler(new TagReconciler(client, tagPermalinkPolicy))
.extension(new Tag())
.build();
}
@Bean
Controller systemSettingController(ExtensionClient client,
SystemConfigurableEnvironmentFetcher environmentFetcher,
ApplicationContext applicationContext) {
return new ControllerBuilder("system-setting", client)
.reconciler(new SystemSettingReconciler(client, environmentFetcher,
applicationContext))
.extension(new ConfigMap())
.build();
}
@Bean
Controller attachmentController(ExtensionClient client,
ExtensionComponentsFinder extensionComponentsFinder,
ExternalUrlSupplier externalUrl) {
return new ControllerBuilder("attachment", client)
.reconciler(
new AttachmentReconciler(client, extensionComponentsFinder, externalUrl))
.extension(new Attachment())
.build();
}
@Bean
Controller singlePageController(ExtensionClient client, ContentService contentService,
ApplicationContext applicationContext, CounterService counterService,
ExternalUrlSupplier externalUrlSupplier) {
return new ControllerBuilder("single-page", client)
.reconciler(new SinglePageReconciler(client, contentService,
applicationContext, counterService, externalUrlSupplier)
)
.extension(new SinglePage())
.build();
}
@Bean
Controller commentController(ExtensionClient client, MeterRegistry meterRegistry,
SchemeManager schemeManager) {
return new ControllerBuilder("comment", client)
// TODO Make it configurable
.workerCount(10)
.reconciler(new CommentReconciler(client, meterRegistry, schemeManager))
.extension(new Comment())
.build();
}
@Bean
Controller reverseProxyController(ExtensionClient client,
ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionRegistry) {
return new ControllerBuilder("reverse-proxy", client)
.reconciler(new ReverseProxyReconciler(client, reverseProxyRouterFunctionRegistry))
.extension(new ReverseProxy())
.build();
}
DefaultControllerManager controllerManager(ExtensionClient client) {
return new DefaultControllerManager(client);
}
}
}

View File

@ -12,10 +12,13 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
import run.halo.app.core.extension.Category;
import run.halo.app.core.extension.Post;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.utils.JsonUtils;
@ -25,6 +28,7 @@ import run.halo.app.infra.utils.JsonUtils;
* @author guqing
* @since 2.0.0
*/
@Component
public class CategoryReconciler implements Reconciler<Reconciler.Request> {
private static final String FINALIZER_NAME = "category-protection";
private final ExtensionClient client;
@ -54,6 +58,13 @@ public class CategoryReconciler implements Reconciler<Reconciler.Request> {
.orElseGet(() -> new Result(false, null));
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Category())
.build();
}
private void addFinalizerIfNecessary(Category oldCategory) {
Set<String> finalizers = oldCategory.getMetadata().getFinalizers();
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {

View File

@ -13,12 +13,15 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.Comment;
import run.halo.app.core.extension.Reply;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.Ref;
import run.halo.app.extension.SchemeManager;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.metrics.MeterUtils;
@ -29,6 +32,7 @@ import run.halo.app.metrics.MeterUtils;
* @author guqing
* @since 2.0.0
*/
@Component
public class CommentReconciler implements Reconciler<Reconciler.Request> {
public static final String FINALIZER_NAME = "comment-protection";
private final ExtensionClient client;
@ -58,6 +62,13 @@ public class CommentReconciler implements Reconciler<Reconciler.Request> {
.orElseGet(() -> new Result(false, null));
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Comment())
.build();
}
private boolean isDeleted(Comment comment) {
return comment.getMetadata().getDeletionTimestamp() != null;
}

View File

@ -2,6 +2,7 @@ package run.halo.app.core.extension.reconciler;
import java.time.Duration;
import java.util.Objects;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import run.halo.app.core.extension.Category;
import run.halo.app.core.extension.MenuItem;
@ -12,9 +13,12 @@ import run.halo.app.core.extension.SinglePage;
import run.halo.app.core.extension.Tag;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.Ref;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.Reconciler.Request;
@Component
public class MenuItemReconciler implements Reconciler<Request> {
private final ExtensionClient client;
@ -46,6 +50,13 @@ public class MenuItemReconciler implements Reconciler<Request> {
}).orElseGet(() -> new Result(false, null));
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new MenuItem())
.build();
}
private Result handleCategoryRef(String menuItemName, MenuItemStatus status, Ref categoryRef) {
client.fetch(Category.class, categoryRef.getName())
.filter(category -> category.getStatus() != null)

View File

@ -1,19 +0,0 @@
package run.halo.app.core.extension.reconciler;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Reconciler;
public class MenuReconciler implements Reconciler<Reconciler.Request> {
private final ExtensionClient client;
public MenuReconciler(ExtensionClient client) {
this.client = client;
}
@Override
public Result reconcile(Request request) {
return new Result(false, null);
}
}

View File

@ -17,10 +17,13 @@ import org.pf4j.PluginRuntimeException;
import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.Plugin;
import run.halo.app.core.extension.ReverseProxy;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.Reconciler.Request;
import run.halo.app.infra.utils.JsonUtils;
@ -37,6 +40,7 @@ import run.halo.app.plugin.resources.BundleResourceUtils;
* @since 2.0.0
*/
@Slf4j
@Component
public class PluginReconciler implements Reconciler<Request> {
private static final String FINALIZER_NAME = "plugin-protection";
private final ExtensionClient client;
@ -63,6 +67,13 @@ public class PluginReconciler implements Reconciler<Request> {
return new Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Plugin())
.build();
}
private void reconcilePluginState(String name) {
if (haloPluginManager.getPlugin(name) == null) {
ensurePluginLoaded();

View File

@ -10,6 +10,7 @@ import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import run.halo.app.content.ContentService;
import run.halo.app.content.PostService;
@ -23,6 +24,8 @@ import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ExtensionOperator;
import run.halo.app.extension.ExtensionUtil;
import run.halo.app.extension.Ref;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.Condition;
import run.halo.app.infra.ConditionList;
@ -45,6 +48,7 @@ import run.halo.app.metrics.MeterUtils;
* @since 2.0.0
*/
@AllArgsConstructor
@Component
public class PostReconciler implements Reconciler<Reconciler.Request> {
private static final String FINALIZER_NAME = "post-protection";
private final ExtensionClient client;
@ -73,6 +77,15 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
return new Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Post())
// TODO Make it configurable
.workerCount(10)
.build();
}
private void reconcileSpec(String name) {
client.fetch(Post.class, name).ifPresent(post -> {
// un-publish post if necessary

View File

@ -4,8 +4,11 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.ReverseProxy;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.plugin.PluginConst;
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
@ -16,6 +19,7 @@ import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
* @author guqing
* @since 2.0.0
*/
@Component
public class ReverseProxyReconciler implements Reconciler<Reconciler.Request> {
private static final String FINALIZER_NAME = "reverse-proxy-protection";
private final ExtensionClient client;
@ -42,6 +46,13 @@ public class ReverseProxyReconciler implements Reconciler<Reconciler.Request> {
.orElse(new Result(false, null));
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new ReverseProxy())
.build();
}
private void registerReverseProxy(ReverseProxy reverseProxy) {
String pluginId = getPluginId(reverseProxy);
routerFunctionRegistry.register(pluginId, reverseProxy).block();

View File

@ -9,16 +9,20 @@ import java.util.Objects;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Lazy;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.Role;
import run.halo.app.core.extension.RoleBinding;
import run.halo.app.core.extension.RoleBinding.Subject;
import run.halo.app.core.extension.User;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.Reconciler.Request;
import run.halo.app.infra.utils.JsonUtils;
@Slf4j
@Component
public class RoleBindingReconciler implements Reconciler<Request> {
private final ExtensionClient client;
@ -68,4 +72,11 @@ public class RoleBindingReconciler implements Reconciler<Request> {
return new Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new RoleBinding())
.build();
}
}

View File

@ -9,9 +9,12 @@ import java.util.Objects;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.Role;
import run.halo.app.core.extension.service.RoleService;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.Reconciler.Request;
import run.halo.app.infra.utils.JsonUtils;
@ -23,6 +26,7 @@ import run.halo.app.infra.utils.JsonUtils;
* @since 2.0.0
*/
@Slf4j
@Component
public class RoleReconciler implements Reconciler<Request> {
private final ExtensionClient client;
@ -65,6 +69,13 @@ public class RoleReconciler implements Reconciler<Request> {
return new Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Role())
.build();
}
private List<String> aggregateUiPermissions(List<Role> dependencyRoles) {
return dependencyRoles.stream()
.filter(role -> role.getMetadata().getAnnotations() != null)

View File

@ -14,6 +14,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import run.halo.app.content.ContentService;
import run.halo.app.content.permalinks.ExtensionLocator;
@ -26,6 +27,8 @@ import run.halo.app.extension.ExtensionOperator;
import run.halo.app.extension.ExtensionUtil;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.Ref;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.Condition;
import run.halo.app.infra.ConditionList;
@ -51,6 +54,7 @@ import run.halo.app.theme.router.PermalinkIndexDeleteCommand;
*/
@Slf4j
@AllArgsConstructor
@Component
public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
private static final String FINALIZER_NAME = "single-page-protection";
private static final GroupVersionKind GVK = GroupVersionKind.fromExtension(SinglePage.class);
@ -81,6 +85,13 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
return new Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new SinglePage())
.build();
}
private void reconcileSpec(String name) {
client.fetch(SinglePage.class, name).ifPresent(page -> {
// un-publish if necessary

View File

@ -8,9 +8,12 @@ import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
@ -26,6 +29,7 @@ import run.halo.app.theme.router.PermalinkRuleChangedEvent;
* @since 2.0.0
*/
@Slf4j
@Component
public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
public static final String OLD_THEME_ROUTE_RULES = "halo.run/old-theme-route-rules";
public static final String FINALIZER_NAME = "system-setting-protection";
@ -59,6 +63,13 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
return new Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new ConfigMap())
.build();
}
private void customizeSystem(String name) {
if (!SystemSetting.SYSTEM_CONFIG_DEFAULT.equals(name)) {
return;

View File

@ -5,10 +5,13 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.springframework.stereotype.Component;
import run.halo.app.content.permalinks.TagPermalinkPolicy;
import run.halo.app.core.extension.Post;
import run.halo.app.core.extension.Tag;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.utils.JsonUtils;
@ -18,6 +21,7 @@ import run.halo.app.infra.utils.JsonUtils;
* @author guqing
* @since 2.0.0
*/
@Component
public class TagReconciler implements Reconciler<Reconciler.Request> {
private static final String FINALIZER_NAME = "tag-protection";
private final ExtensionClient client;
@ -46,6 +50,13 @@ public class TagReconciler implements Reconciler<Reconciler.Request> {
.orElseGet(() -> new Result(false, null));
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Tag())
.build();
}
private void cleanUpResources(Tag tag) {
// remove permalink from permalink indexer
tagPermalinkPolicy.onPermalinkDelete(tag);

View File

@ -9,6 +9,7 @@ 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;
@ -16,6 +17,8 @@ import run.halo.app.core.extension.Theme;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.Reconciler.Request;
import run.halo.app.infra.exception.ThemeUninstallException;
@ -29,6 +32,7 @@ import run.halo.app.theme.ThemePathPolicy;
* @author guqing
* @since 2.0.0
*/
@Component
public class ThemeReconciler implements Reconciler<Request> {
private final ExtensionClient client;
@ -52,6 +56,13 @@ public class ThemeReconciler implements Reconciler<Request> {
return new Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Theme())
.build();
}
private void reconcileStatus(String name) {
client.fetch(Theme.class, name).ifPresent(theme -> {
Theme oldTheme = JsonUtils.deepCopy(theme);

View File

@ -1,11 +1,16 @@
package run.halo.app.core.extension.reconciler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.User;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.Reconciler.Request;
@Slf4j
@Component
public class UserReconciler implements Reconciler<Request> {
private final ExtensionClient client;
@ -20,4 +25,11 @@ public class UserReconciler implements Reconciler<Request> {
return new Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new User())
.build();
}
}

View File

@ -6,6 +6,7 @@ import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -17,6 +18,8 @@ import run.halo.app.core.extension.attachment.endpoint.AttachmentHandler;
import run.halo.app.core.extension.attachment.endpoint.AttachmentHandler.DeleteOption;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.Reconciler.Request;
import run.halo.app.infra.ExternalUrlSupplier;
@ -24,6 +27,7 @@ import run.halo.app.infra.exception.NotFoundException;
import run.halo.app.plugin.ExtensionComponentsFinder;
@Slf4j
@Component
public class AttachmentReconciler implements Reconciler<Request> {
private final ExtensionClient client;
@ -92,6 +96,13 @@ public class AttachmentReconciler implements Reconciler<Request> {
return null;
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Attachment())
.build();
}
void updateStatus(String attachmentName, AttachmentStatus status) {
client.fetch(Attachment.class, attachmentName)
.filter(attachment -> !Objects.deepEquals(attachment.getStatus(), status))

View File

@ -37,10 +37,11 @@ public class ControllerBuilder {
private int workerCount = 1;
public ControllerBuilder(String name, ExtensionClient client) {
Assert.hasText(name, "Extension name is required");
public ControllerBuilder(Reconciler<Request> reconciler, ExtensionClient client) {
Assert.notNull(reconciler, "Reconciler must not be null");
Assert.notNull(client, "Extension client must not be null");
this.name = name;
this.name = reconciler.getClass().getName();
this.reconciler = reconciler;
this.client = client;
}
@ -54,11 +55,6 @@ public class ControllerBuilder {
return this;
}
public ControllerBuilder reconciler(Reconciler<Request> reconciler) {
this.reconciler = reconciler;
return this;
}
public ControllerBuilder nowSupplier(Supplier<Instant> nowSupplier) {
this.nowSupplier = nowSupplier;
return this;

View File

@ -1,42 +1,19 @@
package run.halo.app.extension.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
public interface ControllerManager {
@Slf4j
public class ControllerManager implements ApplicationListener<ApplicationReadyEvent>,
ApplicationContextAware, DisposableBean {
/**
* Register and start a reconciler.
*
* @param reconciler reconciler must not be null.
*/
void start(Reconciler<Reconciler.Request> reconciler);
private ApplicationContext applicationContext;
/**
* Unregister and stop a reconciler.
*
* @param reconciler reconciler must not be null.
*/
void stop(Reconciler<Reconciler.Request> reconciler);
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
applicationContext.getBeansOfType(Controller.class).values().forEach(Controller::start);
}
@Override
public void destroy() {
var controllers =
applicationContext.getBeansOfType(Controller.class).values();
log.info("Shutting down {} controllers...", controllers.size());
controllers.forEach(
controller -> {
try {
controller.dispose();
} catch (Throwable t) {
log.error("Failed to dispose controller {}", controller.getName(), t);
}
});
log.info("Shutdown {} controllers.", controllers.size());
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

View File

@ -0,0 +1,83 @@
package run.halo.app.extension.controller;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Reconciler.Request;
import run.halo.app.infra.SchemeInitializedEvent;
@Slf4j
public class DefaultControllerManager
implements ApplicationListener<SchemeInitializedEvent>,
ApplicationContextAware, DisposableBean, ControllerManager {
private final ExtensionClient client;
private ApplicationContext applicationContext;
/**
* Map with key: reconciler class name, value: controller self.
*/
private final ConcurrentHashMap<String, Controller> controllers;
public DefaultControllerManager(ExtensionClient client) {
this.client = client;
controllers = new ConcurrentHashMap<>();
}
@Override
public void start(Reconciler<Request> reconciler) {
var builder = new ControllerBuilder(reconciler, client);
var controller = reconciler.setupWith(builder);
controllers.put(reconciler.getClass().getName(), controller);
controller.start();
}
@Override
public void stop(Reconciler<Request> reconciler) {
var controller = controllers.remove(reconciler.getClass().getName());
// destroy it
disposeSilently(controller);
}
private static void disposeSilently(Controller controller) {
if (controller == null) {
return;
}
try {
log.info("Shutting down controller {}...", controller.getName());
controller.dispose();
log.info("Shutdown controller {} successfully", controller.getName());
} catch (Throwable t) {
log.error("Failed to dispose controller {}", controller.getName(), t);
}
}
@Override
public void destroy() {
log.info("Shutting down {} controllers...", controllers.size());
controllers.forEach((name, controller) -> disposeSilently(controller));
log.info("Shutdown {} controllers.", controllers.size());
}
@Override
public void onApplicationEvent(SchemeInitializedEvent event) {
// register reconcilers in system after scheme initialized
applicationContext.<Reconciler<Request>>getBeanProvider(
forClassWithGenerics(Reconciler.class, Request.class))
.orderedStream()
.forEach(this::start);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

View File

@ -6,6 +6,8 @@ public interface Reconciler<R> {
Result reconcile(R request);
Controller setupWith(ControllerBuilder builder);
record Request(String name) {
}

View File

@ -1,36 +0,0 @@
package run.halo.app.extension.gc;
import java.time.Duration;
import java.time.Instant;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ExtensionConverter;
import run.halo.app.extension.SchemeManager;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.DefaultController;
import run.halo.app.extension.controller.DefaultDelayQueue;
import run.halo.app.extension.store.ExtensionStoreClient;
@Configuration(proxyBeanMethods = false)
public class GarbageCollectorConfiguration {
@Bean
Controller garbageCollector(ExtensionClient client,
ExtensionStoreClient storeClient,
ExtensionConverter converter,
SchemeManager schemeManager) {
var reconciler = new GcReconciler(client, storeClient, converter);
var queue = new DefaultDelayQueue<GcRequest>(Instant::now, Duration.ofMillis(500));
var synchronizer = new GcSynchronizer(client, queue, schemeManager);
return new DefaultController<>(
"garbage-collector-controller",
reconciler,
queue,
synchronizer,
Duration.ofMillis(500),
Duration.ofSeconds(1000),
// TODO Make it configurable
10);
}
}

View File

@ -0,0 +1,28 @@
package run.halo.app.extension.gc;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import run.halo.app.extension.controller.Controller;
import run.halo.app.infra.SchemeInitializedEvent;
@Component
public class GcControllerInitializer
implements ApplicationListener<SchemeInitializedEvent>, DisposableBean {
private final Controller gcController;
public GcControllerInitializer(GcReconciler gcReconciler) {
this.gcController = gcReconciler.setupWith(null);
}
@Override
public void onApplicationEvent(SchemeInitializedEvent event) {
gcController.start();
}
@Override
public void destroy() throws Exception {
gcController.dispose();
}
}

View File

@ -1,15 +1,24 @@
package run.halo.app.extension.gc;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import run.halo.app.extension.Extension;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ExtensionConverter;
import run.halo.app.extension.SchemeManager;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.DefaultController;
import run.halo.app.extension.controller.DefaultDelayQueue;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.store.ExtensionStoreClient;
@Slf4j
@Component
class GcReconciler implements Reconciler<GcRequest> {
private final ExtensionClient client;
@ -18,11 +27,14 @@ class GcReconciler implements Reconciler<GcRequest> {
private final ExtensionConverter converter;
private final SchemeManager schemeManager;
GcReconciler(ExtensionClient client, ExtensionStoreClient storeClient,
ExtensionConverter converter) {
ExtensionConverter converter, SchemeManager schemeManager) {
this.client = client;
this.storeClient = storeClient;
this.converter = converter;
this.schemeManager = schemeManager;
}
@ -41,6 +53,21 @@ class GcReconciler implements Reconciler<GcRequest> {
return null;
}
@Override
public Controller setupWith(ControllerBuilder builder) {
var queue = new DefaultDelayQueue<GcRequest>(Instant::now, Duration.ofMillis(500));
var synchronizer = new GcSynchronizer(client, queue, schemeManager);
return new DefaultController<>(
"garbage-collector-controller",
this,
queue,
synchronizer,
Duration.ofMillis(500),
Duration.ofSeconds(1000),
// TODO Make it configurable
10);
}
private Predicate<Extension> deletable() {
return extension -> CollectionUtils.isEmpty(extension.getMetadata().getFinalizers())
&& extension.getMetadata().getDeletionTimestamp() != null;

View File

@ -20,28 +20,23 @@ class ControllerBuilderTest {
ExtensionClient client;
@Test
void buildWithBlankName() {
void buildWithNullReconciler() {
assertThrows(IllegalArgumentException.class,
() -> new ControllerBuilder("", client).build());
() -> new ControllerBuilder(null, client).build(), "Reconciler must not be null");
}
@Test
void buildWithNullClient() {
assertThrows(IllegalArgumentException.class,
() -> new ControllerBuilder("fake-name", null).build());
() -> new ControllerBuilder(new FakeReconciler(), null).build());
}
@Test
void buildTest() {
assertThrows(IllegalArgumentException.class,
() -> new ControllerBuilder("fake-name", client)
() -> new ControllerBuilder(new FakeReconciler(), client)
.build(),
"Extension must not be null");
assertThrows(IllegalArgumentException.class,
() -> new ControllerBuilder("fake-name", client)
.extension(new FakeExtension())
.build(),
"Reconciler must not be null");
assertNotNull(fakeBuilder().build());
@ -92,9 +87,21 @@ class ControllerBuilderTest {
}
ControllerBuilder fakeBuilder() {
return new ControllerBuilder("fake-name", client)
.extension(new FakeExtension())
.reconciler(request -> new Reconciler.Result(false, null));
return new ControllerBuilder(new FakeReconciler(), client)
.extension(new FakeExtension());
}
static class FakeReconciler implements Reconciler<Reconciler.Request> {
@Override
public Result reconcile(Request request) {
return new Reconciler.Result(false, null);
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return null;
}
}
}