diff --git a/src/main/java/run/halo/app/core/extension/reconciler/PluginReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/PluginReconciler.java index d14f44dba..8835fea63 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/PluginReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/PluginReconciler.java @@ -29,9 +29,11 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Component; import org.springframework.util.Assert; +import org.springframework.web.util.UriComponentsBuilder; import run.halo.app.core.extension.Plugin; import run.halo.app.core.extension.ReverseProxy; import run.halo.app.core.extension.Setting; @@ -108,20 +110,12 @@ public class PluginReconciler implements Reconciler { } createInitialReverseProxyIfNotPresent(plugin); - Plugin.PluginStatus status = plugin.statusNonNull(); - // filled logo path - String logo = plugin.getSpec().getLogo(); - if (PathUtils.isAbsoluteUri(logo)) { - status.setLogo(logo); - } else { - String assetsPrefix = - PluginConst.assertsRoutePrefix(plugin.getMetadata().getName()); - status.setLogo(PathUtils.combinePath(assetsPrefix, logo)); - } + generateAccessibleLogoUrl(plugin); // update phase PluginWrapper pluginWrapper = getPluginWrapper(name); + Plugin.PluginStatus status = plugin.statusNonNull(); status.setPhase(pluginWrapper.getPluginState()); updateStatus(plugin.getMetadata().getName(), status); return false; @@ -129,6 +123,23 @@ public class PluginReconciler implements Reconciler { .orElse(false); } + void generateAccessibleLogoUrl(Plugin plugin) { + String logo = plugin.getSpec().getLogo(); + if (StringUtils.isBlank(logo)) { + return; + } + Plugin.PluginStatus status = plugin.statusNonNull(); + if (PathUtils.isAbsoluteUri(logo)) { + status.setLogo(logo); + } else { + String assetsPrefix = + PluginConst.assertsRoutePrefix(plugin.getMetadata().getName()); + String versionedLogo = + applyVersioningToStaticResource(logo, plugin.getSpec().getVersion()); + status.setLogo(PathUtils.combinePath(assetsPrefix, versionedLogo)); + } + } + Optional lookupPluginSetting(String name, String settingName) { Assert.notNull(name, "Plugin name must not be null"); Assert.notNull(settingName, "Setting name must not be null"); @@ -374,12 +385,15 @@ public class PluginReconciler implements Reconciler { plugin.statusNonNull().setLastStartTime(Instant.now()); + final String pluginVersion = plugin.getSpec().getVersion(); String jsBundlePath = BundleResourceUtils.getJsBundlePath(haloPluginManager, name); + jsBundlePath = applyVersioningToStaticResource(jsBundlePath, pluginVersion); status.setEntry(jsBundlePath); String cssBundlePath = BundleResourceUtils.getCssBundlePath(haloPluginManager, name); + cssBundlePath = applyVersioningToStaticResource(cssBundlePath, pluginVersion); status.setStylesheet(cssBundlePath); status.setPhase(currentState); @@ -398,6 +412,15 @@ public class PluginReconciler implements Reconciler { }); } + private String applyVersioningToStaticResource(@Nullable String path, String pluginVersion) { + if (StringUtils.isNotBlank(path)) { + return UriComponentsBuilder.fromUriString(path) + .queryParam("version", pluginVersion) + .build().toString(); + } + return path; + } + PluginStartingError getStaringErrorInfo(String name) { PluginStartingError startingError = haloPluginManager.getPluginStartingError(name); diff --git a/src/test/java/run/halo/app/core/extension/reconciler/PluginReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/PluginReconcilerTest.java index b7ff0ac45..31f378583 100644 --- a/src/test/java/run/halo/app/core/extension/reconciler/PluginReconcilerTest.java +++ b/src/test/java/run/halo/app/core/extension/reconciler/PluginReconcilerTest.java @@ -18,6 +18,7 @@ import java.util.Optional; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -287,6 +288,78 @@ class PluginReconcilerTest { verify(extensionClient, times(1)).update(any()); } + @Nested + class PluginLogoTest { + + @Test + void absoluteUri() { + Plugin plugin = new Plugin(); + plugin.setSpec(new Plugin.PluginSpec()); + plugin.getSpec().setLogo("https://example.com/logo.png"); + plugin.getSpec().setVersion("1.0.0"); + pluginReconciler.generateAccessibleLogoUrl(plugin); + assertThat(plugin.statusNonNull().getLogo()) + .isEqualTo("https://example.com/logo.png"); + } + + @Test + void absoluteUriWithQueryParam() { + Plugin plugin = new Plugin(); + plugin.setSpec(new Plugin.PluginSpec()); + plugin.getSpec().setLogo("https://example.com/logo.png?hello=world"); + plugin.getSpec().setVersion("1.0.0"); + pluginReconciler.generateAccessibleLogoUrl(plugin); + assertThat(plugin.statusNonNull().getLogo()) + .isEqualTo("https://example.com/logo.png?hello=world"); + } + + @Test + void logoIsNull() { + Plugin plugin = new Plugin(); + plugin.setSpec(new Plugin.PluginSpec()); + plugin.getSpec().setLogo(null); + plugin.getSpec().setVersion("1.0.0"); + pluginReconciler.generateAccessibleLogoUrl(plugin); + assertThat(plugin.statusNonNull().getLogo()).isNull(); + } + + @Test + void logoIsEmpty() { + Plugin plugin = new Plugin(); + plugin.setSpec(new Plugin.PluginSpec()); + plugin.getSpec().setLogo(""); + plugin.getSpec().setVersion("1.0.0"); + pluginReconciler.generateAccessibleLogoUrl(plugin); + assertThat(plugin.statusNonNull().getLogo()).isNull(); + } + + @Test + void relativePath() { + Plugin plugin = new Plugin(); + plugin.setSpec(new Plugin.PluginSpec()); + plugin.setMetadata(new Metadata()); + plugin.getMetadata().setName("fake-plugin"); + plugin.getSpec().setLogo("/static/logo.jpg"); + plugin.getSpec().setVersion("1.0.0"); + pluginReconciler.generateAccessibleLogoUrl(plugin); + assertThat(plugin.statusNonNull().getLogo()) + .isEqualTo("/plugins/fake-plugin/assets/static/logo.jpg?version=1.0.0"); + } + + @Test + void dataBlob() { + Plugin plugin = new Plugin(); + plugin.setSpec(new Plugin.PluginSpec()); + plugin.setMetadata(new Metadata()); + plugin.getMetadata().setName("fake-plugin"); + plugin.getSpec().setLogo("data:image/gif;base64,R0lGODfake"); + plugin.getSpec().setVersion("2.0.0"); + pluginReconciler.generateAccessibleLogoUrl(plugin); + assertThat(plugin.statusNonNull().getLogo()) + .isEqualTo("data:image/gif;base64,R0lGODfake"); + } + } + private ArgumentCaptor doReconcileNeedRequeue() { ArgumentCaptor pluginCaptor = ArgumentCaptor.forClass(Plugin.class); doNothing().when(extensionClient).update(pluginCaptor.capture());