From 4e322d3fa068881ef809e3b6d7bbc29cbc9ad6b4 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 21 Jul 2020 16:31:23 +0200 Subject: [PATCH] GH-555 Add missing maven resource support to function deployer Resolves #555 --- .../main/asciidoc/spring-cloud-function.adoc | 40 ++++++- .../demo-stream-0.0.1-SNAPSHOT.jar | Bin 0 -> 4086 bytes .../demo-stream-0.0.1-SNAPSHOT.pom | 107 ++++++++++++++++++ .../0.0.1-SNAPSHOT/maven-metadata-local.xml | 24 ++++ .../demo/demo-stream/maven-metadata-local.xml | 11 ++ spring-cloud-function-deployer/pom.xml | 5 + .../FunctionDeployerConfiguration.java | 18 ++- .../deployer/FunctionDeployerTests.java | 24 ++++ 8 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/demo-stream-0.0.1-SNAPSHOT.jar create mode 100644 spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/demo-stream-0.0.1-SNAPSHOT.pom create mode 100644 spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/maven-metadata-local.xml create mode 100644 spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/maven-metadata-local.xml diff --git a/docs/src/main/asciidoc/spring-cloud-function.adoc b/docs/src/main/asciidoc/spring-cloud-function.adoc index be1686e2b..f53e97d92 100644 --- a/docs/src/main/asciidoc/spring-cloud-function.adoc +++ b/docs/src/main/asciidoc/spring-cloud-function.adoc @@ -353,7 +353,12 @@ The standard entry point is to add `spring-cloud-function-deployer` to the class ``` -At a minimum the user has to provide a `spring.cloud.function.location` which is a URL or resource location for the archive containing the functions. It can optionally use a `maven:` prefix to locate the artifact via a dependency lookup (see `FunctionProperties` for complete details). A Spring Boot application is bootstrapped from the jar file, using the `MANIFEST.MF` to locate a start class, so that a standard Spring Boot fat jar works well, for example. If the target jar can be launched successfully then the result is a function registered in the main application's `FunctionCatalog`. The registered function can be applied by code in the main application, even though it was created in an isolated class loader (by deault). +At a minimum the user has to provide a `spring.cloud.function.location` which is a URL or resource location for the archive containing +the functions. It can optionally use a `maven:` prefix to locate the artifact via a dependency lookup (see `FunctionProperties` +for complete details). A Spring Boot application is bootstrapped from the jar file, using the `MANIFEST.MF` to locate a start class, so +that a standard Spring Boot fat jar works well, for example. If the target jar can be launched successfully then the result is a function +registered in the main application's `FunctionCatalog`. The registered function can be applied by code in the main application, even though +it was created in an isolated class loader (by deault). Here is the example of deploying a JAR which contains an 'uppercase' function and invoking it . @@ -373,6 +378,39 @@ public class DeployFunctionDemo { } ``` +And here is the example using Maven URI (taken from one of the tests in `FunctionDeployerTests`): + +```java +@SpringBootApplication +public class DeployFunctionDemo { + + public static void main(String[] args) { + String[] args = new String[] { + "--spring.cloud.function.location=maven://oz.demo:demo-uppercase:0.0.1-SNAPSHOT", + "--spring.cloud.function.function-class=oz.demo.uppercase.MyFunction" }; + + ApplicationContext context = SpringApplication.run(DeployerApplication.class, args); + FunctionCatalog catalog = context.getBean(FunctionCatalog.class); + Function function = catalog.lookup("myFunction"); + + assertThat(function.apply("bob")).isEqualTo("BOB"); + } +} +``` + +Keep in mind that Maven resource such as local and remote repositories, user, password and more are resolved using default MavenProperties which +effectively use local defaults and will work for majority of cases. However if you need to customize you can simply provide a bean of type +`MavenProperties` where you can set additional properties (see example below). + +```java +@Bean +public MavenProperties mavenProperties() { + MavenProperties properties = new MavenProperties(); + properties.setLocalRepository("target/it/"); + return properties; +} +``` + === Supported Packaging Scenarios Currently Spring Cloud Function supports several packaging scenarios to give you the most flexibility when it comes to deploying functions. diff --git a/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/demo-stream-0.0.1-SNAPSHOT.jar b/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/demo-stream-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..f2c112136b9ad1d0c05b6e5dd53bd2344fa2c0f7 GIT binary patch literal 4086 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 + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.0.RELEASE + + + + oz.demo + demo-stream + 0.0.1-SNAPSHOT + demo-stream + Demo project for Spring Boot + + + 1.8 + Hoxton.SR5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + + + + + + + diff --git a/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/maven-metadata-local.xml b/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/maven-metadata-local.xml new file mode 100644 index 000000000..98aed06e9 --- /dev/null +++ b/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/maven-metadata-local.xml @@ -0,0 +1,24 @@ + + + oz.demo + demo-stream + 0.0.1-SNAPSHOT + + + true + + 20200721131233 + + + jar + 0.0.1-SNAPSHOT + 20200721131233 + + + pom + 0.0.1-SNAPSHOT + 20200721131233 + + + + diff --git a/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/maven-metadata-local.xml b/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/maven-metadata-local.xml new file mode 100644 index 000000000..bcc06a525 --- /dev/null +++ b/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/maven-metadata-local.xml @@ -0,0 +1,11 @@ + + + oz.demo + demo-stream + + + 0.0.1-SNAPSHOT + + 20200721131233 + + diff --git a/spring-cloud-function-deployer/pom.xml b/spring-cloud-function-deployer/pom.xml index 8e08d703a..3b1c1634e 100644 --- a/spring-cloud-function-deployer/pom.xml +++ b/spring-cloud-function-deployer/pom.xml @@ -30,6 +30,11 @@ org.springframework.cloud spring-cloud-function-context + + org.springframework.cloud + spring-cloud-deployer-resource-maven + 2.4.0-M1 + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionDeployerConfiguration.java b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionDeployerConfiguration.java index afb447550..f4dd26d8c 100644 --- a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionDeployerConfiguration.java +++ b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionDeployerConfiguration.java @@ -33,6 +33,8 @@ import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.loader.archive.Archive; import org.springframework.boot.loader.archive.ExplodedArchive; import org.springframework.boot.loader.archive.JarFileArchive; +import org.springframework.cloud.deployer.resource.maven.MavenProperties; +import org.springframework.cloud.deployer.resource.maven.MavenResourceLoader; import org.springframework.cloud.function.context.FunctionProperties; import org.springframework.cloud.function.context.FunctionRegistry; import org.springframework.context.SmartLifecycle; @@ -42,6 +44,8 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -64,13 +68,23 @@ public class FunctionDeployerConfiguration { @Bean SmartLifecycle functionArchiveDeployer(FunctionDeployerProperties functionProperties, - FunctionRegistry functionRegistry, ApplicationArguments arguments) { + FunctionRegistry functionRegistry, ApplicationArguments arguments, @Nullable MavenProperties mavenProperties) { ApplicationArguments updatedArguments = this.updateArguments(arguments); Archive archive = null; try { - File file = new File(functionProperties.getLocation()); + File file; + String location = functionProperties.getLocation(); + Assert.hasText(location, "`spring.cloud.function.location` property must be defined."); + if (location.startsWith("maven://")) { + MavenResourceLoader resourceLoader = new MavenResourceLoader(mavenProperties); + file = resourceLoader.getResource(location).getFile(); + } + else { + file = new File(location); + } + if (!file.exists()) { throw new IllegalStateException("Failed to create archive: " + functionProperties.getLocation() + " does not exist"); } diff --git a/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/FunctionDeployerTests.java b/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/FunctionDeployerTests.java index 617e39206..580cd62c5 100644 --- a/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/FunctionDeployerTests.java +++ b/spring-cloud-function-deployer/src/test/java/org/springframework/cloud/function/deployer/FunctionDeployerTests.java @@ -16,6 +16,8 @@ package org.springframework.cloud.function.deployer; + + import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -29,11 +31,14 @@ import reactor.util.function.Tuples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.deployer.resource.maven.MavenProperties; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -48,6 +53,19 @@ public class FunctionDeployerTests { System.clearProperty("spring.cloud.function.definition"); } + @Test + public void testWithMavenConfiguration() throws Exception { + String[] args = new String[] { + "--spring.cloud.function.location=maven://oz.demo:demo-stream:0.0.1-SNAPSHOT", + "--spring.cloud.function.function-class=oz.demo.demostream.MyFunction" }; + + ApplicationContext context = SpringApplication.run(DeployerApplication.class, args); + FunctionCatalog catalog = context.getBean(FunctionCatalog.class); + Function function = catalog.lookup("myFunction"); + + assertThat(function.apply("bob")).isEqualTo("BOB"); + } + /* * Target function `class UpperCaseFunction implements Function` * Main/Start class present, no Spring configuration @@ -376,5 +394,11 @@ public class FunctionDeployerTests { @SpringBootApplication(proxyBeanMethods = false) private static class DeployerApplication { + @Bean + public MavenProperties mavenProperties() { + MavenProperties properties = new MavenProperties(); + properties.setLocalRepository("mavenrepo/"); + return properties; + } } }