GH-555 Add missing maven resource support to function deployer

Resolves #555
This commit is contained in:
Oleg Zhurakousky
2020-07-21 16:31:23 +02:00
parent ae37b3bb67
commit 4e322d3fa0
8 changed files with 226 additions and 3 deletions

View File

@@ -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<String, String> 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.

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<!-- <version>2.2.6.RELEASE</version> -->
<relativePath />
</parent>
<groupId>oz.demo</groupId>
<artifactId>demo-stream</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-stream</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR5</spring-cloud.version>
</properties>
<dependencies>
<!-- <dependency> -->
<!-- <groupId>org.springframework.boot</groupId> -->
<!-- <artifactId>spring-boot-starter-web</artifactId> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.cloud</groupId> -->
<!-- <artifactId>spring-cloud-starter-function-web</artifactId> -->
<!-- <version>3.1.0-SNAPSHOT</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.cloud</groupId> -->
<!-- <artifactId>spring-cloud-function-rsocket</artifactId> -->
<!-- <version>3.1.0-SNAPSHOT</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.cloud</groupId> -->
<!-- <artifactId>spring-cloud-function-web</artifactId> -->
<!-- <version>3.1.0-SNAPSHOT</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.cloud</groupId> -->
<!-- <artifactId>spring-cloud-function-web</artifactId> -->
<!-- <version>3.1.0-SNAPSHOT</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.json</groupId> -->
<!-- <artifactId>json</artifactId> -->
<!-- <version>20190722</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.cloud</groupId> -->
<!-- <artifactId>spring-cloud-function-web</artifactId> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.cloud</groupId> -->
<!-- <artifactId>spring-cloud-stream</artifactId> -->
<!-- <version>3.1.0-SNAPSHOT</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.cloud</groupId> -->
<!-- <artifactId>spring-cloud-stream-binder-rabbit</artifactId> -->
<!-- <version>3.1.0-SNAPSHOT</version> -->
<!-- </dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- <build> -->
<!-- <plugins> -->
<!-- <plugin> -->
<!-- <groupId>org.springframework.boot</groupId> -->
<!-- <artifactId>spring-boot-maven-plugin</artifactId> -->
<!-- </plugin> -->
<!-- </plugins> -->
<!-- </build> -->
</project>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
<groupId>oz.demo</groupId>
<artifactId>demo-stream</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20200721131233</lastUpdated>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>0.0.1-SNAPSHOT</value>
<updated>20200721131233</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>
<value>0.0.1-SNAPSHOT</value>
<updated>20200721131233</updated>
</snapshotVersion>
</snapshotVersions>
</versioning>
</metadata>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>oz.demo</groupId>
<artifactId>demo-stream</artifactId>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20200721131233</lastUpdated>
</versioning>
</metadata>

View File

@@ -30,6 +30,11 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-resource-maven</artifactId>
<version>2.4.0-M1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -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");
}

View File

@@ -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<String, String> function = catalog.lookup("myFunction");
assertThat(function.apply("bob")).isEqualTo("BOB");
}
/*
* Target function `class UpperCaseFunction implements Function<String, String>`
* 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;
}
}
}