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 000000000..f2c112136 Binary files /dev/null and b/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/demo-stream-0.0.1-SNAPSHOT.jar differ diff --git a/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/demo-stream-0.0.1-SNAPSHOT.pom b/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/demo-stream-0.0.1-SNAPSHOT.pom new file mode 100644 index 000000000..b6eb34938 --- /dev/null +++ b/spring-cloud-function-deployer/mavenrepo/oz/demo/demo-stream/0.0.1-SNAPSHOT/demo-stream-0.0.1-SNAPSHOT.pom @@ -0,0 +1,107 @@ + + + 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; + } } }