feat: plugin supports multiple ways of specifying licenses (#2152)

* feat: plugin supports multiple ways of specifying licenses

* feat: add unit test case for deserialize license
pull/2158/head^2
guqing 2022-06-15 23:36:12 +08:00 committed by GitHub
parent 83d668b0a9
commit fcbf0031a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 165 additions and 29 deletions

View File

@ -1,10 +1,14 @@
package run.halo.app.plugin; package run.halo.app.plugin;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import run.halo.app.extension.AbstractExtension; import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK; import run.halo.app.extension.GVK;
@ -42,7 +46,8 @@ public class Plugin extends AbstractExtension {
private String description; private String description;
private String license; @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<License> license;
/** /**
* SemVer format. * SemVer format.
@ -51,4 +56,19 @@ public class Plugin extends AbstractExtension {
private String pluginClass = BasePlugin.class.getName(); private String pluginClass = BasePlugin.class.getName();
} }
@Getter
@Setter
public static class License {
private String name;
private String url;
public License() {
}
public License(String name) {
this.name = name;
this.url = "";
}
}
} }

View File

@ -2,12 +2,16 @@ package run.halo.app.plugin;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.pf4j.DefaultPluginDescriptor; import org.pf4j.DefaultPluginDescriptor;
import org.pf4j.PluginDependency; import org.pf4j.PluginDependency;
import org.pf4j.PluginDescriptor; import org.pf4j.PluginDescriptor;
import org.pf4j.PluginDescriptorFinder; import org.pf4j.PluginDescriptorFinder;
import org.pf4j.util.FileUtils; import org.pf4j.util.FileUtils;
import org.springframework.util.CollectionUtils;
/** /**
* Find a plugin descriptor for a plugin path. * Find a plugin descriptor for a plugin path.
@ -41,6 +45,7 @@ public class YamlPluginDescriptorFinder implements PluginDescriptorFinder {
private DefaultPluginDescriptor convert(Plugin plugin) { private DefaultPluginDescriptor convert(Plugin plugin) {
String pluginId = plugin.getMetadata().getName(); String pluginId = plugin.getMetadata().getName();
Plugin.PluginSpec spec = plugin.getSpec(); Plugin.PluginSpec spec = plugin.getSpec();
DefaultPluginDescriptor defaultPluginDescriptor = DefaultPluginDescriptor defaultPluginDescriptor =
new DefaultPluginDescriptor(pluginId, new DefaultPluginDescriptor(pluginId,
spec.getDescription(), spec.getDescription(),
@ -48,7 +53,7 @@ public class YamlPluginDescriptorFinder implements PluginDescriptorFinder {
spec.getVersion(), spec.getVersion(),
spec.getRequires(), spec.getRequires(),
spec.getAuthor(), spec.getAuthor(),
spec.getLicense()); joinLicense(spec.getLicense()));
// add dependencies // add dependencies
spec.getPluginDependencies().forEach((pluginDepName, versionRequire) -> { spec.getPluginDependencies().forEach((pluginDepName, versionRequire) -> {
PluginDependency dependency = PluginDependency dependency =
@ -57,4 +62,13 @@ public class YamlPluginDescriptorFinder implements PluginDescriptorFinder {
}); });
return defaultPluginDescriptor; return defaultPluginDescriptor;
} }
private String joinLicense(List<Plugin.License> licenses) {
if (CollectionUtils.isEmpty(licenses)) {
return StringUtils.EMPTY;
}
return licenses.stream()
.map(Plugin.License::getName)
.collect(Collectors.joining(","));
}
} }

View File

@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginRuntimeException; import org.pf4j.PluginRuntimeException;
import org.pf4j.util.FileUtils; import org.pf4j.util.FileUtils;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import run.halo.app.extension.Unstructured; import run.halo.app.extension.Unstructured;
import run.halo.app.infra.utils.YamlUnstructuredLoader; import run.halo.app.infra.utils.YamlUnstructuredLoader;
@ -70,8 +71,13 @@ public class YamlPluginFinder {
if (Files.notExists(propertiesPath)) { if (Files.notExists(propertiesPath)) {
throw new PluginRuntimeException("Cannot find '{}' path", propertiesPath); throw new PluginRuntimeException("Cannot find '{}' path", propertiesPath);
} }
Resource propertyResource = new FileSystemResource(propertiesPath);
return unstructuredToPlugin(propertyResource);
}
protected Plugin unstructuredToPlugin(Resource propertyResource) {
YamlUnstructuredLoader yamlUnstructuredLoader = YamlUnstructuredLoader yamlUnstructuredLoader =
new YamlUnstructuredLoader(new FileSystemResource(propertiesPath)); new YamlUnstructuredLoader(propertyResource);
List<Unstructured> unstructuredList = yamlUnstructuredLoader.load(); List<Unstructured> unstructuredList = yamlUnstructuredLoader.load();
if (unstructuredList.size() != 1) { if (unstructuredList.size() != 1) {
throw new PluginRuntimeException("Unable to find plugin descriptor file '{}'", throw new PluginRuntimeException("Unable to find plugin descriptor file '{}'",

View File

@ -13,7 +13,10 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.pf4j.PluginRuntimeException; import org.pf4j.PluginRuntimeException;
import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.core.io.Resource;
import org.springframework.security.util.InMemoryResource;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
import run.halo.app.extension.Unstructured;
import run.halo.app.infra.utils.JsonUtils; import run.halo.app.infra.utils.JsonUtils;
/** /**
@ -38,33 +41,36 @@ class YamlPluginFinderTest {
Plugin plugin = pluginFinder.find(testPath); Plugin plugin = pluginFinder.find(testPath);
assertThat(plugin).isNotNull(); assertThat(plugin).isNotNull();
JSONAssert.assertEquals(""" JSONAssert.assertEquals("""
{ {
"spec": { "spec": {
"displayName": "a name to show", "displayName": "a name to show",
"version": "0.0.1", "version": "0.0.1",
"author": "guqing", "author": "guqing",
"logo": "https://guqing.xyz/avatar", "logo": "https://guqing.xyz/avatar",
"pluginDependencies": { "pluginDependencies": {
"banana": "0.0.1" "banana": "0.0.1"
},
"homepage": "https://github.com/guqing/halo-plugin-1",
"description": "Tell me more about this plugin.",
"license": [{
"name": "MIT",
"url": ""
}],
"requires": ">=2.0.0",
"pluginClass": "run.halo.app.plugin.BasePlugin"
}, },
"homepage": "https://github.com/guqing/halo-plugin-1", "apiVersion": "v1",
"description": "Tell me more about this plugin.", "kind": "Plugin",
"license": "MIT", "metadata": {
"requires": ">=2.0.0", "name": "plugin-1",
"pluginClass": "run.halo.app.plugin.BasePlugin" "labels": null,
}, "annotations": null,
"apiVersion": "v1", "version": null,
"kind": "Plugin", "creationTimestamp": null,
"metadata": { "deletionTimestamp": null
"name": "plugin-1", }
"labels": null,
"annotations": null,
"version": null,
"creationTimestamp": null,
"deletionTimestamp": null
} }
} """,
""",
JsonUtils.objectToJson(plugin), JsonUtils.objectToJson(plugin),
false); false);
} }
@ -77,4 +83,94 @@ class YamlPluginFinderTest {
}).isInstanceOf(PluginRuntimeException.class) }).isInstanceOf(PluginRuntimeException.class)
.hasMessage("Cannot find '/tmp/plugin.yaml' path"); .hasMessage("Cannot find '/tmp/plugin.yaml' path");
} }
}
@Test
void acceptArrayLicense() throws JSONException, JsonProcessingException {
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 JsonProcessingException, 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, JsonProcessingException {
Resource pluginResource = new InMemoryResource("""
apiVersion: v1
kind: Plugin
metadata:
name: plugin-1
spec:
license:
- name: MIT
url: https://exmple.com
""");
Plugin plugin = pluginFinder.unstructuredToPlugin(pluginResource);
assertThat(plugin.getSpec()).isNotNull();
JSONAssert.assertEquals("""
[{
"name": "MIT",
"url": "https://exmple.com"
}]
""", JsonUtils.objectToJson(plugin.getSpec().getLicense()), false);
}
@Test
void deserializeLicense() throws JSONException, JsonProcessingException {
String pluginJson = """
{
"apiVersion": "v1",
"kind": "Plugin",
"metadata": {
"name": "plugin-1"
},
"spec": {
"license": [
{
"name": "MIT",
"url": "https://exmple.com"
}
]
}
}
""";
Plugin plugin = Unstructured.OBJECT_MAPPER.readValue(pluginJson, Plugin.class);
assertThat(plugin.getSpec()).isNotNull();
JSONAssert.assertEquals(pluginJson, JsonUtils.objectToJson(plugin), false);
}
}