mirror of https://github.com/halo-dev/halo
refactor: the way of plugin initialize load (#2242)
<!-- Thanks for sending a pull request! Here are some tips for you: 1. 如果这是你的第一次,请阅读我们的贡献指南:<https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>。 1. If this is your first time, please read our contributor guidelines: <https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>. 2. 请根据你解决问题的类型为 Pull Request 添加合适的标签。 2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. 3. 请确保你已经添加并运行了适当的测试。 3. Ensure you have added or ran the appropriate tests for your PR. --> #### What type of PR is this? /kind improvement /area core /milestone 2.0 <!-- 添加其中一个类别: Add one of the following kinds: /kind bug /kind cleanup /kind documentation /kind feature /kind improvement 适当添加其中一个或多个类别(可选): Optionally add one or more of the following kinds if applicable: /kind api-change /kind deprecation /kind failing-test /kind flake /kind regression --> #### What this PR does / why we need it: 1. 优化插件初始化加载方式及 Plugin 自定义模型资源的更新 2. 插件 plugin.yaml 中 license 配置不再支持只配置字符串,而使用如下替代 ```yaml license: - name: "MIT" ``` 3. 可以在 application.yaml 中配置 ```yaml halo: initial-extension-locations: - "path/to/extensions/yaml" ``` 用于在系统启动时创建或更新自定义模型数据 #### Which issue(s) this PR fixes: <!-- PR 合并时自动关闭 issue。 Automatically closes linked issue when PR is merged. 用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)` Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> Fixes # #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? <!-- 如果当前 Pull Request 的修改不会造成用户侧的任何变更,在 `release-note` 代码块儿中填写 `NONE`。 否则请填写用户侧能够理解的 Release Note。如果当前 Pull Request 包含破坏性更新(Break Change), Release Note 需要以 `action required` 开头。 If no, just write "NONE" in the release-note block below. If yes, a release note is required: Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required". --> ```release-note None ```pull/2247/head
parent
1cbd3c74e3
commit
90da5a13a1
|
@ -1,17 +1,19 @@
|
|||
package run.halo.app.core.extension;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.pf4j.PluginState;
|
||||
import org.springframework.lang.NonNull;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.GVK;
|
||||
import run.halo.app.plugin.BasePlugin;
|
||||
|
@ -34,6 +36,20 @@ public class Plugin extends AbstractExtension {
|
|||
|
||||
private PluginStatus status;
|
||||
|
||||
/**
|
||||
* Gets plugin status.
|
||||
*
|
||||
* @return empty object if status is null.
|
||||
*/
|
||||
@NonNull
|
||||
@JsonIgnore
|
||||
public PluginStatus statusNonNull() {
|
||||
if (this.status == null) {
|
||||
return new PluginStatus();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PluginSpec {
|
||||
|
||||
|
@ -51,7 +67,6 @@ public class Plugin extends AbstractExtension {
|
|||
|
||||
private String description;
|
||||
|
||||
@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
||||
private List<License> license;
|
||||
|
||||
/**
|
||||
|
@ -68,6 +83,12 @@ public class Plugin extends AbstractExtension {
|
|||
private String settingName;
|
||||
|
||||
private String configMapName;
|
||||
|
||||
@NonNull
|
||||
@JsonIgnore
|
||||
public List<String> extensionLocationsNonNull() {
|
||||
return Objects.requireNonNullElseGet(extensionLocations, List::of);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
@ -75,14 +96,6 @@ public class Plugin extends AbstractExtension {
|
|||
public static class License {
|
||||
private String name;
|
||||
private String url;
|
||||
|
||||
public License() {
|
||||
}
|
||||
|
||||
public License(String name) {
|
||||
this.name = name;
|
||||
this.url = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
|
|
|
@ -10,12 +10,14 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.pf4j.PluginRuntimeException;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.RuntimeMode;
|
||||
import run.halo.app.core.extension.Plugin;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.PluginStartingError;
|
||||
import run.halo.app.plugin.YamlPluginFinder;
|
||||
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionFactory;
|
||||
|
||||
|
@ -66,7 +68,7 @@ public class PluginReconciler implements Reconciler {
|
|||
}
|
||||
|
||||
private void reconcilePluginState(Plugin plugin) {
|
||||
Plugin.PluginStatus pluginStatus = plugin.getStatus();
|
||||
Plugin.PluginStatus pluginStatus = plugin.statusNonNull();
|
||||
String name = plugin.getMetadata().getName();
|
||||
PluginWrapper pluginWrapper = haloPluginManager.getPlugin(name);
|
||||
if (pluginWrapper == null) {
|
||||
|
@ -81,6 +83,8 @@ public class PluginReconciler implements Reconciler {
|
|||
return;
|
||||
}
|
||||
|
||||
ensureSpecUpToDateWhenDevelopmentMode(pluginWrapper, plugin);
|
||||
|
||||
if (!Objects.equals(pluginStatus.getPhase(), pluginWrapper.getPluginState())) {
|
||||
// Set to the correct state
|
||||
pluginStatus.setPhase(pluginWrapper.getPluginState());
|
||||
|
@ -120,14 +124,14 @@ public class PluginReconciler implements Reconciler {
|
|||
|
||||
private boolean shouldReconcileStartState(Plugin plugin) {
|
||||
return plugin.getSpec().getEnabled()
|
||||
&& plugin.getStatus().getPhase() != PluginState.STARTED;
|
||||
&& plugin.statusNonNull().getPhase() != PluginState.STARTED;
|
||||
}
|
||||
|
||||
private void startPlugin(Plugin plugin) {
|
||||
String pluginName = plugin.getMetadata().getName();
|
||||
PluginState currentState = haloPluginManager.startPlugin(pluginName);
|
||||
handleStatus(plugin, currentState, PluginState.STARTED);
|
||||
Plugin.PluginStatus status = plugin.getStatus();
|
||||
Plugin.PluginStatus status = plugin.statusNonNull();
|
||||
// TODO Check whether the JS bundle rule exists. If it does not exist, do not populate
|
||||
// populate stylesheet path
|
||||
String jsBundleRoute = ReverseProxyRouterFunctionFactory.buildRoutePath(pluginName,
|
||||
|
@ -141,7 +145,7 @@ public class PluginReconciler implements Reconciler {
|
|||
|
||||
private boolean shouldReconcileStopState(Plugin plugin) {
|
||||
return !plugin.getSpec().getEnabled()
|
||||
&& plugin.getStatus().getPhase() == PluginState.STARTED;
|
||||
&& plugin.statusNonNull().getPhase() == PluginState.STARTED;
|
||||
}
|
||||
|
||||
private void stopPlugin(Plugin plugin) {
|
||||
|
@ -152,10 +156,7 @@ public class PluginReconciler implements Reconciler {
|
|||
|
||||
private void handleStatus(Plugin plugin, PluginState currentState,
|
||||
PluginState desiredState) {
|
||||
Plugin.PluginStatus status = plugin.getStatus();
|
||||
if (status == null) {
|
||||
status = new Plugin.PluginStatus();
|
||||
}
|
||||
Plugin.PluginStatus status = plugin.statusNonNull();
|
||||
status.setPhase(currentState);
|
||||
status.setLastTransitionTime(Instant.now());
|
||||
if (desiredState.equals(currentState)) {
|
||||
|
@ -169,4 +170,18 @@ public class PluginReconciler implements Reconciler {
|
|||
throw new PluginRuntimeException(startingError.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureSpecUpToDateWhenDevelopmentMode(PluginWrapper pluginWrapper,
|
||||
Plugin oldPlugin) {
|
||||
if (!RuntimeMode.DEPLOYMENT.equals(pluginWrapper.getRuntimeMode())) {
|
||||
return;
|
||||
}
|
||||
YamlPluginFinder yamlPluginFinder = new YamlPluginFinder();
|
||||
Plugin pluginFromPath = yamlPluginFinder.find(pluginWrapper.getPluginPath());
|
||||
// ensure plugin spec is up to date
|
||||
Plugin.PluginSpec pluginSpec = JsonUtils.deepCopy(pluginFromPath.getSpec());
|
||||
if (!Objects.equals(oldPlugin.getSpec(), pluginSpec)) {
|
||||
oldPlugin.setSpec(pluginSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package run.halo.app.infra;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.thymeleaf.util.StringUtils;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.infra.utils.YamlUnstructuredLoader;
|
||||
|
||||
/**
|
||||
* <p>Extension resources initializer.</p>
|
||||
* <p>Check whether {@link HaloProperties#getInitialExtensionLocations()} is configured
|
||||
* When the system ready, and load resources according to it to creates {@link Unstructured}</p>
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ExtensionResourceInitializer implements ApplicationListener<ApplicationReadyEvent> {
|
||||
private final HaloProperties haloProperties;
|
||||
private final ExtensionClient extensionClient;
|
||||
|
||||
public ExtensionResourceInitializer(HaloProperties haloProperties,
|
||||
ExtensionClient extensionClient) {
|
||||
this.haloProperties = haloProperties;
|
||||
this.extensionClient = extensionClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
|
||||
Set<String> extensionLocations = haloProperties.getInitialExtensionLocations();
|
||||
if (!CollectionUtils.isEmpty(extensionLocations)) {
|
||||
|
||||
Resource[] resources = extensionLocations.stream()
|
||||
.map(this::listYamlFiles)
|
||||
.flatMap(List::stream)
|
||||
.toArray(Resource[]::new);
|
||||
|
||||
log.debug("Initialization loaded [{}] resources to establish.", resources.length);
|
||||
|
||||
new YamlUnstructuredLoader(resources).load()
|
||||
.forEach(unstructured -> extensionClient.fetch(unstructured.groupVersionKind(),
|
||||
unstructured.getMetadata().getName())
|
||||
.ifPresentOrElse(persisted -> {
|
||||
unstructured.getMetadata()
|
||||
.setVersion(persisted.getMetadata().getVersion());
|
||||
extensionClient.update(unstructured);
|
||||
}, () -> extensionClient.create(unstructured)));
|
||||
}
|
||||
}
|
||||
|
||||
private List<FileSystemResource> listYamlFiles(String location) {
|
||||
try (Stream<Path> walk = Files.walk(Paths.get(location))) {
|
||||
return walk.filter(this::isYamlFile)
|
||||
.map(path -> new FileSystemResource(path.toFile()))
|
||||
.toList();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isYamlFile(Path pathname) {
|
||||
Path fileName = pathname.getFileName();
|
||||
return StringUtils.endsWith(fileName, ".yaml")
|
||||
|| StringUtils.endsWith(fileName, ".yml");
|
||||
}
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
package run.halo.app.infra.properties;
|
||||
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @date 2022-04-12
|
||||
* @since 2022-04-12
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "halo")
|
||||
public class HaloProperties {
|
||||
|
||||
private Set<String> initialExtensionLocations;
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package run.halo.app.plugin;
|
||||
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.core.extension.Plugin;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.plugin.event.HaloPluginLoadedEvent;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
public class PluginLoadedListener implements ApplicationListener<HaloPluginLoadedEvent> {
|
||||
private final ExtensionClient extensionClient;
|
||||
|
||||
public PluginLoadedListener(ExtensionClient extensionClient) {
|
||||
this.extensionClient = extensionClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(HaloPluginLoadedEvent event) {
|
||||
PluginWrapper pluginWrapper = event.getPluginWrapper();
|
||||
// TODO: Optimize plugin custom resource loading method
|
||||
// load plugin.yaml
|
||||
YamlPluginFinder yamlPluginFinder = new YamlPluginFinder();
|
||||
Plugin plugin = yamlPluginFinder.find(pluginWrapper.getPluginPath());
|
||||
extensionClient.fetch(Plugin.class, plugin.getMetadata().getName())
|
||||
.ifPresentOrElse(persisted -> {
|
||||
plugin.getMetadata().setVersion(persisted.getMetadata().getVersion());
|
||||
extensionClient.update(plugin);
|
||||
}, () -> extensionClient.create(plugin));
|
||||
}
|
||||
}
|
|
@ -37,8 +37,7 @@ public class PluginStartedListener implements ApplicationListener<HaloPluginStar
|
|||
// load unstructured
|
||||
DefaultResourceLoader resourceLoader =
|
||||
new DefaultResourceLoader(pluginWrapper.getPluginClassLoader());
|
||||
plugin.getSpec().getExtensionLocations()
|
||||
.stream()
|
||||
plugin.getSpec().extensionLocationsNonNull().stream()
|
||||
.map(resourceLoader::getResource)
|
||||
.filter(Resource::exists)
|
||||
.map(resource -> new YamlUnstructuredLoader(resource).load())
|
||||
|
|
|
@ -47,7 +47,8 @@ import run.halo.app.infra.utils.YamlUnstructuredLoader;
|
|||
* # 'displayName' explains what the plugin does in only a few words
|
||||
* displayName: "a name to show"
|
||||
* description: "Tell me more about this plugin."
|
||||
* license: MIT
|
||||
* license:
|
||||
* - name: MIT
|
||||
* </pre>
|
||||
*
|
||||
* @author guqing
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package run.halo.app.infra;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.GroupVersionKind;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link ExtensionResourceInitializer}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ExtensionResourceInitializerTest {
|
||||
|
||||
@Mock
|
||||
private ExtensionClient extensionClient;
|
||||
@Mock
|
||||
private HaloProperties haloProperties;
|
||||
@Mock
|
||||
private ApplicationReadyEvent applicationReadyEvent;
|
||||
|
||||
private ExtensionResourceInitializer extensionResourceInitializer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
extensionResourceInitializer =
|
||||
new ExtensionResourceInitializer(haloProperties, extensionClient);
|
||||
|
||||
Path tempDirectory = Files.createTempDirectory("extension-resource-initializer-test");
|
||||
Path multiDirectory = Files.createDirectories(tempDirectory.resolve("a/b/c"));
|
||||
Files.writeString(tempDirectory.resolve("hello.yml"), """
|
||||
kind: FakeExtension
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: fake-extension
|
||||
spec:
|
||||
hello: world
|
||||
""",
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
Files.writeString(multiDirectory.getParent().resolve("fake-1.txt"), """
|
||||
kind: FakeExtension
|
||||
name: fake-extension
|
||||
""",
|
||||
StandardCharsets.UTF_8);
|
||||
Files.writeString(multiDirectory.resolve("fake.yaml"), """
|
||||
kind: FakeExtension
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: fake-extension
|
||||
spec:
|
||||
hello: world
|
||||
""",
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
// test file in directory
|
||||
Path filePath = Files.createTempDirectory("extension-resource-file-test")
|
||||
.resolve("good.yml");
|
||||
Files.writeString(filePath, """
|
||||
kind: FakeExtension
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: config-file-is-ok
|
||||
spec:
|
||||
key: value
|
||||
""",
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
when(haloProperties.getInitialExtensionLocations())
|
||||
.thenReturn(Set.of(tempDirectory.toString(), filePath.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void onApplicationEvent() throws JSONException {
|
||||
ArgumentCaptor<Unstructured> argumentCaptor = ArgumentCaptor.forClass(Unstructured.class);
|
||||
|
||||
when(extensionClient.fetch(any(GroupVersionKind.class), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
extensionResourceInitializer.onApplicationEvent(applicationReadyEvent);
|
||||
|
||||
verify(extensionClient, times(3)).create(argumentCaptor.capture());
|
||||
|
||||
List<Unstructured> values = argumentCaptor.getAllValues();
|
||||
assertThat(values).isNotNull();
|
||||
assertThat(values).hasSize(3);
|
||||
JSONAssert.assertEquals("""
|
||||
[
|
||||
{
|
||||
"kind": "FakeExtension",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "config-file-is-ok"
|
||||
},
|
||||
"spec": {
|
||||
"key": "value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "FakeExtension",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "fake-extension"
|
||||
},
|
||||
"spec": {
|
||||
"hello": "world"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "FakeExtension",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "fake-extension"
|
||||
},
|
||||
"spec": {
|
||||
"hello": "world"
|
||||
}
|
||||
}
|
||||
]
|
||||
""", JsonUtils.objectToJson(values), false);
|
||||
}
|
||||
}
|
|
@ -88,7 +88,7 @@ class YamlPluginFinderTest {
|
|||
"license": [
|
||||
{
|
||||
"name": "MIT",
|
||||
"url": ""
|
||||
"url": null
|
||||
}
|
||||
],
|
||||
"requires": ">=2.0.0",
|
||||
|
@ -124,50 +124,6 @@ class YamlPluginFinderTest {
|
|||
.hasMessage("Unable to find plugin descriptor file: plugin.yaml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptArrayLicense() throws JSONException {
|
||||
Resource pluginResource = new InMemoryResource("""
|
||||
apiVersion: v1
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: plugin-1
|
||||
spec:
|
||||
license: "MIT"
|
||||
""");
|
||||
Plugin plugin = pluginFinder.unstructuredToPlugin(pluginResource);
|
||||
assertThat(plugin.getSpec()).isNotNull();
|
||||
JSONAssert.assertEquals("""
|
||||
[{
|
||||
"name": "MIT",
|
||||
"url": ""
|
||||
}]
|
||||
""", JsonUtils.objectToJson(plugin.getSpec().getLicense()), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptMultipleItemArrayLicense() throws JSONException {
|
||||
Resource pluginResource = new InMemoryResource("""
|
||||
apiVersion: v1
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: plugin-1
|
||||
spec:
|
||||
license: ["MIT", "Apache-2.0"]
|
||||
""");
|
||||
Plugin plugin = pluginFinder.unstructuredToPlugin(pluginResource);
|
||||
assertThat(plugin.getSpec()).isNotNull();
|
||||
JSONAssert.assertEquals("""
|
||||
[{
|
||||
"name": "MIT",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"name": "Apache-2.0",
|
||||
"url": ""
|
||||
}]
|
||||
""", JsonUtils.objectToJson(plugin.getSpec().getLicense()), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptArrayObjectLicense() throws JSONException {
|
||||
Resource pluginResource = new InMemoryResource("""
|
||||
|
|
|
@ -13,4 +13,5 @@ spec:
|
|||
homepage: https://github.com/guqing/halo-plugin-1
|
||||
displayName: "a name to show"
|
||||
description: "Tell me more about this plugin."
|
||||
license: MIT
|
||||
license:
|
||||
- name: MIT
|
Binary file not shown.
Loading…
Reference in New Issue