Skip to content

Commit

Permalink
feat (jkube-kit/jkube-kit-spring-boot) : Support for Spring Native (e…
Browse files Browse the repository at this point in the history
…clipse-jkube#2138)

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia authored and manusa committed Sep 7, 2023
1 parent 9aa89d2 commit 047e718
Show file tree
Hide file tree
Showing 9 changed files with 409 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Usage:
./scripts/extract-changelog-for-version.sh 1.3.37 5
```
### 1.15-SNAPSHOT
* Fix #2138: Support for Spring Boot Native Image

### 1.14.0 (2023-08-31)
* Fix #1674: SpringBootGenerator utilizes the layered jar if present and use it as Docker layers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,27 @@ public static boolean isLayeredJar(File fatJar) {
throw new IllegalStateException("Failure in inspecting fat jar for layers.idx file", ioException);
}
}

public static Plugin getNativePlugin(JavaProject project) {
Plugin plugin = JKubeProjectUtil.getPlugin(project, "org.graalvm.buildtools", "native-maven-plugin");
if (plugin != null) {
return plugin;
}
return JKubeProjectUtil.getPlugin(project, "org.graalvm.buildtools.native", "org.graalvm.buildtools.native.gradle.plugin");
}

public static File getNativeArtifactFile(JavaProject project) {
for (String location : new String[] {"", "native/nativeCompile/"}) {
File nativeArtifactDir = new File(project.getBuildDirectory(), location);
File[] nativeExecutableArtifacts = nativeArtifactDir.listFiles(f -> f.isFile() && f.canExecute());
if (nativeExecutableArtifacts != null && nativeExecutableArtifacts.length > 0) {
if (nativeExecutableArtifacts.length == 1) {
return nativeExecutableArtifacts[0];
}
throw new IllegalStateException("More than one native executable file found in " + nativeArtifactDir.getAbsolutePath());
}
}
return null;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,148 @@ void isLayeredJar_whenJarContainsLayers_thenReturnTrue(@TempDir File temporaryFo
// Then
assertThat(result).isTrue();
}

@Test
void getNativePlugin_whenNoNativePluginPresent_thenReturnNull() {
assertThat(SpringBootUtil.getNativePlugin(JavaProject.builder().build())).isNull();
}

@Test
void getNativePlugin_whenMavenNativePluginPresent_thenReturnPlugin() {
// Given
JavaProject javaProject = JavaProject.builder()
.plugin(Plugin.builder()
.groupId("org.graalvm.buildtools")
.artifactId("native-maven-plugin")
.build())
.build();

// When
Plugin plugin = SpringBootUtil.getNativePlugin(javaProject);

// Then
assertThat(plugin).isNotNull();
}

@Test
void getNativePlugin_whenGradleNativePluginPresent_thenReturnPlugin() {
// Given
JavaProject javaProject = JavaProject.builder()
.plugin(Plugin.builder()
.groupId("org.graalvm.buildtools.native")
.artifactId("org.graalvm.buildtools.native.gradle.plugin")
.build())
.build();

// When
Plugin plugin = SpringBootUtil.getNativePlugin(javaProject);

// Then
assertThat(plugin).isNotNull();
}

@Test
void getNativeArtifactFile_whenNativeExecutableNotFound_thenReturnNull(@TempDir File temporaryFolder) throws IOException {
// Given
JavaProject javaProject = JavaProject.builder()
.artifactId("sample")
.buildDirectory(temporaryFolder)
.plugin(Plugin.builder()
.groupId("org.graalvm.buildtools")
.artifactId("native-maven-plugin")
.build())
.build();

// When
File nativeArtifactFound = SpringBootUtil.getNativeArtifactFile(javaProject);

// Then
assertThat(nativeArtifactFound).isNull();
}

@Test
void getNativeArtifactFile_whenNativeExecutableInStandardMavenBuildDirectory_thenReturnNativeArtifact(@TempDir File temporaryFolder) throws IOException {
// Given
File nativeArtifactFile = Files.createFile(temporaryFolder.toPath().resolve("sample")).toFile();
assertThat(nativeArtifactFile.setExecutable(true)).isTrue();
JavaProject javaProject = JavaProject.builder()
.artifactId("sample")
.buildDirectory(temporaryFolder)
.plugin(Plugin.builder()
.groupId("org.graalvm.buildtools")
.artifactId("native-maven-plugin")
.build())
.build();

// When
File nativeArtifactFound = SpringBootUtil.getNativeArtifactFile(javaProject);

// Then
assertThat(nativeArtifactFound).hasName("sample");
}

@Test
void getNativeArtifactFile_whenMoreThanOneNativeExecutableInStandardMavenBuildDirectory_thenThrowException(@TempDir File temporaryFolder) throws IOException {
// Given
File nativeArtifactFile = Files.createFile(temporaryFolder.toPath().resolve("sample")).toFile();
assertThat(nativeArtifactFile.setExecutable(true)).isTrue();
File nativeArtifactFile2 = Files.createFile(temporaryFolder.toPath().resolve("sample2")).toFile();
assertThat(nativeArtifactFile2.setExecutable(true)).isTrue();
JavaProject javaProject = JavaProject.builder()
.artifactId("sample")
.buildDirectory(temporaryFolder)
.plugin(Plugin.builder()
.groupId("org.graalvm.buildtools")
.artifactId("native-maven-plugin")
.build())
.build();

// When + Then
assertThatIllegalStateException()
.isThrownBy(() -> SpringBootUtil.getNativeArtifactFile(javaProject))
.withMessage("More than one native executable file found in " + temporaryFolder.getAbsolutePath());
}

@Test
void getNativeArtifactFile_whenNativeExecutableInStandardMavenBuildDirectoryAndImageNameOverridden_thenReturnNativeArtifact(@TempDir File temporaryFolder) throws IOException {
// Given
File nativeArtifactFile = Files.createFile(temporaryFolder.toPath().resolve("custom-native-name")).toFile();
assertThat(nativeArtifactFile.setExecutable(true)).isTrue();
JavaProject javaProject = JavaProject.builder()
.artifactId("sample")
.buildDirectory(temporaryFolder)
.plugin(Plugin.builder()
.groupId("org.graalvm.buildtools")
.artifactId("native-maven-plugin")
.build())
.build();

// When
File nativeArtifactFound = SpringBootUtil.getNativeArtifactFile(javaProject);

// Then
assertThat(nativeArtifactFound).hasName("custom-native-name");
}

@Test
void getNativeArtifactFile_whenNativeExecutableInStandardGradleNativeDirectory_thenReturnNativeArtifact(@TempDir File temporaryFolder) throws IOException {
// Given
Files.createDirectories(temporaryFolder.toPath().resolve("native").resolve("nativeCompile"));
File nativeArtifactFile = Files.createFile(temporaryFolder.toPath().resolve("native").resolve("nativeCompile").resolve("sample")).toFile();
assertThat(nativeArtifactFile.setExecutable(true)).isTrue();
JavaProject javaProject = JavaProject.builder()
.artifactId("sample")
.buildDirectory(temporaryFolder)
.plugin(Plugin.builder()
.groupId("org.graalvm.buildtools.native")
.artifactId("org.graalvm.buildtools.native.gradle.plugin")
.build())
.build();

// When
File nativeArtifactFound = SpringBootUtil.getNativeArtifactFile(javaProject);

// Then
assertThat(nativeArtifactFound).hasName("sample");
}
}
14 changes: 14 additions & 0 deletions jkube-kit/jkube-kit-spring-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,18 @@

</dependencies>

<build>
<resources>
<!-- Copy over default images versions defined above -->
<resource>
<directory>src/main/resources-filtered</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2019 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at:
*
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.jkube.springboot.generator;

import org.eclipse.jkube.generator.api.FromSelector;
import org.eclipse.jkube.generator.api.GeneratorConfig;
import org.eclipse.jkube.generator.api.GeneratorContext;
import org.eclipse.jkube.kit.common.Arguments;
import org.eclipse.jkube.kit.common.Assembly;
import org.eclipse.jkube.kit.common.AssemblyConfiguration;
import org.eclipse.jkube.kit.common.AssemblyFileSet;
import org.eclipse.jkube.kit.common.JavaProject;

import java.io.File;
import java.util.List;

import static org.eclipse.jkube.kit.common.util.FileUtil.getRelativePath;

public class NativeGenerator extends AbstractSpringBootNestedGenerator {
private final File nativeBinary;
private final FromSelector fromSelector;

public NativeGenerator(GeneratorContext generatorContext, GeneratorConfig generatorConfig, File nativeBinary) {
super(generatorContext, generatorConfig);
this.nativeBinary = nativeBinary;
fromSelector = new FromSelector.Default(generatorContext, "springboot-native");
}


@Override
public String getFrom() {
return fromSelector.getFrom();
}

@Override
public String getDefaultJolokiaPort() {
return "0";
}

@Override
public String getDefaultPrometheusPort() {
return "0";
}

@Override
public Arguments getBuildEntryPoint() {
return Arguments.builder()
.execArgument("./" + nativeBinary.getName())
.build();
}

@Override
public String getBuildWorkdir() {
return "/";
}

@Override
public String getTargetDir() {
return "/";
}

@Override
public AssemblyConfiguration createAssemblyConfiguration(List<AssemblyFileSet> defaultFileSets) {
Assembly.AssemblyBuilder assemblyBuilder = Assembly.builder();
final JavaProject project = getProject();
final AssemblyFileSet.AssemblyFileSetBuilder artifactFileSetBuilder = AssemblyFileSet.builder()
.outputDirectory(new File("."))
.directory(getRelativePath(project.getBaseDirectory(), nativeBinary.getParentFile()))
.fileMode("0755");
artifactFileSetBuilder.include(nativeBinary.getName());

assemblyBuilder.fileSets(defaultFileSets);
assemblyBuilder.fileSet(artifactFileSetBuilder.build());

return AssemblyConfiguration.builder()
.targetDir(getTargetDir())
.excludeFinalOutputArtifact(true)
.layer(assemblyBuilder.build())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import static org.eclipse.jkube.generator.javaexec.JavaExecGenerator.JOLOKIA_PORT_DEFAULT;
import static org.eclipse.jkube.generator.javaexec.JavaExecGenerator.PROMETHEUS_PORT_DEFAULT;
import static org.eclipse.jkube.kit.common.util.SpringBootUtil.isLayeredJar;
import java.io.File;
import static org.eclipse.jkube.kit.common.util.SpringBootUtil.getNativeArtifactFile;
import static org.eclipse.jkube.kit.common.util.SpringBootUtil.getNativePlugin;

public interface SpringBootNestedGenerator {
JavaProject getProject();
Expand Down Expand Up @@ -61,6 +64,12 @@ default Map<String, String> getEnv() {
}

static SpringBootNestedGenerator from(GeneratorContext generatorContext, GeneratorConfig generatorConfig, FatJarDetector.Result fatJarDetectorResult) {
if (getNativePlugin(generatorContext.getProject()) != null) {
File nativeBinary = getNativeArtifactFile(generatorContext.getProject());
if (nativeBinary != null) {
return new NativeGenerator(generatorContext, generatorConfig, nativeBinary);
}
}
if (fatJarDetectorResult != null && fatJarDetectorResult.getArchiveFile() != null &&
isLayeredJar(fatJarDetectorResult.getArchiveFile())) {
return new LayeredJarGenerator(generatorContext, generatorConfig, fatJarDetectorResult.getArchiveFile());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#
# Copyright (c) 2019 Red Hat, Inc.
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at:
#
# https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Red Hat, Inc. - initial API and implementation
#

# Properties for specifying the default images version to use
# The replacement values are defined in the parent pom.xml as properties

# Upstream images
springboot-native.upstream.s2i=${image.springboot-native.upstream.s2i}
springboot-native.upstream.docker=${image.springboot-native.upstream.docker}
springboot-native.upstream.istag=${image.springboot-native.upstream.istag}
Loading

0 comments on commit 047e718

Please sign in to comment.